├── .gitattributes ├── .gitignore ├── IncludeToolBox.sln ├── IncludeToolbox.vsix ├── IncludeToolbox ├── Commands │ ├── CommandBase.cs │ ├── CommandSetGuids.cs │ ├── FormatIncludes.cs │ ├── IncludeGraphToolWindow.cs │ ├── IncludeWhatYouUse.cs │ ├── TrialAndErrorRemoval_CodeWindow.cs │ └── TrialAndErrorRemoval_Project.cs ├── Formatter │ ├── IncludeFormatter.cs │ └── IncludeLineInfo.cs ├── Graph │ ├── CompilationBasedGraphParser.cs │ ├── CustomGraphParser.cs │ ├── DGMLGraph.cs │ ├── IncludeGraph.cs │ └── IncludeGraphToDGML.cs ├── GraphWindow │ ├── Commands │ │ ├── RefreshIncludeGraph.cs │ │ ├── RefreshModeComboBox.cs │ │ ├── RefreshModeComboBoxOptions.cs │ │ └── SaveDGML.cs │ ├── PropertyChangedBase.cs │ ├── View │ │ ├── IncludeGraphControl.xaml │ │ ├── IncludeGraphControl.xaml.cs │ │ ├── IncludeGraphToolWindow.cs │ │ └── ToolWindowStyle.xaml │ └── ViewModel │ │ ├── FolderIncludeTreeItem.cs │ │ ├── HierarchyIncludeTreeViewItem.cs │ │ ├── IncludeGraphViewModel.cs │ │ └── IncludeTreeViewItem.cs ├── IncludeToolbox.args.json ├── IncludeToolbox.csproj ├── IncludeWhatYouUse │ ├── IWYU.cs │ └── IWYUDownload.cs ├── Options │ ├── Constants.cs │ ├── FormatterOptionsPage.cs │ ├── IncludeWhatYouUseOptionsPage.cs │ ├── OptionsPage.cs │ ├── TrialAndErrorRemovalOptionsPage.cs │ └── ViewerOptionsPage.cs ├── Output.cs ├── Package │ ├── CommandDefinitions.vsct │ ├── IncludeToolboxPackage.cs │ ├── Key.snk │ ├── VSPackage.resx │ └── source.extension.vsixmanifest ├── Properties │ └── AssemblyInfo.cs ├── RegexUtils.cs ├── Resources │ ├── IncludeFormatterIcons.pdn │ ├── IncludeFormatterIcons.png │ ├── IncludeFormatterPackage.png │ ├── IncludeGraphToolbarIcons.png │ ├── include13.png │ └── include16.png ├── TrialAndErrorRemoval.cs ├── Utils.cs ├── VCHelper.cs ├── VSUtils.cs └── license.txt ├── README.md ├── Tests ├── IncludeFormatingTest.cs ├── IncludeGraphTest.cs ├── IncludeLineInfoTests.cs ├── IncludeWhatYouUseTests.cs ├── Properties │ └── AssemblyInfo.cs ├── Tests.csproj └── testdata │ ├── includegraph.dgml │ ├── includegraph_grouped.dgml │ ├── includegraph_withcolors.dgml │ ├── includegraph_withcolors_grouped.dgml │ ├── simplegraph.dgml │ ├── source0.cpp │ ├── source1.cpp │ ├── subdir │ ├── inline.inl │ ├── subdir │ │ └── subsub.h │ └── testinclude.h │ └── testinclude.h └── license.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | IncludeToolbox.vsix filter=lfs diff=lfs merge=lfs -text 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | 214 | # Other.. 215 | *.opendb 216 | *.db 217 | -------------------------------------------------------------------------------- /IncludeToolBox.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.156 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IncludeToolbox", "IncludeToolbox\IncludeToolbox.csproj", "{F9E250C6-A7AD-4888-8F17-6876736B8DCF}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{F577F5D2-5E3C-43BE-9030-AF2609A0917A}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {F9E250C6-A7AD-4888-8F17-6876736B8DCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {F9E250C6-A7AD-4888-8F17-6876736B8DCF}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {F9E250C6-A7AD-4888-8F17-6876736B8DCF}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {F9E250C6-A7AD-4888-8F17-6876736B8DCF}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {F577F5D2-5E3C-43BE-9030-AF2609A0917A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {F577F5D2-5E3C-43BE-9030-AF2609A0917A}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {F577F5D2-5E3C-43BE-9030-AF2609A0917A}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {F577F5D2-5E3C-43BE-9030-AF2609A0917A}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {87A2994A-8E27-4066-8C05-7E4DD461D23D} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /IncludeToolbox.vsix: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:d410334bec97304d46120a2621251cefa9e703237c249f5891d3cac2c4b6eb36 3 | size 71090 4 | -------------------------------------------------------------------------------- /IncludeToolbox/Commands/CommandBase.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | using System.ComponentModel.Design; 4 | using Microsoft.VisualStudio.Shell; 5 | using Task = System.Threading.Tasks.Task; 6 | 7 | namespace IncludeToolbox.Commands 8 | { 9 | internal abstract class CommandBase where T : CommandBase, new() 10 | { 11 | /// 12 | /// Initializes the singleton instance of the command. 13 | /// 14 | /// Owner package, not null. 15 | public static void Initialize(Package package) 16 | { 17 | if (package == null) 18 | throw new ArgumentNullException("package"); 19 | ThreadHelper.ThrowIfNotOnUIThread(); 20 | 21 | Instance = new T(); 22 | Instance.Package = package; 23 | Instance.SetupMenuCommand(); 24 | } 25 | 26 | protected virtual void SetupMenuCommand() 27 | { 28 | OleMenuCommandService commandService = ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService; 29 | if(commandService == null) 30 | { 31 | Output.Instance.WriteLine("Failed to retrieve MenuCommandService. No commands could be registered!"); 32 | return; 33 | } 34 | 35 | EventHandler callback = async (sender, e) => 36 | { 37 | try 38 | { 39 | await this.MenuItemCallback(sender, e); 40 | } 41 | catch (Exception exception) 42 | { 43 | await Output.Instance.ErrorMsg("Unexpected Error: {0}", exception.ToString()); 44 | } 45 | }; 46 | 47 | menuCommand = new OleMenuCommand(callback, CommandID); 48 | commandService.AddCommand(menuCommand); 49 | } 50 | 51 | 52 | /// 53 | /// Gets the instance of the command. 54 | /// 55 | public static T Instance 56 | { 57 | get; 58 | private set; 59 | } 60 | 61 | /// 62 | /// VS Package that provides this command, not null. 63 | /// 64 | protected Package Package { get; private set; } 65 | 66 | protected IServiceProvider ServiceProvider => Package; 67 | 68 | protected OleMenuCommand menuCommand; 69 | 70 | public abstract CommandID CommandID { get; } 71 | 72 | protected abstract Task MenuItemCallback(object sender, EventArgs e); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /IncludeToolbox/Commands/CommandSetGuids.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace IncludeToolbox.Commands 4 | { 5 | static class CommandSetGuids 6 | { 7 | /// 8 | /// Command menu group (command set GUID) for document menu. 9 | /// 10 | public static readonly Guid MenuGroup = new Guid("aef3a531-8af4-4b7b-800a-e32503dfc6e2"); 11 | 12 | /// 13 | /// Command menu group (command set GUID) for tool menu. 14 | /// 15 | public static readonly Guid ToolGroup = new Guid("032eb795-1f1c-440d-af98-43cdc1de7a8b"); 16 | 17 | /// 18 | /// Command menu group for commands in the project menu. 19 | /// 20 | public static readonly Guid ProjectGroup = new Guid("1970ECF3-6C03-4CCF-B422-8DD07F774ED8"); 21 | 22 | /// 23 | /// Commandset for all toolbar elements in the include graph toolwindow. 24 | /// 25 | public static readonly Guid GraphWindowToolbarCmdSet = new Guid("0B242452-870A-489B-8336-88FD01AEF0C1"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /IncludeToolbox/Commands/FormatIncludes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.Shell; 5 | using Microsoft.VisualStudio.Text; 6 | using Microsoft.VisualStudio.Text.Editor; 7 | using Task = System.Threading.Tasks.Task; 8 | 9 | namespace IncludeToolbox.Commands 10 | { 11 | /// 12 | /// Command handler 13 | /// 14 | internal sealed class FormatIncludes : CommandBase 15 | { 16 | public override CommandID CommandID => new CommandID(CommandSetGuids.MenuGroup, 0x0100); 17 | 18 | public FormatIncludes() 19 | { 20 | } 21 | 22 | protected override void SetupMenuCommand() 23 | { 24 | base.SetupMenuCommand(); 25 | menuCommand.BeforeQueryStatus += UpdateVisibility; 26 | } 27 | 28 | private void UpdateVisibility(object sender, EventArgs e) 29 | { 30 | // Check whether any includes are selected. 31 | var viewHost = VSUtils.GetCurrentTextViewHost(); 32 | var selectionSpan = GetSelectionSpan(viewHost); 33 | var lines = Formatter.IncludeLineInfo.ParseIncludes(selectionSpan.GetText(), Formatter.ParseOptions.RemoveEmptyLines); 34 | 35 | menuCommand.Visible = lines.Any(x => x.ContainsActiveInclude); 36 | } 37 | 38 | /// 39 | /// Returns process selection range - whole lines! 40 | /// 41 | SnapshotSpan GetSelectionSpan(IWpfTextViewHost viewHost) 42 | { 43 | var sel = viewHost.TextView.Selection.StreamSelectionSpan; 44 | var start = new SnapshotPoint(viewHost.TextView.TextSnapshot, sel.Start.Position).GetContainingLine().Start; 45 | var end = new SnapshotPoint(viewHost.TextView.TextSnapshot, sel.End.Position).GetContainingLine().End; 46 | 47 | return new SnapshotSpan(start, end); 48 | } 49 | 50 | /// 51 | /// This function is the callback used to execute the command when the menu item is clicked. 52 | /// See the constructor to see how the menu item is associated with this function using 53 | /// OleMenuCommandService service and MenuCommand class. 54 | /// 55 | /// Event sender. 56 | /// Event args. 57 | protected override async Task MenuItemCallback(object sender, EventArgs e) 58 | { 59 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 60 | 61 | var settings = (FormatterOptionsPage)Package.GetDialogPage(typeof(FormatterOptionsPage)); 62 | 63 | // Try to find absolute paths 64 | var document = VSUtils.GetDTE().ActiveDocument; 65 | var project = document.ProjectItem?.ContainingProject; 66 | if (project == null) 67 | { 68 | Output.Instance.WriteLine("The document '{0}' is not part of a project.", document.Name); 69 | return; 70 | } 71 | var includeDirectories = VSUtils.GetProjectIncludeDirectories(project); 72 | 73 | // Read. 74 | var viewHost = VSUtils.GetCurrentTextViewHost(); 75 | var selectionSpan = GetSelectionSpan(viewHost); 76 | 77 | // Format 78 | string formatedText = Formatter.IncludeFormatter.FormatIncludes(selectionSpan.GetText(), document.FullName, includeDirectories, settings); 79 | 80 | // Overwrite. 81 | using (var edit = viewHost.TextView.TextBuffer.CreateEdit()) 82 | { 83 | edit.Replace(selectionSpan, formatedText); 84 | edit.Apply(); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /IncludeToolbox/Commands/IncludeGraphToolWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Design; 3 | using Microsoft.VisualStudio.Shell; 4 | using Microsoft.VisualStudio.Shell.Interop; 5 | using Task = System.Threading.Tasks.Task; 6 | 7 | namespace IncludeToolbox.Commands 8 | { 9 | /// 10 | /// Command handler 11 | /// 12 | internal sealed class IncludeGraphToolWindow : CommandBase 13 | { 14 | public override CommandID CommandID => new CommandID(CommandSetGuids.ToolGroup, 0x0102); 15 | 16 | /// 17 | /// Shows the tool window when the menu item is clicked. 18 | /// 19 | /// The event sender. 20 | /// The event args. 21 | protected override async Task MenuItemCallback(object sender, EventArgs e) 22 | { 23 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 24 | 25 | // Get the instance number 0 of this tool window. This window is single instance so this instance 26 | // is actually the only one. 27 | // The last flag is set to true so that if the tool window does not exists it will be created. 28 | ToolWindowPane window = Package.FindToolWindow(typeof(GraphWindow.IncludeGraphToolWindow), 0, true); 29 | if (window?.Frame == null) 30 | { 31 | await Output.Instance.ErrorMsg("Failed to open Include Graph window!"); 32 | } 33 | else 34 | { 35 | IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; 36 | windowFrame.SetProperty((int)__VSFPROPID.VSFPROPID_CmdUIGuid, GraphWindow.IncludeGraphToolWindow.GUIDString); 37 | Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show()); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /IncludeToolbox/Commands/IncludeWhatYouUse.cs: -------------------------------------------------------------------------------- 1 | using IncludeToolbox.IncludeWhatYouUse; 2 | using Microsoft.VisualStudio.Shell; 3 | using Microsoft.VisualStudio.Shell.Interop; 4 | using System; 5 | using System.ComponentModel.Design; 6 | using System.IO; 7 | using System.Threading.Tasks; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace IncludeToolbox.Commands 11 | { 12 | /// 13 | /// Command handler 14 | /// 15 | internal sealed class IncludeWhatYouUse : CommandBase 16 | { 17 | public override CommandID CommandID => new CommandID(CommandSetGuids.MenuGroup, 0x0103); 18 | 19 | /// 20 | /// Whether we already checked for updates. 21 | /// 22 | private bool checkedForUpdatesThisSession = false; 23 | 24 | public IncludeWhatYouUse() 25 | { 26 | } 27 | 28 | protected override void SetupMenuCommand() 29 | { 30 | base.SetupMenuCommand(); 31 | menuCommand.BeforeQueryStatus += UpdateVisibility; 32 | } 33 | 34 | private void UpdateVisibility(object sender, EventArgs e) 35 | { 36 | // Needs to be part of a VCProject to be applicable. 37 | var document = VSUtils.GetDTE()?.ActiveDocument; 38 | menuCommand.Visible = VSUtils.VCUtils.IsVCProject(document?.ProjectItem?.ContainingProject); 39 | } 40 | 41 | private async Task DownloadIWYUWithProgressBar(string executablePath, IVsThreadedWaitDialogFactory dialogFactory) 42 | { 43 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 44 | 45 | IVsThreadedWaitDialog2 progressDialog; 46 | dialogFactory.CreateInstance(out progressDialog); 47 | if (progressDialog == null) 48 | { 49 | Output.Instance.WriteLine("Failed to get create wait dialog."); 50 | return false; 51 | } 52 | 53 | progressDialog.StartWaitDialogWithPercentageProgress( 54 | szWaitCaption: "Include Toolbox - Downloading include-what-you-use", 55 | szWaitMessage: "", // comes in later. 56 | szProgressText: null, 57 | varStatusBmpAnim: null, 58 | szStatusBarText: "Downloading include-what-you-use", 59 | fIsCancelable: true, 60 | iDelayToShowDialog: 0, 61 | iTotalSteps: 100, 62 | iCurrentStep: 0); 63 | 64 | var cancellationToken = new System.Threading.CancellationTokenSource(); 65 | 66 | try 67 | { 68 | await IWYUDownload.DownloadIWYU(executablePath, delegate (string section, string status, float percentage) 69 | { 70 | ThreadHelper.ThrowIfNotOnUIThread(); 71 | 72 | bool canceled; 73 | progressDialog.UpdateProgress( 74 | szUpdatedWaitMessage: section, 75 | szProgressText: status, 76 | szStatusBarText: $"Downloading include-what-you-use - {section} - {status}", 77 | iCurrentStep: (int)(percentage * 100), 78 | iTotalSteps: 100, 79 | fDisableCancel: true, 80 | pfCanceled: out canceled); 81 | if (canceled) 82 | { 83 | cancellationToken.Cancel(); 84 | } 85 | }, cancellationToken.Token); 86 | } 87 | catch (Exception e) 88 | { 89 | await Output.Instance.ErrorMsg("Failed to download include-what-you-use: {0}", e); 90 | return false; 91 | } 92 | finally 93 | { 94 | progressDialog.EndWaitDialog(); 95 | } 96 | 97 | return true; 98 | } 99 | 100 | private async Task OptionalDownloadOrUpdate(IncludeWhatYouUseOptionsPage settings, IVsThreadedWaitDialogFactory dialogFactory) 101 | { 102 | // Check existence, offer to download if it's not there. 103 | bool downloadedNewIwyu = false; 104 | if (!File.Exists(settings.ExecutablePath)) 105 | { 106 | if (await Output.Instance.YesNoMsg($"Can't find include-what-you-use in '{settings.ExecutablePath}'. Do you want to download it from '{IWYUDownload.DisplayRepositorURL}'?") != Output.MessageResult.Yes) 107 | { 108 | return; 109 | } 110 | 111 | downloadedNewIwyu = await DownloadIWYUWithProgressBar(settings.ExecutablePath, dialogFactory); 112 | if (!downloadedNewIwyu) 113 | return; 114 | } 115 | else if (settings.AutomaticCheckForUpdates && !checkedForUpdatesThisSession) 116 | { 117 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 118 | 119 | IVsThreadedWaitDialog2 dialog = null; 120 | dialogFactory.CreateInstance(out dialog); 121 | dialog?.StartWaitDialog("Include Toolbox", "Running Include-What-You-Use", null, null, "Checking for Updates for include-what-you-use", 0, false, true); 122 | bool newVersionAvailable = await IWYUDownload.IsNewerVersionAvailableOnline(settings.ExecutablePath); 123 | dialog?.EndWaitDialog(); 124 | 125 | if (newVersionAvailable) 126 | { 127 | checkedForUpdatesThisSession = true; 128 | if (await Output.Instance.YesNoMsg($"There is a new version of include-what-you-use available. Do you want to download it from '{IWYUDownload.DisplayRepositorURL}'?") == Output.MessageResult.Yes) 129 | { 130 | downloadedNewIwyu = await DownloadIWYUWithProgressBar(settings.ExecutablePath, dialogFactory); 131 | } 132 | } 133 | } 134 | if (downloadedNewIwyu) 135 | settings.AddMappingFiles(IWYUDownload.GetMappingFilesNextToIwyuPath(settings.ExecutablePath)); 136 | } 137 | 138 | /// 139 | /// This function is the callback used to execute the command when the menu item is clicked. 140 | /// See the constructor to see how the menu item is associated with this function using 141 | /// OleMenuCommandService service and MenuCommand class. 142 | /// 143 | /// Event sender. 144 | /// Event args. 145 | protected override async Task MenuItemCallback(object sender, EventArgs e) 146 | { 147 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 148 | 149 | var settingsIwyu = (IncludeWhatYouUseOptionsPage)Package.GetDialogPage(typeof(IncludeWhatYouUseOptionsPage)); 150 | Output.Instance.Clear(); 151 | 152 | var document = VSUtils.GetDTE().ActiveDocument; 153 | if (document == null) 154 | { 155 | Output.Instance.WriteLine("No active document!"); 156 | return; 157 | } 158 | var project = document.ProjectItem?.ContainingProject; 159 | if (project == null) 160 | { 161 | Output.Instance.WriteLine("The document {0} is not part of a project.", document.Name); 162 | return; 163 | } 164 | 165 | var dialogFactory = ServiceProvider.GetService(typeof(SVsThreadedWaitDialogFactory)) as IVsThreadedWaitDialogFactory; 166 | if (dialogFactory == null) 167 | { 168 | Output.Instance.WriteLine("Failed to get IVsThreadedWaitDialogFactory service."); 169 | return; 170 | } 171 | 172 | await OptionalDownloadOrUpdate(settingsIwyu, dialogFactory); 173 | 174 | // We should really have it now, but just in case our update or download method screwed up. 175 | if (!File.Exists(settingsIwyu.ExecutablePath)) 176 | { 177 | await Output.Instance.ErrorMsg("Unexpected error: Can't find include-what-you-use.exe after download/update."); 178 | return; 179 | } 180 | checkedForUpdatesThisSession = true; 181 | 182 | // Save all documents. 183 | try 184 | { 185 | document.DTE.Documents.SaveAll(); 186 | } 187 | catch(Exception saveException) 188 | { 189 | Output.Instance.WriteLine("Failed to get save all documents: {0}", saveException); 190 | } 191 | 192 | // Start wait dialog. 193 | { 194 | IVsThreadedWaitDialog2 dialog = null; 195 | dialogFactory.CreateInstance(out dialog); 196 | dialog?.StartWaitDialog("Include Toolbox", "Running include-what-you-use", null, null, "Running include-what-you-use", 0, false, true); 197 | 198 | string output = await IWYU.RunIncludeWhatYouUse(document.FullName, project, settingsIwyu); 199 | if (settingsIwyu.ApplyProposal && output != null) 200 | { 201 | var settingsFormatting = (FormatterOptionsPage)Package.GetDialogPage(typeof(FormatterOptionsPage)); 202 | await IWYU.Apply(output, settingsIwyu.RunIncludeFormatter, settingsFormatting); 203 | } 204 | 205 | dialog?.EndWaitDialog(); 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /IncludeToolbox/Commands/TrialAndErrorRemoval_CodeWindow.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | using System; 4 | using System.ComponentModel.Design; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Windows; 9 | using Microsoft.VisualStudio.Shell; 10 | using Microsoft.VisualStudio.Shell.Interop; 11 | using EnvDTE; 12 | using Microsoft.VisualStudio.Text; 13 | using Task = System.Threading.Tasks.Task; 14 | 15 | namespace IncludeToolbox.Commands 16 | { 17 | /// 18 | /// Command handler 19 | /// 20 | internal sealed class TrialAndErrorRemoval_CodeWindow : CommandBase 21 | { 22 | public override CommandID CommandID => new CommandID(CommandSetGuids.MenuGroup, 0x0104); 23 | 24 | private TrialAndErrorRemoval impl; 25 | 26 | public TrialAndErrorRemoval_CodeWindow() 27 | { 28 | } 29 | 30 | protected override void SetupMenuCommand() 31 | { 32 | base.SetupMenuCommand(); 33 | 34 | impl = new TrialAndErrorRemoval(); 35 | menuCommand.BeforeQueryStatus += UpdateVisibility; 36 | } 37 | 38 | private async void UpdateVisibility(object sender, EventArgs e) 39 | { 40 | menuCommand.Visible = (await VSUtils.VCUtils.IsCompilableFile(VSUtils.GetDTE().ActiveDocument)).Result; 41 | } 42 | 43 | /// 44 | /// This function is the callback used to execute the command when the menu item is clicked. 45 | /// See the constructor to see how the menu item is associated with this function using 46 | /// OleMenuCommandService service and MenuCommand class. 47 | /// 48 | /// Event sender. 49 | /// Event args. 50 | protected override async Task MenuItemCallback(object sender, EventArgs e) 51 | { 52 | var document = VSUtils.GetDTE().ActiveDocument; 53 | if (document != null) 54 | await impl.PerformTrialAndErrorIncludeRemoval(document, (TrialAndErrorRemovalOptionsPage)Package.GetDialogPage(typeof(TrialAndErrorRemovalOptionsPage))); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /IncludeToolbox/Commands/TrialAndErrorRemoval_Project.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Microsoft.VisualStudio; 3 | using Microsoft.VisualStudio.Shell; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel.Design; 7 | using System.Threading.Tasks; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace IncludeToolbox.Commands 11 | { 12 | /// 13 | /// Command handler 14 | /// 15 | internal sealed class TrialAndErrorRemoval_Project : CommandBase 16 | { 17 | public override CommandID CommandID => new CommandID(CommandSetGuids.ProjectGroup, 0x0100); 18 | 19 | private TrialAndErrorRemoval impl; 20 | private TrialAndErrorRemovalOptionsPage settings; 21 | 22 | private ProjectItems projectItems = null; 23 | private int numTotalRemovedIncludes = 0; 24 | private Queue projectFiles; 25 | 26 | public TrialAndErrorRemoval_Project() 27 | { 28 | projectFiles = new Queue(); 29 | } 30 | 31 | protected override void SetupMenuCommand() 32 | { 33 | base.SetupMenuCommand(); 34 | 35 | impl = new TrialAndErrorRemoval(); 36 | impl.OnFileFinished += OnDocumentIncludeRemovalFinished; 37 | menuCommand.BeforeQueryStatus += UpdateVisibility; 38 | 39 | settings = (TrialAndErrorRemovalOptionsPage)Package.GetDialogPage(typeof(TrialAndErrorRemovalOptionsPage)); 40 | } 41 | 42 | private void OnDocumentIncludeRemovalFinished(int removedIncludes, bool canceled) 43 | { 44 | _ = Task.Run(async () => 45 | { 46 | numTotalRemovedIncludes += removedIncludes; 47 | if (canceled || !await ProcessNextFile()) 48 | { 49 | _ = Output.Instance.InfoMsg("Removed total of {0} #include directives from project.", numTotalRemovedIncludes); 50 | numTotalRemovedIncludes = 0; 51 | } 52 | }); 53 | } 54 | 55 | private void UpdateVisibility(object sender, EventArgs e) 56 | { 57 | ThreadHelper.ThrowIfNotOnUIThread(); 58 | string reason; 59 | var project = GetSelectedCppProject(out reason); 60 | menuCommand.Visible = project != null; 61 | } 62 | 63 | static Project GetSelectedCppProject(out string reasonForFailure) 64 | { 65 | ThreadHelper.ThrowIfNotOnUIThread(); 66 | 67 | reasonForFailure = ""; 68 | 69 | var selectedItems = VSUtils.GetDTE().SelectedItems; 70 | if (selectedItems.Count < 1) 71 | { 72 | reasonForFailure = "Selection is empty!"; 73 | return null; 74 | } 75 | 76 | // Reading .Item(object) behaves weird, but iterating works. 77 | foreach (SelectedItem item in selectedItems) 78 | { 79 | Project vcProject = item?.Project; 80 | if (VSUtils.VCUtils.IsVCProject(vcProject)) 81 | { 82 | return vcProject; 83 | } 84 | } 85 | 86 | reasonForFailure = "Selection does not contain a C++ project!"; 87 | return null; 88 | } 89 | 90 | private async Task ProcessNextFile() 91 | { 92 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 93 | 94 | while (projectFiles.Count > 0) 95 | { 96 | ProjectItem projectItem = projectFiles.Dequeue(); 97 | 98 | Document document = null; 99 | try 100 | { 101 | document = projectItem.Open().Document; 102 | } 103 | catch (Exception) 104 | { 105 | } 106 | if (document == null) 107 | continue; 108 | 109 | bool started = await impl.PerformTrialAndErrorIncludeRemoval(document, settings); 110 | if (started) 111 | return true; 112 | } 113 | return false; 114 | } 115 | 116 | private static void RecursiveFindFilesInProject(ProjectItems items, ref Queue projectFiles) 117 | { 118 | ThreadHelper.ThrowIfNotOnUIThread(); 119 | 120 | var e = items.GetEnumerator(); 121 | while (e.MoveNext()) 122 | { 123 | var item = e.Current; 124 | if (item == null) 125 | continue; 126 | var projectItem = item as ProjectItem; 127 | if (projectItem == null) 128 | continue; 129 | 130 | Guid projectItemKind = new Guid(projectItem.Kind); 131 | if (projectItemKind == VSConstants.GUID_ItemType_VirtualFolder || 132 | projectItemKind == VSConstants.GUID_ItemType_PhysicalFolder) 133 | { 134 | RecursiveFindFilesInProject(projectItem.ProjectItems, ref projectFiles); 135 | } 136 | else if (projectItemKind == VSConstants.GUID_ItemType_PhysicalFile) 137 | { 138 | projectFiles.Enqueue(projectItem); 139 | } 140 | else 141 | { 142 | Output.Instance.WriteLine("Unexpected Error: Unknown projectItem {0} of Kind {1}", projectItem.Name, projectItem.Kind); 143 | } 144 | } 145 | } 146 | 147 | private async Task PerformTrialAndErrorRemoval(Project project) 148 | { 149 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 150 | 151 | projectItems = project.ProjectItems; 152 | 153 | projectFiles.Clear(); 154 | RecursiveFindFilesInProject(projectItems, ref projectFiles); 155 | 156 | if (projectFiles.Count > 2) 157 | { 158 | if (await Output.Instance.YesNoMsg("Attention! Trial and error include removal on large projects make take up to several hours! In this time you will not be able to use Visual Studio. Are you sure you want to continue?") 159 | != Output.MessageResult.Yes) 160 | { 161 | return; 162 | } 163 | } 164 | 165 | numTotalRemovedIncludes = 0; 166 | await ProcessNextFile(); 167 | } 168 | 169 | 170 | /// 171 | /// This function is the callback used to execute the command when the menu item is clicked. 172 | /// See the constructor to see how the menu item is associated with this function using 173 | /// OleMenuCommandService service and MenuCommand class. 174 | /// 175 | /// Event sender. 176 | /// Event args. 177 | protected override async Task MenuItemCallback(object sender, EventArgs e) 178 | { 179 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 180 | 181 | if (TrialAndErrorRemoval.WorkInProgress) 182 | { 183 | await Output.Instance.ErrorMsg("Trial and error include removal already in progress!"); 184 | return; 185 | } 186 | 187 | try 188 | { 189 | Project project = GetSelectedCppProject(out string reasonForFailure); 190 | if (project == null) 191 | { 192 | Output.Instance.WriteLine(reasonForFailure); 193 | return; 194 | } 195 | 196 | await PerformTrialAndErrorRemoval(project); 197 | } 198 | finally 199 | { 200 | projectItems = null; 201 | } 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /IncludeToolbox/Graph/CompilationBasedGraphParser.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Microsoft.VisualStudio.PlatformUI; 3 | using Microsoft.VisualStudio.Shell; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using Task = System.Threading.Tasks.Task; 10 | 11 | namespace IncludeToolbox.Graph 12 | { 13 | public static class CompilationBasedGraphParser 14 | { 15 | public delegate Task OnCompleteCallback(IncludeGraph graph, Document document, bool success); 16 | 17 | // There can always be only one compilation operation and it takes a while. 18 | // This makes the whole mechanism effectively a singletonish thing. 19 | private static bool CompilationOngoing { get { return documentBeingCompiled != null; } } 20 | private static bool showIncludeSettingBefore = false; 21 | private static OnCompleteCallback onCompleted; 22 | private static Document documentBeingCompiled; 23 | private static IncludeGraph graphBeingExtended; 24 | 25 | 26 | public static async Task CanPerformShowIncludeCompilation(Document document) 27 | { 28 | if (CompilationOngoing) 29 | { 30 | return new BoolWithReason 31 | { 32 | Result = false, 33 | Reason = "Can't compile while another file is being compiled.", 34 | }; 35 | } 36 | 37 | var dte = VSUtils.GetDTE(); 38 | if (dte == null) 39 | { 40 | return new BoolWithReason 41 | { 42 | Result = false, 43 | Reason = "Failed to acquire dte object.", 44 | }; 45 | } 46 | 47 | var result = await VSUtils.VCUtils.IsCompilableFile(document); 48 | if (result.Result == false) 49 | { 50 | result.Reason = $"Can't extract include graph since current file '{document?.FullName ?? ""}' can't be compiled: {result.Reason}."; 51 | return result; 52 | } 53 | 54 | return new BoolWithReason { Result = true, Reason = "" }; 55 | } 56 | 57 | /// 58 | /// Parses a given source file using cl.exe with the /showIncludes option and adds the output to the original graph. 59 | /// 60 | /// 61 | /// If this is the first file, the graph is necessarily a tree after this operation. 62 | /// 63 | /// true if successful, false otherwise. 64 | public static async Task AddIncludesRecursively_ShowIncludesCompilation(this IncludeGraph graph, Document document, OnCompleteCallback onCompleted) 65 | { 66 | var canPerformShowIncludeCompilation = await CanPerformShowIncludeCompilation(document); 67 | if (!canPerformShowIncludeCompilation.Result) 68 | { 69 | await Output.Instance.ErrorMsg(canPerformShowIncludeCompilation.Reason); 70 | return false; 71 | } 72 | 73 | try 74 | { 75 | var dte = VSUtils.GetDTE(); 76 | if (dte == null) 77 | { 78 | await Output.Instance.ErrorMsg("Failed to acquire dte object."); 79 | return false; 80 | } 81 | 82 | try 83 | { 84 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 85 | showIncludeSettingBefore = VSUtils.VCUtils.GetCompilerSetting_ShowIncludes(document.ProjectItem?.ContainingProject); 86 | VSUtils.VCUtils.SetCompilerSetting_ShowIncludes(document.ProjectItem?.ContainingProject, true); 87 | } 88 | catch (VCQueryFailure queryFailure) 89 | { 90 | await Output.Instance.ErrorMsg("Can't compile with show includes: {0}.", queryFailure.Message); 91 | return false; 92 | } 93 | 94 | // Only after we're through all early out error cases, set static compilation infos. 95 | dte.Events.BuildEvents.OnBuildDone += OnBuildConfigFinished; 96 | CompilationBasedGraphParser.onCompleted = onCompleted; 97 | CompilationBasedGraphParser.documentBeingCompiled = document; 98 | CompilationBasedGraphParser.graphBeingExtended = graph; 99 | 100 | // Even with having the config changed and having compile force==true, we still need to make a dummy change in order to enforce recompilation of this file. 101 | { 102 | document.Activate(); 103 | var documentTextView = VSUtils.GetCurrentTextViewHost(); 104 | var textBuffer = documentTextView.TextView.TextBuffer; 105 | using (var edit = textBuffer.CreateEdit()) 106 | { 107 | edit.Insert(0, " "); 108 | edit.Apply(); 109 | } 110 | using (var edit = textBuffer.CreateEdit()) 111 | { 112 | edit.Replace(new Microsoft.VisualStudio.Text.Span(0, 1), ""); 113 | edit.Apply(); 114 | } 115 | } 116 | 117 | await VSUtils.VCUtils.CompileSingleFile(document); 118 | } 119 | catch(Exception e) 120 | { 121 | await ResetPendingCompilationInfo(); 122 | await Output.Instance.ErrorMsg("Compilation of file '{0}' with /showIncludes failed: {1}.", document.FullName, e); 123 | return false; 124 | } 125 | 126 | return true; 127 | } 128 | 129 | private static async Task ResetPendingCompilationInfo() 130 | { 131 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 132 | 133 | try 134 | { 135 | VSUtils.VCUtils.SetCompilerSetting_ShowIncludes(documentBeingCompiled.ProjectItem?.ContainingProject, showIncludeSettingBefore); 136 | } 137 | catch (VCQueryFailure) { } 138 | 139 | onCompleted = null; 140 | documentBeingCompiled = null; 141 | graphBeingExtended = null; 142 | 143 | VSUtils.GetDTE().Events.BuildEvents.OnBuildDone -= OnBuildConfigFinished; 144 | } 145 | 146 | private static void OnBuildConfigFinished(vsBuildScope Scope, vsBuildAction Action) 147 | { 148 | ThreadHelper.ThrowIfNotOnUIThread(); 149 | 150 | // Sometimes we get this message several times. 151 | if (!CompilationOngoing) 152 | return; 153 | 154 | // Parsing maybe successful for an unsuccessful build! 155 | bool successfulParsing = true; 156 | try 157 | { 158 | string outputText = VSUtils.GetOutputText(); 159 | if (string.IsNullOrEmpty(outputText)) 160 | { 161 | successfulParsing = false; 162 | return; 163 | } 164 | 165 | // What we're building right now is a tree. 166 | // However, combined with the existing data it might be a wide graph. 167 | var includeTreeItemStack = new Stack(); 168 | includeTreeItemStack.Push(graphBeingExtended.CreateOrGetItem(documentBeingCompiled.FullName, out _)); 169 | 170 | var includeDirectories = VSUtils.GetProjectIncludeDirectories(documentBeingCompiled.ProjectItem.ContainingProject); 171 | includeDirectories.Insert(0, PathUtil.Normalize(documentBeingCompiled.Path) + Path.DirectorySeparatorChar); 172 | 173 | const string includeNoteString = "Note: including file: "; 174 | string[] outputLines = System.Text.RegularExpressions.Regex.Split(outputText, "\r\n|\r|\n"); // yes there are actually \r\n in there in some VS versions. 175 | foreach (string line in outputLines) 176 | { 177 | int startIndex = line.IndexOf(includeNoteString); 178 | if (startIndex < 0) 179 | continue; 180 | startIndex += includeNoteString.Length; 181 | 182 | int includeStartIndex = startIndex; 183 | while (includeStartIndex < line.Length && line[includeStartIndex] == ' ') 184 | ++includeStartIndex; 185 | int depth = includeStartIndex - startIndex; 186 | 187 | if (depth >= includeTreeItemStack.Count) 188 | { 189 | includeTreeItemStack.Push(includeTreeItemStack.Peek().Includes.Last().IncludedFile); 190 | } 191 | while (depth < includeTreeItemStack.Count - 1) 192 | includeTreeItemStack.Pop(); 193 | 194 | string fullIncludePath = line.Substring(includeStartIndex); 195 | IncludeGraph.GraphItem includedItem = graphBeingExtended.CreateOrGetItem(fullIncludePath, out _); 196 | includeTreeItemStack.Peek().Includes.Add(new IncludeGraph.Include() { IncludedFile = includedItem }); 197 | } 198 | } 199 | 200 | catch (Exception e) 201 | { 202 | _ = Output.Instance.ErrorMsg("Failed to parse output from /showInclude compilation of file '{0}': {1}", documentBeingCompiled.FullName, e); 203 | successfulParsing = false; 204 | return; 205 | } 206 | finally 207 | { 208 | _ = onCompleted(graphBeingExtended, documentBeingCompiled, successfulParsing); 209 | _ = ResetPendingCompilationInfo(); 210 | } 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /IncludeToolbox/Graph/CustomGraphParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace IncludeToolbox.Graph 7 | { 8 | public static class CustomGraphParser 9 | { 10 | /// 11 | /// Parses a given file text using our own simplified include parsing and adds the output to the original graph. 12 | /// 13 | /// 14 | /// If this is the first file, the graph is *not* necessarily a tree after this operation. 15 | /// 16 | /// For any entry in the graph, it will be assumed that all include have already been parsed, therefore no additional parsing takes place for such files! 17 | /// 18 | /// For simplicity (one could argue also for completeness) we do not run any form of preprocessor. 19 | /// This also means, that we need to assume that every include is included always only exactly once. 20 | /// Obviously, this assumption is generally false! However, in practice a include is rarely included several times intentionally. 21 | /// 22 | /// Filename to the file that should be processed, may be relative. 23 | /// File content that should be parsed. 24 | /// 25 | /// Directories for resolving includes. 26 | /// Unlike in other places in the code, we always try to resolve first with the local path. So you should *not* to include it here yourself. 27 | /// This is necessary since the local path changes during the recursion. 28 | /// 29 | public static void AddIncludesRecursively_ManualParsing(this IncludeGraph graph, string filename, string fileContent, IEnumerable includeDirectories, IEnumerable nonParseDirectories) 30 | { 31 | var graphItem = graph.CreateOrGetItem(filename, out bool isNewGraphItem); 32 | if (!isNewGraphItem) 33 | return; 34 | 35 | ParseIncludesRecursively(graph, graphItem, fileContent, includeDirectories, nonParseDirectories); 36 | } 37 | 38 | /// 39 | public static void AddIncludesRecursively_ManualParsing(this IncludeGraph graph, string filename, IEnumerable includeDirectories, IEnumerable nonParseDirectories) 40 | { 41 | var graphItem = graph.CreateOrGetItem(filename, out bool isNewGraphItem); 42 | if (!isNewGraphItem) 43 | return; 44 | 45 | ParseIncludesRecursively(graph, graphItem, File.ReadAllText(filename), includeDirectories, nonParseDirectories); 46 | } 47 | 48 | private static void ParseIncludesRecursively(IncludeGraph graph, IncludeGraph.GraphItem parentItem, string fileContent, IEnumerable includeDirectories, IEnumerable nonParseDirectories) 49 | { 50 | string currentDirectory = Path.GetDirectoryName(parentItem.AbsoluteFilename); 51 | var includeDirectoriesPlusLocal = includeDirectories.Prepend(currentDirectory); 52 | 53 | var includes = Formatter.IncludeLineInfo.ParseIncludes(fileContent, Formatter.ParseOptions.KeepOnlyValidIncludes); 54 | foreach (var includeLine in includes) 55 | { 56 | // Try to resolve the include (may fail) 57 | string resolvedInclude = includeLine.TryResolveInclude(includeDirectoriesPlusLocal, out bool successfullyResolved); 58 | // Create a link to the file in any case now even if resolving was unsuccessful. 59 | var includedFile = graph.CreateOrGetItem_AbsoluteNormalizedPath(resolvedInclude, out bool isNewGraphItem); 60 | 61 | if (successfullyResolved && isNewGraphItem && !nonParseDirectories.Any(x => resolvedInclude.StartsWith(x))) 62 | { 63 | bool successReadingFile = true; 64 | try 65 | { 66 | fileContent = File.ReadAllText(resolvedInclude); 67 | } 68 | catch 69 | { 70 | successReadingFile = false; 71 | Output.Instance.WriteLine("Unable to read included file: '{0}'", resolvedInclude); 72 | } 73 | if (successReadingFile) 74 | { 75 | ParseIncludesRecursively(graph, includedFile, fileContent, includeDirectories, nonParseDirectories); 76 | } 77 | } 78 | 79 | parentItem.Includes.Add(new IncludeGraph.Include { IncludeLine = includeLine, IncludedFile = includedFile }); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /IncludeToolbox/Graph/DGMLGraph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Xml; 5 | using System.Xml.Serialization; 6 | 7 | namespace IncludeToolbox.Graph 8 | { 9 | /// 10 | /// A simple DGML graph file writer. 11 | /// 12 | public class DGMLGraph 13 | { 14 | public class Node 15 | { 16 | // Standard DGML attributes. 17 | [XmlAttribute] 18 | public string Id; 19 | [XmlAttribute] 20 | public string Label; 21 | [XmlAttribute] 22 | public string Background; 23 | [XmlAttribute] 24 | public string Group 25 | { 26 | get 27 | { 28 | switch(GroupCollapse) 29 | { 30 | case GroupCollapseState.Expanded: 31 | return "Expanded"; 32 | case GroupCollapseState.Collapsed: 33 | return "Collapsed"; 34 | } 35 | return null; 36 | } 37 | set { throw new NotSupportedException(); } // setter required for xml serialization 38 | } 39 | 40 | public enum GroupCollapseState 41 | { 42 | None, 43 | Expanded, 44 | Collapsed 45 | } 46 | 47 | [XmlIgnore] 48 | public GroupCollapseState GroupCollapse = GroupCollapseState.None; 49 | 50 | // Extra attributes. 51 | [XmlAttribute] 52 | public int NumIncludes = 0; 53 | [XmlAttribute] 54 | public int NumUniqueTransitiveChildren = 0; 55 | 56 | 57 | public bool ShouldSerializeNumIncludes() 58 | { 59 | return NumIncludes != 0; 60 | } 61 | public bool ShouldSerializeNumUniqueTransitiveChildren() 62 | { 63 | return NumUniqueTransitiveChildren != 0; 64 | } 65 | } 66 | 67 | public class Link 68 | { 69 | public enum LinkType 70 | { 71 | Normal, 72 | GroupContains 73 | } 74 | 75 | [XmlAttribute] 76 | public string Source; 77 | [XmlAttribute] 78 | public string Target; 79 | [XmlAttribute] 80 | public string Label; 81 | [XmlAttribute] 82 | public string Category 83 | { 84 | get { return Type == LinkType.Normal ? null : "Contains"; } 85 | set { throw new NotSupportedException(); } // setter required for xml serialization 86 | } 87 | 88 | 89 | [XmlIgnore] 90 | public LinkType Type = LinkType.Normal; 91 | } 92 | 93 | public List Nodes { get; private set; } = new List(); 94 | public List Links { get; private set; } = new List(); 95 | 96 | public DGMLGraph() 97 | { 98 | } 99 | 100 | public struct Graph 101 | { 102 | public Node[] Nodes; 103 | public Link[] Links; 104 | } 105 | 106 | public void Serialize(string xmlpath) 107 | { 108 | Graph g = new Graph(); 109 | g.Nodes = Nodes.ToArray(); 110 | g.Links = Links.ToArray(); 111 | 112 | XmlRootAttribute root = new XmlRootAttribute("DirectedGraph"); 113 | root.Namespace = "http://schemas.microsoft.com/vs/2009/dgml"; 114 | XmlSerializer serializer = new XmlSerializer(typeof(Graph), root); 115 | XmlWriterSettings settings = new XmlWriterSettings(); 116 | settings.Indent = true; 117 | using (XmlWriter xmlWriter = XmlWriter.Create(xmlpath, settings)) 118 | { 119 | serializer.Serialize(xmlWriter, g); 120 | } 121 | } 122 | 123 | public void ColorizeByTransitiveChildCount(System.Drawing.Color noChildrenColor, System.Drawing.Color maxChildrenColor) 124 | { 125 | int maxChildCount = Nodes.Max(x => x.NumUniqueTransitiveChildren); 126 | if (maxChildCount == 0) 127 | maxChildCount = 1; // Avoid division by zero later on. 128 | 129 | foreach(var node in Nodes) 130 | { 131 | float intensity = (float)node.NumUniqueTransitiveChildren / maxChildCount; 132 | System.Drawing.Color color = System.Drawing.Color.FromArgb 133 | ( 134 | red: (int)(noChildrenColor.R + (maxChildrenColor.R - noChildrenColor.R) * intensity + 0.5f), 135 | green: (int)(noChildrenColor.G + (maxChildrenColor.G - noChildrenColor.G) * intensity + 0.5f), 136 | blue: (int)(noChildrenColor.B + (maxChildrenColor.B - noChildrenColor.B) * intensity + 0.5f) 137 | ); 138 | 139 | node.Background = System.Drawing.ColorTranslator.ToHtml(color); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /IncludeToolbox/Graph/IncludeGraph.cs: -------------------------------------------------------------------------------- 1 | using IncludeToolbox.GraphWindow; 2 | using Microsoft.VisualStudio.PlatformUI; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace IncludeToolbox.Graph 7 | { 8 | /// 9 | /// Graph of files including files. 10 | /// 11 | public class IncludeGraph 12 | { 13 | public struct Include 14 | { 15 | /// 16 | /// Absolute path to the file that includes another file. 17 | /// 18 | /// May be null, signaling that the include line could not be resolved. 19 | public GraphItem IncludedFile; 20 | 21 | /// 22 | /// The original include line in sourceFile. 23 | /// 24 | /// Depending on the graph generation algorithm, this may be null. 25 | public Formatter.IncludeLineInfo IncludeLine; 26 | } 27 | 28 | public class GraphItem 29 | { 30 | public GraphItem(string absoluteFilename) 31 | { 32 | AbsoluteFilename = absoluteFilename; 33 | FormattedName = absoluteFilename; 34 | Includes = new List(); 35 | } 36 | 37 | /// 38 | /// Absolute path to the file that includes the other files. 39 | /// 40 | /// 41 | /// If an absolute filename can't be provided (e.g. due to resolve failure), this can be any kind of unique file identifier. 42 | /// This is also used as key in the graph item dictionary. 43 | /// 44 | public string AbsoluteFilename { get; private set; } 45 | 46 | /// 47 | /// A formatted name that can be set from the outside. Is by default the same as AbsoluteFilename. 48 | /// 49 | public string FormattedName { get; set; } 50 | 51 | /// 52 | /// List of all includes of this file. 53 | /// 54 | public List Includes { get; private set; } 55 | } 56 | 57 | 58 | public IReadOnlyCollection GraphItems => graphItems.Values; 59 | 60 | /// 61 | /// Map of all files that the graph reaches. 62 | /// Use CreateOrAddItem to populate it. 63 | /// 64 | private Dictionary graphItems = new Dictionary(); 65 | 66 | /// 67 | /// Retrieves item from a given identifying absolute filename. 68 | /// 69 | /// 70 | /// Filename of an include, may be relative. Will be normalized internally. 71 | /// If an absolute filename can't be provided (e.g. due to resolve failure), this can be any kind of unique file identifier. 72 | /// 73 | public GraphItem CreateOrGetItem(string filename, out bool isNew) 74 | { 75 | filename = Utils.GetExactPathName(filename); 76 | return CreateOrGetItem_AbsoluteNormalizedPath(filename, out isNew); 77 | } 78 | 79 | public GraphItem CreateOrGetItem_AbsoluteNormalizedPath(string normalizedAbsoluteFilename, out bool isNew) 80 | { 81 | GraphItem outItem; 82 | isNew = !graphItems.TryGetValue(normalizedAbsoluteFilename, out outItem); 83 | if (isNew) 84 | { 85 | outItem = new GraphItem(normalizedAbsoluteFilename); 86 | graphItems.Add(normalizedAbsoluteFilename, outItem); 87 | } 88 | return outItem; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /IncludeToolbox/Graph/IncludeGraphToDGML.cs: -------------------------------------------------------------------------------- 1 | using IncludeToolbox.GraphWindow; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using static IncludeToolbox.Graph.IncludeGraph; 5 | using System; 6 | 7 | namespace IncludeToolbox.Graph 8 | { 9 | /// 10 | /// Extension methods to IncludeGraph for DGML interop. 11 | /// 12 | public static class IncludeGraphToDGML 13 | { 14 | static public DGMLGraph ToDGMLGraph(this IncludeGraph graph, bool folderGrouping, bool expandGroups) 15 | { 16 | var uniqueTransitiveChildrenMap = FindUniqueChildren(graph.GraphItems); 17 | 18 | DGMLGraph dgmlGraph = new DGMLGraph(); 19 | foreach (GraphItem node in graph.GraphItems) 20 | { 21 | dgmlGraph.Nodes.Add(new DGMLGraph.Node 22 | { 23 | Id = node.AbsoluteFilename, 24 | Label = node.FormattedName, 25 | Background = null, 26 | NumIncludes = node.Includes.Count, 27 | NumUniqueTransitiveChildren = uniqueTransitiveChildrenMap[node].Count, 28 | }); 29 | foreach (Include include in node.Includes) 30 | { 31 | dgmlGraph.Links.Add(new DGMLGraph.Link { Source = node.AbsoluteFilename, Target = include.IncludedFile?.AbsoluteFilename ?? null }); 32 | } 33 | } 34 | 35 | if (folderGrouping) 36 | { 37 | // Reusing a ViewModel datastructure is arguably a bit ugly, but it matches exactly what we want. 38 | // TODO: Consider splitting functionallity out. 39 | var folderGroupingRoot = new FolderIncludeTreeViewItem_Root(graph.GraphItems, null); 40 | foreach (var child in folderGroupingRoot.Children) 41 | AddFolderGroupingRecursive(dgmlGraph, child, null, expandGroups); 42 | } 43 | 44 | 45 | return dgmlGraph; 46 | } 47 | 48 | private static void AddFolderGroupingRecursive(DGMLGraph dgml, IncludeTreeViewItem child, FolderIncludeTreeViewItem_Folder parent, bool expandGroups) 49 | { 50 | if (parent != null) 51 | { 52 | dgml.Links.Add(new DGMLGraph.Link { Type = DGMLGraph.Link.LinkType.GroupContains, Source = parent.AbsoluteFilename, Target = child.AbsoluteFilename }); 53 | } 54 | 55 | if (child is FolderIncludeTreeViewItem_Folder folder) 56 | { 57 | dgml.Nodes.Add(new DGMLGraph.Node 58 | { 59 | Id = folder.AbsoluteFilename, 60 | Label = folder.Name, 61 | GroupCollapse = expandGroups ? DGMLGraph.Node.GroupCollapseState.Expanded : DGMLGraph.Node.GroupCollapseState.Collapsed 62 | }); 63 | foreach (var subChild in folder.Children) 64 | AddFolderGroupingRecursive(dgml, subChild, folder, expandGroups); 65 | } 66 | } 67 | 68 | /// 69 | /// Creates hashlist of all transitive children for all graph items. 70 | /// 71 | static private Dictionary> FindUniqueChildren(IReadOnlyCollection graphItems) 72 | { 73 | var uniqueChildrenLists = new Dictionary>(graphItems.Count); 74 | 75 | // We do not assume that there is a single root node. The graph might contain several independent cpp files. 76 | foreach (var node in graphItems) 77 | { 78 | FindUnqiueChildrenRec(node, uniqueChildrenLists); 79 | if (uniqueChildrenLists.Count == graphItems.Count) 80 | break; 81 | } 82 | 83 | return uniqueChildrenLists; 84 | } 85 | 86 | static private IEnumerable FindUnqiueChildrenRec(GraphItem node, Dictionary> uniqueChildrenMap) 87 | { 88 | if (node == null) 89 | return Enumerable.Empty(); 90 | 91 | HashSet uniqueChildren; 92 | if (!uniqueChildrenMap.TryGetValue(node, out uniqueChildren)) 93 | { 94 | uniqueChildren = new HashSet(); 95 | uniqueChildrenMap.Add(node, uniqueChildren); // Add immediately to avoid problems with graph circles. 96 | 97 | foreach (var include in node.Includes) 98 | { 99 | uniqueChildren.Add(include.IncludedFile); 100 | uniqueChildren.UnionWith(FindUnqiueChildrenRec(include.IncludedFile, uniqueChildrenMap)); 101 | } 102 | } 103 | return uniqueChildren; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/Commands/RefreshIncludeGraph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.Design; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace IncludeToolbox.GraphWindow.Commands 9 | { 10 | class RefreshIncludeGraph : IncludeToolbox.Commands.CommandBase 11 | { 12 | public override CommandID CommandID { get; } = new CommandID(IncludeToolbox.Commands.CommandSetGuids.GraphWindowToolbarCmdSet, 0x101); 13 | 14 | private IncludeGraphViewModel viewModel; 15 | 16 | public void SetViewModel(IncludeGraphViewModel viewModel) 17 | { 18 | this.viewModel = viewModel; 19 | viewModel.PropertyChanged += ViewModel_PropertyChanged; 20 | UpdateFromViewModel(); 21 | } 22 | 23 | protected override void SetupMenuCommand() 24 | { 25 | base.SetupMenuCommand(); 26 | menuCommand.BeforeQueryStatus += (a, b) => UpdateFromViewModel(); 27 | } 28 | 29 | private void UpdateFromViewModel() 30 | { 31 | if (viewModel == null) 32 | return; 33 | 34 | menuCommand.Text = viewModel.RefreshTooltip; 35 | menuCommand.Enabled = viewModel.CanRefresh; 36 | } 37 | 38 | private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 39 | { 40 | if(e.PropertyName == nameof(IncludeGraphViewModel.RefreshTooltip)) 41 | { 42 | menuCommand.Text = viewModel.RefreshTooltip; 43 | } 44 | else if(e.PropertyName == nameof(IncludeGraphViewModel.CanRefresh)) 45 | { 46 | menuCommand.Enabled = viewModel.CanRefresh; 47 | } 48 | } 49 | 50 | protected override async Task MenuItemCallback(object sender, EventArgs e) 51 | { 52 | if (viewModel == null) 53 | return; 54 | 55 | await viewModel.RefreshIncludeGraph(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/Commands/RefreshModeComboBox.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using System; 3 | using System.ComponentModel.Design; 4 | using System.Runtime.InteropServices; 5 | using Task = System.Threading.Tasks.Task; 6 | 7 | namespace IncludeToolbox.GraphWindow.Commands 8 | { 9 | class RefreshModeComboBox : IncludeToolbox.Commands.CommandBase 10 | { 11 | public override CommandID CommandID { get; } = new CommandID(IncludeToolbox.Commands.CommandSetGuids.GraphWindowToolbarCmdSet, 0x102); 12 | 13 | private IncludeGraphViewModel viewModel; 14 | 15 | public void SetViewModel(IncludeGraphViewModel viewModel) 16 | { 17 | this.viewModel = viewModel; 18 | } 19 | 20 | protected override Task MenuItemCallback(object sender, EventArgs e) 21 | { 22 | if ((null == e) || (e == EventArgs.Empty)) 23 | { 24 | // We should never get here; EventArgs are required. 25 | throw new ArgumentException("Event args are required!"); 26 | } 27 | 28 | OleMenuCmdEventArgs eventArgs = e as OleMenuCmdEventArgs; 29 | 30 | if (eventArgs != null) 31 | { 32 | object input = eventArgs.InValue; 33 | IntPtr vOut = eventArgs.OutValue; 34 | 35 | if (vOut != IntPtr.Zero && input != null) 36 | { 37 | throw new ArgumentException("Both in and out parameters are invalid!"); 38 | } 39 | if (vOut != IntPtr.Zero) 40 | { 41 | // when vOut is non-NULL, the IDE is requesting the current value for the combo 42 | Marshal.GetNativeVariantForObject(IncludeGraphViewModel.RefreshModeNames[(int)viewModel.ActiveRefreshMode], vOut); 43 | } 44 | 45 | else if (input != null) 46 | { 47 | int newChoice = -1; 48 | if (!int.TryParse(input.ToString(), out newChoice)) 49 | { 50 | // user typed a string argument in command window. 51 | for (int i = 0; i < IncludeGraphViewModel.RefreshModeNames.Length; i++) 52 | { 53 | if (string.Compare(IncludeGraphViewModel.RefreshModeNames[i], input.ToString(), StringComparison.CurrentCultureIgnoreCase) == 0) 54 | { 55 | newChoice = i; 56 | break; 57 | } 58 | } 59 | } 60 | 61 | // new value was selected or typed in 62 | if (newChoice != -1) 63 | { 64 | viewModel.ActiveRefreshMode = (IncludeGraphViewModel.RefreshMode)newChoice; 65 | } 66 | else 67 | { 68 | throw new ArgumentException("Unknown combo box index or string"); 69 | } 70 | } 71 | else 72 | { 73 | // We should never get here; EventArgs are required. 74 | throw new ArgumentException("Event args are required!"); 75 | } 76 | } 77 | else 78 | { 79 | // We should never get here; EventArgs are required. 80 | throw new ArgumentException("Event args are required!"); 81 | } 82 | 83 | return Task.CompletedTask; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/Commands/RefreshModeComboBoxOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using System; 3 | using System.ComponentModel.Design; 4 | using System.Runtime.InteropServices; 5 | using Task = System.Threading.Tasks.Task; 6 | 7 | namespace IncludeToolbox.GraphWindow.Commands 8 | { 9 | /// 10 | /// Options for the RefreshMode combo box. 11 | /// 12 | class RefreshModeComboBoxOptions : IncludeToolbox.Commands.CommandBase 13 | { 14 | public override CommandID CommandID { get; } = new CommandID(IncludeToolbox.Commands.CommandSetGuids.GraphWindowToolbarCmdSet, 0x103); 15 | 16 | protected override Task MenuItemCallback(object sender, EventArgs e) 17 | { 18 | if (e == EventArgs.Empty) 19 | { 20 | // We should never get here; EventArgs are required. 21 | throw new ArgumentException("Event args are required!"); 22 | } 23 | 24 | OleMenuCmdEventArgs eventArgs = e as OleMenuCmdEventArgs; 25 | 26 | if (eventArgs != null) 27 | { 28 | object inParam = eventArgs.InValue; 29 | IntPtr vOut = eventArgs.OutValue; 30 | 31 | if (inParam != null) 32 | { 33 | throw new ArgumentException("In parameter may not be specified"); 34 | } 35 | else if (vOut != IntPtr.Zero) 36 | { 37 | Marshal.GetNativeVariantForObject(IncludeGraphViewModel.RefreshModeNames, vOut); 38 | } 39 | else 40 | { 41 | throw new ArgumentException("Out parameter can not be NULL"); 42 | } 43 | } 44 | 45 | return Task.CompletedTask; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/Commands/SaveDGML.cs: -------------------------------------------------------------------------------- 1 | using IncludeToolbox.Graph; 2 | using Microsoft.VisualStudio.Shell; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel.Design; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Task = System.Threading.Tasks.Task; 10 | 11 | namespace IncludeToolbox.GraphWindow.Commands 12 | { 13 | class SaveDGML : IncludeToolbox.Commands.CommandBase 14 | { 15 | public override CommandID CommandID { get; } = new CommandID(IncludeToolbox.Commands.CommandSetGuids.GraphWindowToolbarCmdSet, 0x104); 16 | 17 | private IncludeGraphViewModel viewModel; 18 | 19 | public void SetViewModel(IncludeGraphViewModel viewModel) 20 | { 21 | this.viewModel = viewModel; 22 | viewModel.PropertyChanged += ViewModel_PropertyChanged; 23 | menuCommand.Enabled = viewModel.NumIncludes > 0; 24 | } 25 | 26 | private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 27 | { 28 | if(e.PropertyName == nameof(IncludeGraphViewModel.NumIncludes)) 29 | { 30 | menuCommand.Enabled = viewModel.NumIncludes > 0; 31 | } 32 | } 33 | 34 | protected override async Task MenuItemCallback(object sender, EventArgs e) 35 | { 36 | if (viewModel == null) 37 | return; 38 | 39 | if (viewModel.NumIncludes <= 0) 40 | { 41 | await Output.Instance.ErrorMsg("There is no include tree to save!"); 42 | return; 43 | } 44 | 45 | // Show save dialog. 46 | Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog(); 47 | dlg.FileName = ".dgml"; 48 | dlg.DefaultExt = ".dgml"; 49 | dlg.Filter = "Text documents (.dgml)|*.dgml"; 50 | bool? result = dlg.ShowDialog(); 51 | 52 | // Process save file dialog box results 53 | if (result ?? false) 54 | { 55 | var settings = (ViewerOptionsPage)IncludeToolboxPackage.Instance.GetDialogPage(typeof(ViewerOptionsPage)); 56 | DGMLGraph dgmlGraph; 57 | 58 | try 59 | { 60 | dgmlGraph = viewModel.Graph.ToDGMLGraph(settings.CreateGroupNodesForFolders, settings.ExpandFolderGroupNodes); 61 | if (settings.ColorCodeNumTransitiveIncludes) 62 | dgmlGraph.ColorizeByTransitiveChildCount(settings.NoChildrenColor, settings.MaxChildrenColor); 63 | } 64 | catch 65 | { 66 | await Output.Instance.ErrorMsg($"Failed to create dgml graph."); 67 | return; 68 | } 69 | 70 | try 71 | { 72 | dgmlGraph.Serialize(dlg.FileName); 73 | } 74 | catch 75 | { 76 | await Output.Instance.ErrorMsg($"Failed to safe dgml to {dlg.FileName}."); 77 | return; 78 | } 79 | 80 | if (await Output.Instance.YesNoMsg("Saved dgml successfully. Do you want to open it in Visual Studio?") == Output.MessageResult.Yes) 81 | { 82 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 83 | VSUtils.OpenFileAndShowDocument(dlg.FileName); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/PropertyChangedBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace IncludeToolbox.GraphWindow 5 | { 6 | public class PropertyChangedBase : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | protected void OnNotifyPropertyChanged([CallerMemberName]string propertyName = null) 11 | { 12 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/View/IncludeGraphControl.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/View/IncludeGraphControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Input; 4 | 5 | namespace IncludeToolbox.GraphWindow 6 | { 7 | public partial class IncludeGraphControl : UserControl 8 | { 9 | public IncludeGraphViewModel ViewModel { get; private set; } 10 | 11 | public IncludeGraphControl() 12 | { 13 | InitializeComponent(); 14 | ViewModel = (IncludeGraphViewModel)DataContext; 15 | } 16 | 17 | private async void OnIncludeTreeItemMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) 18 | { 19 | if (e.LeftButton == MouseButtonState.Pressed && e.ClickCount == 2) 20 | { 21 | if(sender is FrameworkElement frameworkElement) 22 | { 23 | if (frameworkElement.DataContext is IncludeTreeViewItem treeItem) // Arguably a bit hacky to go over the DataContext, but it seems to be a good direct route. 24 | { 25 | await treeItem.NavigateToInclude(); 26 | } 27 | } 28 | } 29 | } 30 | 31 | private async void OnIncludeTreeItemKeyDown(object sender, KeyEventArgs e) 32 | { 33 | if(e.Key == Key.Enter) 34 | { 35 | if (sender is TreeView treeView) 36 | { 37 | if (treeView.SelectedItem is IncludeTreeViewItem treeItem) 38 | { 39 | await treeItem.NavigateToInclude(); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/View/IncludeGraphToolWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Runtime.InteropServices; 5 | using EnvDTE; 6 | using Microsoft.VisualStudio.Shell; 7 | using System.IO; 8 | using System.Linq; 9 | using IncludeToolbox; 10 | using System.ComponentModel.Design; 11 | 12 | namespace IncludeToolbox.GraphWindow 13 | { 14 | /// 15 | /// This class implements the tool window exposed by this package and hosts a user control. 16 | /// 17 | /// 18 | /// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane, 19 | /// usually implemented by the package implementer. 20 | /// 21 | /// This class derives from the ToolWindowPane class provided from the MPF in order to use its 22 | /// implementation of the IVsUIElementPane interface. 23 | /// 24 | /// 25 | [Guid(IncludeGraphToolWindow.GUIDString)] 26 | public sealed class IncludeGraphToolWindow : ToolWindowPane 27 | { 28 | public const string GUIDString = "c87b586a-6c8b-4129-9b6d-56a761e0ac6d"; 29 | 30 | private readonly IncludeGraphControl graphToolWindowControl; 31 | private const int ToolbarID = 0x1000; 32 | 33 | //private new IncludeToolboxPackage Package 34 | //{ 35 | // get { return (IncludeToolboxPackage)base.Package; } 36 | //} 37 | 38 | private static bool commandsInitialized = false; 39 | 40 | /// 41 | /// Initializes a new instance of the class. 42 | /// 43 | public IncludeGraphToolWindow() : base() 44 | { 45 | this.Caption = "Include Graph"; 46 | 47 | // This is the user control hosted by the tool window; Note that, even if this class implements IDisposable, 48 | // we are not calling Dispose on this object. This is because ToolWindowPane calls Dispose on 49 | // the object returned by the Content property. 50 | graphToolWindowControl = new IncludeGraphControl(); 51 | Content = graphToolWindowControl; 52 | 53 | // Todo: Get rid of IncludeToolboxPackage.Instance singleton thing. Can't use base.Package here yet since it is not initialized yet. 54 | if (!commandsInitialized) 55 | { 56 | commandsInitialized = true; 57 | Commands.RefreshIncludeGraph.Initialize(IncludeToolboxPackage.Instance); 58 | Commands.RefreshIncludeGraph.Instance.SetViewModel(graphToolWindowControl.ViewModel); 59 | 60 | Commands.RefreshModeComboBox.Initialize(IncludeToolboxPackage.Instance); 61 | Commands.RefreshModeComboBox.Instance.SetViewModel(graphToolWindowControl.ViewModel); 62 | Commands.RefreshModeComboBoxOptions.Initialize(IncludeToolboxPackage.Instance); 63 | 64 | Commands.SaveDGML.Initialize(IncludeToolboxPackage.Instance); 65 | Commands.SaveDGML.Instance.SetViewModel(graphToolWindowControl.ViewModel); 66 | } 67 | 68 | ToolBar = new CommandID(IncludeToolbox.Commands.CommandSetGuids.GraphWindowToolbarCmdSet, ToolbarID); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/ViewModel/FolderIncludeTreeItem.cs: -------------------------------------------------------------------------------- 1 | using IncludeToolbox.Graph; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace IncludeToolbox.GraphWindow 10 | { 11 | public class FolderIncludeTreeViewItem_Root : IncludeTreeViewItem 12 | { 13 | IReadOnlyCollection graphItems; 14 | IncludeGraph.GraphItem includingFile; 15 | 16 | public override IReadOnlyList Children 17 | { 18 | get 19 | { 20 | if (cachedItems == null) 21 | GenerateChildItems(); 22 | return cachedItems; 23 | } 24 | } 25 | protected IReadOnlyList cachedItems; 26 | 27 | 28 | public FolderIncludeTreeViewItem_Root(IReadOnlyCollection graphItems, IncludeGraph.GraphItem includingFile) 29 | { 30 | this.graphItems = graphItems; 31 | this.includingFile = includingFile; 32 | this.Name = "Root"; 33 | this.AbsoluteFilename = "Root"; 34 | } 35 | 36 | public void Reset(IReadOnlyCollection graphItems, IncludeGraph.GraphItem includingFile) 37 | { 38 | this.graphItems = graphItems; 39 | this.includingFile = includingFile; 40 | this.cachedItems = null; 41 | NotifyAllPropertiesChanged(); 42 | } 43 | 44 | private void GenerateChildItems() 45 | { 46 | if (graphItems == null) 47 | { 48 | cachedItems = emptyList; 49 | return; 50 | } 51 | 52 | var rootChildren = new List(); 53 | cachedItems = rootChildren; 54 | 55 | // Create first layer of folder and leaf items 56 | var leafItems = new List(); 57 | foreach (IncludeGraph.GraphItem item in graphItems) 58 | { 59 | if (item == includingFile) 60 | continue; 61 | 62 | leafItems.Add(new FolderIncludeTreeViewItem_Leaf(item)); 63 | } 64 | 65 | // Group by folder. 66 | if (leafItems.Count > 0) 67 | { 68 | leafItems.Sort((x, y) => x.ParentFolder.CompareTo(y.ParentFolder)); 69 | 70 | var root = new FolderIncludeTreeViewItem_Folder("", 0); 71 | GroupIncludeRecursively(root, leafItems, 0, leafItems.Count, 0); 72 | rootChildren.AddRange(root.ChildrenList); 73 | } 74 | } 75 | 76 | private string GetNextPathPrefix(string path, int begin) 77 | { 78 | int nextSlash = begin; 79 | while (path.Length > nextSlash && path[nextSlash] != Path.DirectorySeparatorChar) 80 | ++nextSlash; 81 | return path.Substring(0, nextSlash); 82 | } 83 | 84 | private string LargestCommonFolderPrefixInRange(List allLeafItems, int begin, int end, string commonPrefix) 85 | { 86 | string folderBegin = allLeafItems[begin].ParentFolder; 87 | string folderEnd = allLeafItems[end-1].ParentFolder; 88 | 89 | string prefixCandidate = commonPrefix; 90 | string previousPrefix = null; 91 | do 92 | { 93 | if (folderBegin.Length == prefixCandidate.Length) 94 | return prefixCandidate; 95 | 96 | previousPrefix = prefixCandidate; 97 | prefixCandidate = GetNextPathPrefix(folderBegin, previousPrefix.Length + 1); 98 | 99 | } while (folderEnd.StartsWith(prefixCandidate)); 100 | 101 | return previousPrefix; 102 | } 103 | 104 | private void GroupIncludeRecursively(FolderIncludeTreeViewItem_Folder parentFolder, List allLeafItems, int begin, int end, int commonPrefixLength) 105 | { 106 | System.Diagnostics.Debug.Assert(begin < end); 107 | System.Diagnostics.Debug.Assert(allLeafItems.Count >= end); 108 | 109 | // Look through the sorted subsection of folders and find ranges where the prefix changes. 110 | while(begin < end) 111 | { 112 | // New subgroup to look at! 113 | string currentPrefix = GetNextPathPrefix(allLeafItems[begin].ParentFolder, commonPrefixLength + 1); 114 | 115 | // Find end of the rest of the group and expand recurively. 116 | for (int i = begin; i <= end; ++i) 117 | { 118 | if (i == end || !allLeafItems[i].ParentFolder.StartsWith(currentPrefix)) 119 | { 120 | // Find maximal prefix of this group. 121 | string largestPrefix = LargestCommonFolderPrefixInRange(allLeafItems, begin, i, currentPrefix); 122 | var newGroup = new FolderIncludeTreeViewItem_Folder(largestPrefix, commonPrefixLength); 123 | parentFolder.ChildrenList.Add(newGroup); 124 | 125 | // If there are any direct children, they will be first due to sorting. Add them to the new group and ignore this part of the range. 126 | while (allLeafItems[begin].ParentFolder.Length == largestPrefix.Length) 127 | { 128 | newGroup.ChildrenList.Add(allLeafItems[begin]); 129 | ++begin; 130 | if (begin == i) 131 | break; 132 | } 133 | 134 | // What's left is non-direct children (== folders!) that we need to handle recursively. 135 | int numFoldersInGroup = i - begin; 136 | if (numFoldersInGroup > 0) 137 | { 138 | GroupIncludeRecursively(newGroup, allLeafItems, begin, i, largestPrefix.Length); 139 | } 140 | 141 | // Next group starts at this element. 142 | begin = i; 143 | break; 144 | } 145 | } 146 | } 147 | } 148 | 149 | public override Task NavigateToInclude() 150 | { 151 | return Task.CompletedTask; 152 | } 153 | } 154 | 155 | public class FolderIncludeTreeViewItem_Folder : IncludeTreeViewItem 156 | { 157 | public override IReadOnlyList Children => ChildrenList; 158 | public List ChildrenList { get; private set; } = new List(); 159 | 160 | public const string UnresolvedFolderName = ""; 161 | 162 | public FolderIncludeTreeViewItem_Folder(string largestPrefix, int commonPrefixLength) 163 | { 164 | AbsoluteFilename = largestPrefix; 165 | 166 | largestPrefix.Substring(commonPrefixLength); 167 | 168 | if (largestPrefix != UnresolvedFolderName) 169 | { 170 | var stringBuilder = new StringBuilder(largestPrefix); 171 | stringBuilder.Remove(0, commonPrefixLength); 172 | stringBuilder.Append(Path.DirectorySeparatorChar); 173 | if (stringBuilder[0] == Path.DirectorySeparatorChar) 174 | stringBuilder.Remove(0, 1); 175 | 176 | Name = stringBuilder.ToString(); 177 | } 178 | else 179 | { 180 | Name = largestPrefix; 181 | } 182 | } 183 | 184 | public override Task NavigateToInclude() 185 | { 186 | // todo? 187 | return Task.CompletedTask; 188 | } 189 | } 190 | 191 | public class FolderIncludeTreeViewItem_Leaf : IncludeTreeViewItem 192 | { 193 | public override IReadOnlyList Children => emptyList; 194 | 195 | /// 196 | /// Parent folder of this leaf. Needed during build-up. 197 | /// 198 | public string ParentFolder { get; private set; } 199 | 200 | public FolderIncludeTreeViewItem_Leaf(IncludeGraph.GraphItem item) 201 | { 202 | try 203 | { 204 | Name = Path.GetFileName(item.AbsoluteFilename); 205 | } 206 | catch 207 | { 208 | Name = item?.FormattedName; 209 | } 210 | AbsoluteFilename = item?.AbsoluteFilename; 211 | 212 | if (string.IsNullOrWhiteSpace(AbsoluteFilename) || !Path.IsPathRooted(AbsoluteFilename)) 213 | ParentFolder = FolderIncludeTreeViewItem_Folder.UnresolvedFolderName; 214 | else 215 | ParentFolder = Path.GetDirectoryName(AbsoluteFilename); 216 | } 217 | 218 | public override async Task NavigateToInclude() 219 | { 220 | if (AbsoluteFilename != null && Path.IsPathRooted(AbsoluteFilename)) 221 | { 222 | await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 223 | var fileWindow = VSUtils.OpenFileAndShowDocument(AbsoluteFilename); 224 | } 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/ViewModel/HierarchyIncludeTreeViewItem.cs: -------------------------------------------------------------------------------- 1 | using IncludeToolbox.Graph; 2 | using Microsoft.VisualStudio.Shell; 3 | using Microsoft.VisualStudio.Text; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Threading.Tasks; 7 | using Task = System.Threading.Tasks.Task; 8 | 9 | namespace IncludeToolbox.GraphWindow 10 | { 11 | public class HierarchyIncludeTreeViewItem : IncludeTreeViewItem 12 | { 13 | private IncludeGraph.Include include; 14 | 15 | /// 16 | /// File that caused this inlude - the parent! 17 | /// 18 | private string includingFileAbsoluteFilename = null; 19 | 20 | public override IReadOnlyList Children 21 | { 22 | get 23 | { 24 | if (cachedItems == null) 25 | GenerateChildItems(); 26 | return cachedItems; 27 | } 28 | } 29 | protected IReadOnlyList cachedItems; 30 | 31 | public HierarchyIncludeTreeViewItem(IncludeGraph.Include include, string includingFileAbsoluteFilename) 32 | { 33 | Reset(include, includingFileAbsoluteFilename); 34 | } 35 | 36 | private void GenerateChildItems() 37 | { 38 | if (include.IncludedFile?.Includes != null) 39 | { 40 | var cachedItemsList = new List(); 41 | foreach (Graph.IncludeGraph.Include include in include.IncludedFile?.Includes) 42 | { 43 | cachedItemsList.Add(new HierarchyIncludeTreeViewItem(include, this.AbsoluteFilename)); 44 | } 45 | cachedItems = cachedItemsList; 46 | } 47 | else 48 | { 49 | cachedItems = emptyList; 50 | } 51 | } 52 | 53 | public void Reset(IncludeGraph.Include include, string includingFileAbsoluteFilename) 54 | { 55 | this.include = include; 56 | cachedItems = null; 57 | Name = include.IncludedFile?.FormattedName ?? ""; 58 | AbsoluteFilename = include.IncludedFile?.AbsoluteFilename; 59 | this.includingFileAbsoluteFilename = includingFileAbsoluteFilename; 60 | 61 | NotifyAllPropertiesChanged(); 62 | } 63 | 64 | public override async Task NavigateToInclude() 65 | { 66 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 67 | 68 | // Want to navigate to origin of this include, not target if possible 69 | if (includingFileAbsoluteFilename != null && Path.IsPathRooted(includingFileAbsoluteFilename)) 70 | { 71 | var fileWindow = VSUtils.OpenFileAndShowDocument(includingFileAbsoluteFilename); 72 | 73 | // Try to move to carret if possible. 74 | if (include.IncludeLine != null) 75 | { 76 | var textDocument = fileWindow.Document.Object() as EnvDTE.TextDocument; 77 | 78 | if (textDocument != null) 79 | { 80 | var includeLinePoint = textDocument.StartPoint.CreateEditPoint(); 81 | includeLinePoint.MoveToLineAndOffset(include.IncludeLine.LineNumber+1, 1); 82 | includeLinePoint.TryToShow(); 83 | 84 | textDocument.Selection.MoveToPoint(includeLinePoint); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/ViewModel/IncludeGraphViewModel.cs: -------------------------------------------------------------------------------- 1 | using IncludeToolbox.Formatter; 2 | using IncludeToolbox.Graph; 3 | using Microsoft.VisualStudio.PlatformUI; 4 | using Microsoft.VisualStudio.Shell; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.IO; 9 | using System.Linq; 10 | using Task = System.Threading.Tasks.Task; 11 | 12 | namespace IncludeToolbox.GraphWindow 13 | { 14 | public class IncludeGraphViewModel : PropertyChangedBase 15 | { 16 | public HierarchyIncludeTreeViewItem HierarchyIncludeTreeModel { get; set; } = new HierarchyIncludeTreeViewItem(new IncludeGraph.Include(), ""); 17 | public FolderIncludeTreeViewItem_Root FolderGroupedIncludeTreeModel { get; set; } = new FolderIncludeTreeViewItem_Root(null, null); 18 | 19 | public IncludeGraph Graph { get; private set; } 20 | 21 | 22 | public enum RefreshMode 23 | { 24 | DirectParsing, 25 | ShowIncludes, 26 | } 27 | 28 | public static readonly string[] RefreshModeNames = new string[] { "Direct Parsing", "Compile /showIncludes" }; 29 | 30 | public RefreshMode ActiveRefreshMode 31 | { 32 | get => activeRefreshMode; 33 | set 34 | { 35 | if (activeRefreshMode != value) 36 | { 37 | activeRefreshMode = value; 38 | OnNotifyPropertyChanged(); 39 | QueueUpdatingRefreshStatus(); 40 | } 41 | } 42 | } 43 | RefreshMode activeRefreshMode = RefreshMode.DirectParsing; 44 | 45 | public IEnumerable PossibleRefreshModes => Enum.GetValues(typeof(RefreshMode)).Cast(); 46 | 47 | public bool CanRefresh 48 | { 49 | get => canRefresh; 50 | private set { canRefresh = value; OnNotifyPropertyChanged(); } 51 | } 52 | private bool canRefresh = false; 53 | 54 | public string RefreshTooltip 55 | { 56 | get => refreshTooltip; 57 | set { refreshTooltip = value; OnNotifyPropertyChanged(); } 58 | } 59 | private string refreshTooltip = ""; 60 | 61 | public bool RefreshInProgress 62 | { 63 | get => refreshInProgress; 64 | private set 65 | { 66 | refreshInProgress = value; 67 | QueueUpdatingRefreshStatus(); 68 | OnNotifyPropertyChanged(); 69 | OnNotifyPropertyChanged(nameof(CanSave)); 70 | } 71 | } 72 | private bool refreshInProgress = false; 73 | 74 | public string GraphRootFilename 75 | { 76 | get => graphRootFilename; 77 | private set { graphRootFilename = value; OnNotifyPropertyChanged(); } 78 | } 79 | private string graphRootFilename = ""; 80 | 81 | public int NumIncludes 82 | { 83 | get => (Graph?.GraphItems.Count ?? 1) - 1; 84 | } 85 | 86 | public bool CanSave 87 | { 88 | get => !refreshInProgress && Graph != null && Graph.GraphItems.Count > 0; 89 | } 90 | 91 | // Need to keep these guys alive. 92 | private EnvDTE.WindowEvents windowEvents; 93 | 94 | public IncludeGraphViewModel() 95 | { 96 | ThreadHelper.ThrowIfNotOnUIThread(); 97 | 98 | // UI update on dte events. 99 | var dte = VSUtils.GetDTE(); 100 | if (dte != null) 101 | { 102 | windowEvents = dte.Events.WindowEvents; 103 | windowEvents.WindowActivated += (x, y) => QueueUpdatingRefreshStatus(); 104 | } 105 | 106 | QueueUpdatingRefreshStatus(); 107 | } 108 | 109 | private void QueueUpdatingRefreshStatus() 110 | { 111 | _ = Task.Run(async () => 112 | { 113 | var currentDocument = VSUtils.GetDTE()?.ActiveDocument; 114 | 115 | if (RefreshInProgress) 116 | { 117 | CanRefresh = false; 118 | RefreshTooltip = "Refresh in progress"; 119 | } 120 | // Limiting to C++ document is a bit harsh though for the general case as we might not have this information depending on the project type. 121 | // This is why we just check for "having a document" here for now. 122 | else if (currentDocument == null) 123 | { 124 | CanRefresh = false; 125 | RefreshTooltip = "No open document"; 126 | } 127 | else 128 | { 129 | if (activeRefreshMode == RefreshMode.ShowIncludes) 130 | { 131 | var canPerformShowIncludeCompilation = await CompilationBasedGraphParser.CanPerformShowIncludeCompilation(currentDocument); 132 | CanRefresh = canPerformShowIncludeCompilation.Result; 133 | RefreshTooltip = canPerformShowIncludeCompilation.Reason; 134 | } 135 | else 136 | { 137 | CanRefresh = true; 138 | RefreshTooltip = null; 139 | } 140 | } 141 | }); 142 | } 143 | 144 | public async Task RefreshIncludeGraph() 145 | { 146 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 147 | 148 | var currentDocument = VSUtils.GetDTE()?.ActiveDocument; 149 | GraphRootFilename = currentDocument.Name ?? ""; 150 | if (currentDocument == null) 151 | return; 152 | 153 | var newGraph = new IncludeGraph(); 154 | RefreshInProgress = true; 155 | 156 | try 157 | { 158 | switch (activeRefreshMode) 159 | { 160 | case RefreshMode.ShowIncludes: 161 | if (await newGraph.AddIncludesRecursively_ShowIncludesCompilation(currentDocument, OnNewTreeComputed)) 162 | { 163 | ResetIncludeTreeModel(null); 164 | } 165 | break; 166 | 167 | case RefreshMode.DirectParsing: 168 | ResetIncludeTreeModel(null); 169 | var settings = (ViewerOptionsPage)IncludeToolboxPackage.Instance.GetDialogPage(typeof(ViewerOptionsPage)); 170 | var includeDirectories = VSUtils.GetProjectIncludeDirectories(currentDocument.ProjectItem.ContainingProject); 171 | var uiThreadDispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher; 172 | await Task.Run( 173 | async () => 174 | { 175 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 176 | newGraph.AddIncludesRecursively_ManualParsing(currentDocument.FullName, includeDirectories, settings.NoParsePaths); 177 | await OnNewTreeComputed(newGraph, currentDocument, true); 178 | }); 179 | break; 180 | 181 | default: 182 | throw new NotImplementedException(); 183 | } 184 | } 185 | catch(Exception e) 186 | { 187 | Output.Instance.WriteLine("Unexpected error when refreshing Include Graph: {0}", e); 188 | await OnNewTreeComputed(newGraph, currentDocument, false); 189 | } 190 | } 191 | 192 | private void ResetIncludeTreeModel(IncludeGraph.GraphItem root) 193 | { 194 | HierarchyIncludeTreeModel.Reset(new IncludeGraph.Include() { IncludedFile = root }, ""); 195 | OnNotifyPropertyChanged(nameof(HierarchyIncludeTreeModel)); 196 | 197 | FolderGroupedIncludeTreeModel.Reset(Graph?.GraphItems, root); 198 | OnNotifyPropertyChanged(nameof(FolderGroupedIncludeTreeModel)); 199 | 200 | OnNotifyPropertyChanged(nameof(CanSave)); 201 | } 202 | 203 | /// 204 | /// Should be called after a tree was computed. Refreshes tree model. 205 | /// 206 | /// The include tree 207 | /// This can be different from the active document at the time the refresh button was clicked. 208 | /// Wheather the tree was created successfully 209 | private async Task OnNewTreeComputed(IncludeGraph graph, EnvDTE.Document documentTreeComputedFor, bool success) 210 | { 211 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 212 | 213 | RefreshInProgress = false; 214 | 215 | if (success) 216 | { 217 | this.Graph = graph; 218 | 219 | var includeDirectories = VSUtils.GetProjectIncludeDirectories(documentTreeComputedFor.ProjectItem.ContainingProject); 220 | includeDirectories.Insert(0, PathUtil.Normalize(documentTreeComputedFor.Path) + Path.DirectorySeparatorChar); 221 | 222 | foreach (var item in Graph.GraphItems) 223 | item.FormattedName = IncludeFormatter.FormatPath(item.AbsoluteFilename, FormatterOptionsPage.PathMode.Shortest_AvoidUpSteps, includeDirectories); 224 | 225 | ResetIncludeTreeModel(Graph.CreateOrGetItem(documentTreeComputedFor.FullName, out _)); 226 | } 227 | 228 | OnNotifyPropertyChanged(nameof(NumIncludes)); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /IncludeToolbox/GraphWindow/ViewModel/IncludeTreeViewItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace IncludeToolbox.GraphWindow 5 | { 6 | public abstract class IncludeTreeViewItem : PropertyChangedBase 7 | { 8 | public string Name { get; protected set; } 9 | 10 | public string AbsoluteFilename { get; protected set; } 11 | 12 | /// 13 | /// List of children, builds tree lazily. 14 | /// 15 | public abstract IReadOnlyList Children { get; } 16 | 17 | static protected IReadOnlyList emptyList = new IncludeTreeViewItem[0]; 18 | 19 | public IncludeTreeViewItem() 20 | { 21 | } 22 | 23 | protected void NotifyAllPropertiesChanged() 24 | { 25 | OnNotifyPropertyChanged(nameof(Name)); 26 | OnNotifyPropertyChanged(nameof(Children)); 27 | OnNotifyPropertyChanged(nameof(AbsoluteFilename)); 28 | } 29 | 30 | /// 31 | /// Navigates to to the file/include in the IDE. 32 | /// 33 | abstract public Task NavigateToInclude(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /IncludeToolbox/IncludeToolbox.args.json: -------------------------------------------------------------------------------- 1 | { 2 | "DataCollection": [ 3 | { 4 | "Id": "3856631e-87ac-4b3d-8d21-fccdd977a1ae", 5 | "Command": "/rootsuffix Exp" 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /IncludeToolbox/IncludeToolbox.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 16.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | Debug 11 | AnyCPU 12 | 2.0 13 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | {F9E250C6-A7AD-4888-8F17-6876736B8DCF} 15 | Library 16 | Properties 17 | IncludeToolbox 18 | IncludeToolbox 19 | v4.8 20 | true 21 | true 22 | true 23 | false 24 | false 25 | true 26 | true 27 | Program 28 | $(DevEnvDir)devenv.exe 29 | /rootsuffix Exp 30 | 31 | 32 | true 33 | full 34 | false 35 | bin\Debug\ 36 | DEBUG;TRACE 37 | prompt 38 | 4 39 | False 40 | 41 | 42 | VSTHRD200 43 | 44 | 45 | pdbonly 46 | true 47 | bin\Release\ 48 | TRACE 49 | prompt 50 | 4 51 | 52 | 53 | 54 | 55 | Component 56 | 57 | 58 | Component 59 | 60 | 61 | Component 62 | 63 | 64 | Component 65 | 66 | 67 | Component 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | IncludeGraphControl.xaml 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | Designer 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | compile; build; native; contentfiles; analyzers; buildtransitive 131 | 132 | 133 | 17.2.32505.173 134 | 135 | 136 | runtime; build; native; contentfiles; analyzers; buildtransitive 137 | all 138 | 139 | 140 | 141 | 142 | Always 143 | true 144 | 145 | 146 | 147 | 148 | 149 | true 150 | 151 | 152 | 153 | 154 | 155 | Menus.ctmenu 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | MSBuild:Compile 164 | Designer 165 | 166 | 167 | MSBuild:Compile 168 | Designer 169 | 170 | 171 | 172 | 173 | 174 | copy "$(TargetDir)IncludeToolbox.vsix" "$(SolutionDir)IncludeToolbox.vsix" 175 | 176 | 183 | -------------------------------------------------------------------------------- /IncludeToolbox/IncludeWhatYouUse/IWYUDownload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Http; 8 | using System.Text.RegularExpressions; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace IncludeToolbox.IncludeWhatYouUse 13 | { 14 | /// 15 | /// Functions for downloading and versioning of the iwyu installation. 16 | /// 17 | static public class IWYUDownload 18 | { 19 | public const string DisplayRepositorURL = @"https://github.com/Wumpf/iwyu_for_vs_includetoolbox"; 20 | private const string DownloadRepositorURL = @"https://github.com/Wumpf/iwyu_for_vs_includetoolbox/archive/master.zip"; 21 | private const string LatestCommitQuery = @"https://api.github.com/repos/Wumpf/iwyu_for_vs_includetoolbox/git/refs/heads/master"; 22 | 23 | private static async Task GetCurrentVersionOnline() 24 | { 25 | using (var httpClient = new HttpClient()) 26 | { 27 | // User agent is always required for github api. 28 | // https://developer.github.com/v3/#user-agent-required 29 | httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("IncludeToolbox"); 30 | 31 | string latestCommitResponse; 32 | try 33 | { 34 | latestCommitResponse = await httpClient.GetStringAsync(LatestCommitQuery); 35 | } 36 | catch (HttpRequestException e) 37 | { 38 | Output.Instance.WriteLine($"Failed to query IWYU version from {DownloadRepositorURL}: {e}"); 39 | return ""; 40 | } 41 | 42 | // Poor man's json parsing in lack of a json parser. 43 | var shaRegex = new Regex(@"\""sha\""\w*:\w*\""([a-z0-9]+)\"""); 44 | return shaRegex.Match(latestCommitResponse).Groups[1].Value; 45 | } 46 | } 47 | 48 | public static string GetVersionFilePath(string iwyuExectuablePath) 49 | { 50 | string directory = Path.GetDirectoryName(iwyuExectuablePath); 51 | return Path.Combine(directory, "version"); 52 | } 53 | 54 | private static string GetCurrentVersionHarddrive(string iwyuExectuablePath) 55 | { 56 | // Read current version. 57 | try 58 | { 59 | return File.ReadAllText(GetVersionFilePath(iwyuExectuablePath)); 60 | } 61 | catch 62 | { 63 | return ""; 64 | } 65 | } 66 | 67 | public static async Task IsNewerVersionAvailableOnline(string executablePath) 68 | { 69 | string currentVersion = GetCurrentVersionHarddrive(executablePath); 70 | string onlineVersion = await GetCurrentVersionOnline(); 71 | return currentVersion != onlineVersion; 72 | } 73 | 74 | /// 75 | /// Callback for download progress. 76 | /// 77 | /// General stage. 78 | /// Sub status, may be empty. 79 | /// Progress in percent for current section. -1 is there is none. 80 | public delegate void DownloadProgressUpdate(string section, string status, float percentage); 81 | 82 | /// 83 | /// Downloads iwyu from default download repository. 84 | /// 85 | /// 86 | /// Throws an exception if anything goes wrong (and there's a lot that can!) 87 | /// 88 | /// If cancellation token is used. 89 | static public async Task DownloadIWYU(string executablePath, DownloadProgressUpdate onProgressUpdate, CancellationToken cancellationToken) 90 | { 91 | string targetDirectory = Path.GetDirectoryName(executablePath); 92 | Directory.CreateDirectory(targetDirectory); 93 | string targetZipFile = Path.Combine(targetDirectory, "download.zip"); 94 | 95 | // Delete existing zip file. 96 | try 97 | { 98 | File.Delete(targetZipFile); 99 | } 100 | catch { } 101 | 102 | // Download. 103 | onProgressUpdate("Connecting...", "", -1.0f); 104 | 105 | // In contrast to GetCurrentVersionOnline we're not using HttpClient here since WebClient makes downloading files so much nicer. 106 | // (in HttpClient we would need to do the whole buffering + queuing and file writing ourselves) 107 | using (var client = new WebClient()) 108 | { 109 | var cancelRegistration = cancellationToken.Register(() => 110 | { 111 | client.CancelAsync(); 112 | throw new TaskCanceledException(); 113 | }); 114 | 115 | client.DownloadProgressChanged += (object sender, DownloadProgressChangedEventArgs e) => 116 | { 117 | int kbTodo = (int)System.Math.Ceiling((double)e.TotalBytesToReceive / 1024); 118 | int kbDownloaded = (int)System.Math.Ceiling((double)e.BytesReceived / 1024); 119 | onProgressUpdate("Downloading", kbTodo > 0 ? $"{kbTodo} / {kbDownloaded} kB" : $"{kbDownloaded} kB", e.ProgressPercentage * 0.01f); 120 | }; 121 | 122 | await client.DownloadFileTaskAsync(DownloadRepositorURL, targetZipFile); 123 | 124 | cancelRegistration.Dispose(); 125 | } 126 | 127 | // Unpacking. Looks like there is no async api, so we're just moving this to a task. 128 | onProgressUpdate("Unpacking...", "", -1.0f); 129 | await Task.Run(() => 130 | { 131 | using (var zipArchive = new ZipArchive(File.OpenRead(targetZipFile), ZipArchiveMode.Read)) 132 | { 133 | // Don't want to have the top level folder if any, 134 | string topLevelFolderName = ""; 135 | 136 | for (int i = 0; i < zipArchive.Entries.Count; ++i) 137 | { 138 | var file = zipArchive.Entries[i]; 139 | 140 | string targetName = file.FullName.Substring(topLevelFolderName.Length); 141 | string completeFileName = Path.Combine(targetDirectory, targetName); 142 | 143 | // If name is empty it should be a directory. 144 | if (file.Name == "") 145 | { 146 | if (i == 0) // We assume that if the first thing we encounter is a folder, it is a toplevel one. 147 | topLevelFolderName = file.FullName; 148 | else 149 | Directory.CreateDirectory(Path.GetDirectoryName(completeFileName)); 150 | } 151 | else 152 | { 153 | using (var destination = File.Open(completeFileName, FileMode.Create, FileAccess.Write, FileShare.None)) 154 | { 155 | using (var stream = file.Open()) 156 | stream.CopyTo(destination); 157 | } 158 | } 159 | 160 | if (cancellationToken.IsCancellationRequested) 161 | return; 162 | } 163 | } 164 | 165 | }, cancellationToken); 166 | 167 | // Save version. 168 | onProgressUpdate("Saving Version", "", -1.0f); 169 | string version = await GetCurrentVersionOnline(); 170 | File.WriteAllText(GetVersionFilePath(executablePath), version); 171 | } 172 | 173 | static public IEnumerable GetMappingFilesNextToIwyuPath(string executablePath) 174 | { 175 | string targetDirectory = Path.GetDirectoryName(executablePath); 176 | 177 | var impFiles = Directory.EnumerateFiles(targetDirectory). 178 | Where(file => Path.GetExtension(file).Equals(".imp", System.StringComparison.InvariantCultureIgnoreCase)); 179 | foreach (string dirs in Directory.EnumerateDirectories(targetDirectory)) 180 | { 181 | impFiles.Concat( 182 | Directory.EnumerateFiles(targetDirectory). 183 | Where(file => Path.GetExtension(file).Equals(".imp", System.StringComparison.InvariantCultureIgnoreCase)) 184 | ); 185 | } 186 | 187 | return impFiles; 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /IncludeToolbox/Options/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace IncludeToolbox.Options 8 | { 9 | public static class Constants 10 | { 11 | public const string Category = "Include Toolbox"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /IncludeToolbox/Options/FormatterOptionsPage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace IncludeToolbox 8 | { 9 | [Guid("B822F53B-32C0-4560-9A84-2F9DA7AB0E4C")] 10 | public class FormatterOptionsPage : OptionsPage 11 | { 12 | public const string SubCategory = "Include Formatter"; 13 | private const string collectionName = "IncludeFormatter"; 14 | 15 | #region Path 16 | 17 | public enum PathMode 18 | { 19 | Unchanged, 20 | Shortest, 21 | Shortest_AvoidUpSteps, 22 | Absolute, 23 | } 24 | [Category("Path")] 25 | [DisplayName("Mode")] 26 | [Description("Changes the path mode to the given pattern.")] 27 | public PathMode PathFormat { get; set; } = PathMode.Shortest_AvoidUpSteps; 28 | 29 | [Category("Path")] 30 | [DisplayName("Ignore File Relative")] 31 | [Description("If true, include directives will not take the path of the file into account.")] 32 | public bool IgnoreFileRelative { get; set; } = false; 33 | 34 | //[Category("Path")] 35 | //[DisplayName("Ignore Standard Library")] 36 | //[Description("")] 37 | //public bool IgnorePathForStdLib { get; set; } = true; 38 | 39 | #endregion 40 | 41 | #region Formatting 42 | 43 | public enum DelimiterMode 44 | { 45 | Unchanged, 46 | AngleBrackets, 47 | Quotes, 48 | } 49 | [Category("Formatting")] 50 | [DisplayName("Delimiter Mode")] 51 | [Description("Optionally changes all delimiters to either angle brackets <...> or quotes \"...\".")] 52 | public DelimiterMode DelimiterFormatting { get; set; } = DelimiterMode.Unchanged; 53 | 54 | public enum SlashMode 55 | { 56 | Unchanged, 57 | ForwardSlash, 58 | BackSlash, 59 | } 60 | [Category("Formatting")] 61 | [DisplayName("Slash Mode")] 62 | [Description("Changes all slashes to the given type.")] 63 | public SlashMode SlashFormatting { get; set; } = SlashMode.ForwardSlash; 64 | 65 | [Category("Formatting")] 66 | [DisplayName("Remove Empty Lines")] 67 | [Description("If true, all empty lines of a include selection will be removed.")] 68 | public bool RemoveEmptyLines { get; set; } = true; 69 | 70 | #endregion 71 | 72 | #region Sorting 73 | 74 | [Category("Sorting")] 75 | [DisplayName("Include delimiters in precedence regexes")] 76 | [Description("If true, precedence regexes will consider delimiters (angle brackets or quotes.)")] 77 | public bool RegexIncludeDelimiter { get; set; } = false; 78 | 79 | [Category("Sorting")] 80 | [DisplayName("Insert blank line between precedence regex match groups")] 81 | [Description("If true, a blank line will be inserted after each group matching one of the precedence regexes.")] 82 | public bool BlankAfterRegexGroupMatch { get; set; } = false; 83 | 84 | [Category("Sorting")] 85 | [DisplayName("Precedence Regexes")] 86 | [Description("Earlier match means higher sorting priority.\n\"" + RegexUtils.CurrentFileNameKey + "\" will be replaced with the current file name without extension.")] 87 | public string[] PrecedenceRegexes { 88 | get { return precedenceRegexes; } 89 | set { precedenceRegexes = value.Where(x => x.Length > 0).ToArray(); } // Remove empty lines. 90 | } 91 | private string[] precedenceRegexes = new string[] { $"(?i){RegexUtils.CurrentFileNameKey}\\.(h|hpp|hxx|inl|c|cpp|cxx)(?-i)" }; 92 | 93 | public enum TypeSorting 94 | { 95 | None, 96 | AngleBracketsFirst, 97 | QuotedFirst, 98 | } 99 | [Category("Sorting")] 100 | [DisplayName("Sort by Include Type")] 101 | [Description("Optionally put either includes with angle brackets <...> or quotes \"...\" first.")] 102 | public TypeSorting SortByType { get; set; } = TypeSorting.QuotedFirst; 103 | 104 | [Category("Sorting")] 105 | [DisplayName("Remove duplicates")] 106 | [Description("If true, duplicate includes will be removed.")] 107 | public bool RemoveDuplicates { get; set; } = true; 108 | 109 | #endregion 110 | 111 | public override void SaveSettingsToStorage() 112 | { 113 | ThreadHelper.ThrowIfNotOnUIThread(); 114 | var settingsStore = GetSettingsStore(); 115 | 116 | if (!settingsStore.CollectionExists(collectionName)) 117 | settingsStore.CreateCollection(collectionName); 118 | 119 | settingsStore.SetInt32(collectionName, nameof(PathFormat), (int)PathFormat); 120 | settingsStore.SetBoolean(collectionName, nameof(IgnoreFileRelative), IgnoreFileRelative); 121 | 122 | settingsStore.SetInt32(collectionName, nameof(DelimiterFormatting), (int)DelimiterFormatting); 123 | settingsStore.SetInt32(collectionName, nameof(SlashFormatting), (int)SlashFormatting); 124 | settingsStore.SetBoolean(collectionName, nameof(RemoveEmptyLines), RemoveEmptyLines); 125 | 126 | settingsStore.SetBoolean(collectionName, nameof(RegexIncludeDelimiter), RegexIncludeDelimiter); 127 | settingsStore.SetBoolean(collectionName, nameof(BlankAfterRegexGroupMatch), BlankAfterRegexGroupMatch); 128 | var value = string.Join("\n", PrecedenceRegexes); 129 | settingsStore.SetString(collectionName, nameof(PrecedenceRegexes), value); 130 | settingsStore.SetInt32(collectionName, nameof(SortByType), (int)SortByType); 131 | settingsStore.SetBoolean(collectionName, nameof(RemoveDuplicates), RemoveDuplicates); 132 | } 133 | 134 | public override void LoadSettingsFromStorage() 135 | { 136 | ThreadHelper.ThrowIfNotOnUIThread(); 137 | var settingsStore = GetSettingsStore(); 138 | 139 | if (settingsStore.PropertyExists(collectionName, nameof(PathFormat))) 140 | PathFormat = (PathMode)settingsStore.GetInt32(collectionName, nameof(PathFormat)); 141 | if (settingsStore.PropertyExists(collectionName, nameof(IgnoreFileRelative))) 142 | IgnoreFileRelative = settingsStore.GetBoolean(collectionName, nameof(IgnoreFileRelative)); 143 | 144 | if (settingsStore.PropertyExists(collectionName, nameof(DelimiterFormatting))) 145 | DelimiterFormatting = (DelimiterMode) settingsStore.GetInt32(collectionName, nameof(DelimiterFormatting)); 146 | if (settingsStore.PropertyExists(collectionName, nameof(SlashFormatting))) 147 | SlashFormatting = (SlashMode) settingsStore.GetInt32(collectionName, nameof(SlashFormatting)); 148 | if (settingsStore.PropertyExists(collectionName, nameof(RemoveEmptyLines))) 149 | RemoveEmptyLines = settingsStore.GetBoolean(collectionName, nameof(RemoveEmptyLines)); 150 | 151 | if (settingsStore.PropertyExists(collectionName, nameof(RegexIncludeDelimiter))) 152 | RegexIncludeDelimiter = settingsStore.GetBoolean(collectionName, nameof(RegexIncludeDelimiter)); 153 | if (settingsStore.PropertyExists(collectionName, nameof(BlankAfterRegexGroupMatch))) 154 | BlankAfterRegexGroupMatch = settingsStore.GetBoolean(collectionName, nameof(BlankAfterRegexGroupMatch)); 155 | if (settingsStore.PropertyExists(collectionName, nameof(PrecedenceRegexes))) 156 | { 157 | var value = settingsStore.GetString(collectionName, nameof(PrecedenceRegexes)); 158 | PrecedenceRegexes = value.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); 159 | } 160 | if (settingsStore.PropertyExists(collectionName, nameof(SortByType))) 161 | SortByType = (TypeSorting) settingsStore.GetInt32(collectionName, nameof(SortByType)); 162 | if (settingsStore.PropertyExists(collectionName, nameof(RemoveDuplicates))) 163 | RemoveDuplicates = settingsStore.GetBoolean(collectionName, nameof(RemoveDuplicates)); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /IncludeToolbox/Options/OptionsPage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Settings; 2 | using Microsoft.VisualStudio.Shell; 3 | using Microsoft.VisualStudio.Shell.Settings; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Runtime.InteropServices; 10 | 11 | namespace IncludeToolbox 12 | { 13 | /// 14 | /// Base class for all option pages. 15 | /// 16 | public abstract class OptionsPage : DialogPage 17 | { 18 | /// 19 | /// Initializes either with a in place created TaskContext for tests or, if our VS package is acutally active with the standard context. 20 | /// 21 | public OptionsPage() : base(IncludeToolboxPackage.Instance == null ? 22 | #pragma warning disable VSSDK005 // Avoid instantiating JoinableTaskContext 23 | new Microsoft.VisualStudio.Threading.JoinableTaskContext() : ThreadHelper.JoinableTaskContext) 24 | #pragma warning restore VSSDK005 // Avoid instantiating JoinableTaskContext 25 | { } 26 | 27 | // In theory the whole save/load mechanism should be done automatically. 28 | // But *something* is or was broken there. 29 | // see http://stackoverflow.com/questions/32751040/store-array-in-options-using-dialogpage 30 | 31 | static protected WritableSettingsStore GetSettingsStore() 32 | { 33 | ThreadHelper.ThrowIfNotOnUIThread(); 34 | var settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider); 35 | return settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings); 36 | } 37 | 38 | abstract public override void SaveSettingsToStorage(); 39 | abstract public override void LoadSettingsFromStorage(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /IncludeToolbox/Options/TrialAndErrorRemovalOptionsPage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace IncludeToolbox 7 | { 8 | [Guid("DBC8A65D-8B86-4296-9F1F-E785B182B550")] 9 | public class TrialAndErrorRemovalOptionsPage : OptionsPage 10 | { 11 | public const string SubCategory = "Trial and Error Include Removal"; 12 | private const string collectionName = "TryAndErrorRemoval"; // All "try and error" were updated to "trial and error", but need to keep old string here to preserve existing settings files. 13 | 14 | public enum IncludeRemovalOrder 15 | { 16 | BottomToTop, 17 | TopToBottom, 18 | } 19 | [Category(SubCategory)] 20 | [DisplayName("Removal Order")] 21 | [Description("Gives the order which #includes are removed.")] 22 | public IncludeRemovalOrder RemovalOrder { get; set; } = IncludeRemovalOrder.BottomToTop; 23 | 24 | [Category(SubCategory)] 25 | [DisplayName("Ignore First Include")] 26 | [Description("If true, the first include of a file will never be removed (useful for ignoring PCH).")] 27 | public bool IgnoreFirstInclude { get; set; } = true; 28 | 29 | [Category(SubCategory)] 30 | [DisplayName("Ignore List")] 31 | [Description("List of regexes. If the content of a #include directive match with any of these, it will be ignored." + 32 | "\n\"" + RegexUtils.CurrentFileNameKey + "\" will be replaced with the current file name without extension.")] 33 | public string[] IgnoreList { get; set; } = new string[] { $"(\\/|\\\\|^){RegexUtils.CurrentFileNameKey}\\.(h|hpp|hxx|inl|c|cpp|cxx)$", ".inl", "_inl.h" }; 34 | 35 | [Category(SubCategory)] 36 | [DisplayName("Keep Line Breaks")] 37 | [Description("If true, removed includes will leave an empty line.")] 38 | public bool KeepLineBreaks { get; set; } = false; 39 | 40 | 41 | public override void SaveSettingsToStorage() 42 | { 43 | ThreadHelper.ThrowIfNotOnUIThread(); 44 | var settingsStore = GetSettingsStore(); 45 | 46 | if (!settingsStore.CollectionExists(collectionName)) 47 | settingsStore.CreateCollection(collectionName); 48 | 49 | settingsStore.SetInt32(collectionName, nameof(RemovalOrder), (int)RemovalOrder); 50 | settingsStore.SetBoolean(collectionName, nameof(IgnoreFirstInclude), IgnoreFirstInclude); 51 | 52 | var value = string.Join("\n", IgnoreList); 53 | settingsStore.SetString(collectionName, nameof(IgnoreList), value); 54 | 55 | settingsStore.SetBoolean(collectionName, nameof(KeepLineBreaks), KeepLineBreaks); 56 | } 57 | 58 | public override void LoadSettingsFromStorage() 59 | { 60 | ThreadHelper.ThrowIfNotOnUIThread(); 61 | var settingsStore = GetSettingsStore(); 62 | 63 | if (settingsStore.PropertyExists(collectionName, nameof(RemovalOrder))) 64 | RemovalOrder = (IncludeRemovalOrder)settingsStore.GetInt32(collectionName, nameof(RemovalOrder)); 65 | if (settingsStore.PropertyExists(collectionName, nameof(IgnoreFirstInclude))) 66 | IgnoreFirstInclude = settingsStore.GetBoolean(collectionName, nameof(IgnoreFirstInclude)); 67 | 68 | if (settingsStore.PropertyExists(collectionName, nameof(IgnoreList))) 69 | { 70 | var value = settingsStore.GetString(collectionName, nameof(IgnoreList)); 71 | IgnoreList = value.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); 72 | } 73 | 74 | if (settingsStore.PropertyExists(collectionName, nameof(KeepLineBreaks))) 75 | KeepLineBreaks = settingsStore.GetBoolean(collectionName, nameof(KeepLineBreaks)); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /IncludeToolbox/Options/ViewerOptionsPage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace IncludeToolbox 7 | { 8 | [Guid("769AFCC2-25E2-459A-B2A3-89D7308800BD")] 9 | public class ViewerOptionsPage : OptionsPage 10 | { 11 | public const string SubCategory = "Include Viewer & Graph"; 12 | private const string collectionName = "IncludeViewer"; 13 | 14 | [Category("Include Graph Parsing")] 15 | [DisplayName("Graph Endpoint Directories")] 16 | [Description("List of absolute directory paths. For any include below these paths, the graph parsing will stop.")] 17 | public string[] NoParsePaths 18 | { 19 | get { return noParsePaths; } 20 | set 21 | { 22 | // It is critical that the paths are "exact" since we want to use them as with string comparison. 23 | noParsePaths = value; 24 | for (int i = 0; i < noParsePaths.Length; ++i) 25 | noParsePaths[i] = Utils.GetExactPathName(noParsePaths[i]); 26 | } 27 | } 28 | private string[] noParsePaths; 29 | 30 | [Category("Include Graph DGML")] 31 | [DisplayName("Create Group Nodes by Folders")] 32 | [Description("Creates folders like in the folder hierarchy view of Include Graph.")] 33 | public bool CreateGroupNodesForFolders { get; set; } = true; 34 | 35 | [Category("Include Graph DGML")] 36 | [DisplayName("Expand Folder Group Nodes")] 37 | [Description("If true all folder nodes start out expanded, otherwise they are collapsed.")] 38 | public bool ExpandFolderGroupNodes { get; set; } = false; 39 | 40 | [Category("Include Graph DGML")] 41 | [DisplayName("Colorize by Number of Includes")] 42 | [Description("If true each node gets color coded according to its number of unique transitive includes.")] 43 | public bool ColorCodeNumTransitiveIncludes { get; set; } = true; 44 | 45 | [Category("Include Graph DGML")] 46 | [DisplayName("No Children Color")] 47 | [Description("See \"Colorize by Number of Includes\". Color for no children at all.")] 48 | public System.Drawing.Color NoChildrenColor { get; set; } = System.Drawing.Color.White; 49 | 50 | [Category("Include Graph DGML")] 51 | [DisplayName("Max Children Color")] 52 | [Description("See \"Colorize by Number of Includes\". Color for highest number of children.")] 53 | public System.Drawing.Color MaxChildrenColor { get; set; } = System.Drawing.Color.Red; 54 | 55 | public override void SaveSettingsToStorage() 56 | { 57 | ThreadHelper.ThrowIfNotOnUIThread(); 58 | var settingsStore = GetSettingsStore(); 59 | 60 | if (!settingsStore.CollectionExists(collectionName)) 61 | settingsStore.CreateCollection(collectionName); 62 | 63 | var value = string.Join("\n", NoParsePaths); 64 | settingsStore.SetString(collectionName, nameof(NoParsePaths), value); 65 | 66 | 67 | settingsStore.SetBoolean(collectionName, nameof(CreateGroupNodesForFolders), CreateGroupNodesForFolders); 68 | settingsStore.SetBoolean(collectionName, nameof(ExpandFolderGroupNodes), ExpandFolderGroupNodes); 69 | settingsStore.SetBoolean(collectionName, nameof(ColorCodeNumTransitiveIncludes), ColorCodeNumTransitiveIncludes); 70 | settingsStore.SetInt32(collectionName, nameof(NoChildrenColor), NoChildrenColor.ToArgb()); 71 | settingsStore.SetInt32(collectionName, nameof(MaxChildrenColor), MaxChildrenColor.ToArgb()); 72 | } 73 | 74 | public override void LoadSettingsFromStorage() 75 | { 76 | ThreadHelper.ThrowIfNotOnUIThread(); 77 | var settingsStore = GetSettingsStore(); 78 | 79 | if (settingsStore.PropertyExists(collectionName, nameof(NoParsePaths))) 80 | { 81 | var value = settingsStore.GetString(collectionName, nameof(NoParsePaths)); 82 | NoParsePaths = value.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); 83 | } 84 | else 85 | { 86 | // It is surprisingly hard to get to the standard library paths. 87 | // Even finding the VS install path is not easy and - as it turns out - not necessarily where the standard library files reside. 88 | // So in lack of a better idea, we just put everything under the program files folder in here. 89 | string programFiles = Environment.ExpandEnvironmentVariables("%ProgramW6432%"); 90 | string programFilesX86 = Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"); 91 | if(programFiles == programFilesX86) // If somebody uses still x86 system. 92 | NoParsePaths = new string[] { programFiles }; 93 | else 94 | NoParsePaths = new string[] { programFiles, programFilesX86 }; 95 | } 96 | 97 | 98 | if (settingsStore.PropertyExists(collectionName, nameof(CreateGroupNodesForFolders))) 99 | CreateGroupNodesForFolders = settingsStore.GetBoolean(collectionName, nameof(CreateGroupNodesForFolders)); 100 | if (settingsStore.PropertyExists(collectionName, nameof(ExpandFolderGroupNodes))) 101 | ExpandFolderGroupNodes = settingsStore.GetBoolean(collectionName, nameof(ExpandFolderGroupNodes)); 102 | 103 | if (settingsStore.PropertyExists(collectionName, nameof(ColorCodeNumTransitiveIncludes))) 104 | ColorCodeNumTransitiveIncludes = settingsStore.GetBoolean(collectionName, nameof(ColorCodeNumTransitiveIncludes)); 105 | if (settingsStore.PropertyExists(collectionName, nameof(NoChildrenColor))) 106 | NoChildrenColor = System.Drawing.Color.FromArgb(settingsStore.GetInt32(collectionName, nameof(NoChildrenColor))); 107 | if (settingsStore.PropertyExists(collectionName, nameof(MaxChildrenColor))) 108 | MaxChildrenColor = System.Drawing.Color.FromArgb(settingsStore.GetInt32(collectionName, nameof(MaxChildrenColor))); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /IncludeToolbox/Output.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using EnvDTE80; 3 | using Microsoft.VisualStudio.Shell; 4 | using Microsoft.VisualStudio.Shell.Interop; 5 | using System.Threading.Tasks; 6 | using Task = System.Threading.Tasks.Task; 7 | 8 | namespace IncludeToolbox 9 | { 10 | public class Output 11 | { 12 | static public Output Instance { private set; get; } = new Output(); 13 | 14 | public enum MessageResult 15 | { 16 | Yes, 17 | No 18 | } 19 | 20 | private const int VsMessageBoxResult_Yes = 6; 21 | 22 | private Output() 23 | { 24 | } 25 | 26 | private OutputWindowPane outputWindowPane = null; 27 | 28 | private void Init() 29 | { 30 | ThreadHelper.ThrowIfNotOnUIThread(); 31 | 32 | DTE2 dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as DTE2; 33 | if (dte == null) 34 | return; 35 | 36 | OutputWindow outputWindow = dte.ToolWindows.OutputWindow; 37 | outputWindowPane = outputWindow.OutputWindowPanes.Add("IncludeToolbox"); 38 | } 39 | 40 | public void Clear() 41 | { 42 | ThreadHelper.ThrowIfNotOnUIThread(); 43 | 44 | if (outputWindowPane == null) 45 | Init(); 46 | outputWindowPane.Clear(); 47 | } 48 | 49 | public async Task WriteInternal(string text) 50 | { 51 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 52 | 53 | if (outputWindowPane == null) 54 | Init(); 55 | 56 | if (outputWindowPane != null) 57 | { 58 | System.Diagnostics.Debug.Assert(outputWindowPane != null); 59 | outputWindowPane.OutputString(text); 60 | } 61 | } 62 | 63 | public void Write(string text) 64 | { 65 | // Typically we don't care if the message was already written, so let's move this to a different (main-thread) task. 66 | _ = WriteInternal(text); 67 | } 68 | 69 | public void Write(string text, params object[] stringParams) 70 | { 71 | string output = string.Format(text, stringParams); 72 | Write(output); 73 | } 74 | 75 | public void WriteLine(string line) 76 | { 77 | Write(line + '\n'); 78 | } 79 | 80 | public void WriteLine(string line, params object[] stringParams) 81 | { 82 | string output = string.Format(line, stringParams); 83 | WriteLine(output); 84 | } 85 | 86 | public async Task ErrorMsg(string message, params object[] stringParams) 87 | { 88 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 89 | string output = string.Format(message, stringParams); 90 | WriteLine(output); 91 | VsShellUtilities.ShowMessageBox(ServiceProvider.GlobalProvider, output, "Include Toolbox", OLEMSGICON.OLEMSGICON_CRITICAL, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); 92 | } 93 | 94 | public async Task InfoMsg(string message, params object[] stringParams) 95 | { 96 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 97 | string output = string.Format(message, stringParams); 98 | WriteLine(output); 99 | VsShellUtilities.ShowMessageBox(ServiceProvider.GlobalProvider, output, "Include Toolbox", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); 100 | } 101 | 102 | public async Task YesNoMsg(string message) 103 | { 104 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 105 | int result = VsShellUtilities.ShowMessageBox(ServiceProvider.GlobalProvider, message, "Include Toolbox", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_YESNO, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); 106 | return result == VsMessageBoxResult_Yes ? MessageResult.Yes : MessageResult.No; 107 | } 108 | 109 | public void OutputToForeground() 110 | { 111 | ThreadHelper.ThrowIfNotOnUIThread(); 112 | outputWindowPane.Activate(); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /IncludeToolbox/Package/IncludeToolboxPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using Microsoft.VisualStudio.Shell; 6 | using Microsoft.VisualStudio.Shell.Interop; 7 | using Task = System.Threading.Tasks.Task; 8 | 9 | namespace IncludeToolbox 10 | { 11 | [ProvideBindingPath(SubPath = "")] // Necessary to find packaged assemblies. 12 | 13 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 14 | [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] 15 | [ProvideMenuResource("Menus.ctmenu", 1)] 16 | 17 | [ProvideOptionPage(typeof(FormatterOptionsPage), Options.Constants.Category, FormatterOptionsPage.SubCategory, 1000, 1001, true)] 18 | [ProvideOptionPage(typeof(IncludeWhatYouUseOptionsPage), Options.Constants.Category, IncludeWhatYouUseOptionsPage.SubCategory, 1000, 1002, true)] 19 | [ProvideOptionPage(typeof(TrialAndErrorRemovalOptionsPage), Options.Constants.Category, TrialAndErrorRemovalOptionsPage.SubCategory, 1000, 1003, true)] 20 | [ProvideOptionPage(typeof(ViewerOptionsPage), Options.Constants.Category, ViewerOptionsPage.SubCategory, 1000, 1004, true)] 21 | 22 | [ProvideToolWindow(typeof(GraphWindow.IncludeGraphToolWindow))] 23 | [Guid(IncludeToolboxPackage.PackageGuidString)] 24 | [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] 25 | [InstalledProductRegistration("#110", "#112", "0.2", IconResourceID = 400)] 26 | public sealed class IncludeToolboxPackage : AsyncPackage 27 | { 28 | /// 29 | /// IncludeToolboxPackage GUID string. 30 | /// 31 | public const string PackageGuidString = "5c2743c4-1b3f-4edd-b6a0-4379f867d47f"; 32 | 33 | static public Package Instance { get; private set; } 34 | 35 | public IncludeToolboxPackage() 36 | { 37 | // Inside this method you can place any initialization code that does not require 38 | // any Visual Studio service because at this point the package object is created but 39 | // not sited yet inside Visual Studio environment. The place to do all the other 40 | // initialization is the Initialize method. 41 | Instance = this; 42 | } 43 | 44 | #region Package Members 45 | 46 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 47 | { 48 | await base.InitializeAsync(cancellationToken, progress); 49 | 50 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 51 | Commands.IncludeGraphToolWindow.Initialize(this); 52 | Commands.FormatIncludes.Initialize(this); 53 | Commands.IncludeWhatYouUse.Initialize(this); 54 | Commands.TrialAndErrorRemoval_CodeWindow.Initialize(this); 55 | Commands.TrialAndErrorRemoval_Project.Initialize(this); 56 | } 57 | 58 | protected override void Dispose(bool disposing) 59 | { 60 | base.Dispose(disposing); 61 | } 62 | 63 | #endregion 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /IncludeToolbox/Package/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/IncludeToolbox/048d1471dcad27830e548894e9d0e88e2655c63b/IncludeToolbox/Package/Key.snk -------------------------------------------------------------------------------- /IncludeToolbox/Package/VSPackage.resx: -------------------------------------------------------------------------------- 1 |  2 | 12 | 13 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | text/microsoft-resx 120 | 121 | 122 | 2.0 123 | 124 | 125 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | 132 | IncludeToolbox Extension 133 | 134 | 135 | Tools around C/C++ #includes 136 | 137 | 138 | -------------------------------------------------------------------------------- /IncludeToolbox/Package/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IncludeToolbox 6 | Various tools for managing C/C++ #includes: Formatting, sorting, exploring, pruning. 7 | license.txt 8 | Resources\IncludeFormatterPackage.png 9 | Resources\IncludeFormatterPackage.png 10 | Include, Include What You Use, IWYU, Include Formatting, Include Sorting, #include, Include Removal 11 | 12 | 13 | 14 | 15 | amd64 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /IncludeToolbox/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("IncludeToolbox")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("IncludeToolbox")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /IncludeToolbox/RegexUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace IncludeToolbox 8 | { 9 | public static class RegexUtils 10 | { 11 | public const string CurrentFileNameKey = "$(currentFilename)"; 12 | 13 | /// 14 | /// Replaces special macros in a regex list. 15 | /// 16 | /// 17 | /// Name of the current document without extension. 18 | /// 19 | public static string[] FixupRegexes(string[] precedenceRegexes, string documentName) 20 | { 21 | string[] regexes = new string[precedenceRegexes.Length]; 22 | for (int i = 0; i < precedenceRegexes.Length; ++i) 23 | { 24 | regexes[i] = precedenceRegexes[i].Replace(CurrentFileNameKey, documentName); 25 | } 26 | return regexes; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /IncludeToolbox/Resources/IncludeFormatterIcons.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/IncludeToolbox/048d1471dcad27830e548894e9d0e88e2655c63b/IncludeToolbox/Resources/IncludeFormatterIcons.pdn -------------------------------------------------------------------------------- /IncludeToolbox/Resources/IncludeFormatterIcons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/IncludeToolbox/048d1471dcad27830e548894e9d0e88e2655c63b/IncludeToolbox/Resources/IncludeFormatterIcons.png -------------------------------------------------------------------------------- /IncludeToolbox/Resources/IncludeFormatterPackage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/IncludeToolbox/048d1471dcad27830e548894e9d0e88e2655c63b/IncludeToolbox/Resources/IncludeFormatterPackage.png -------------------------------------------------------------------------------- /IncludeToolbox/Resources/IncludeGraphToolbarIcons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/IncludeToolbox/048d1471dcad27830e548894e9d0e88e2655c63b/IncludeToolbox/Resources/IncludeGraphToolbarIcons.png -------------------------------------------------------------------------------- /IncludeToolbox/Resources/include13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/IncludeToolbox/048d1471dcad27830e548894e9d0e88e2655c63b/IncludeToolbox/Resources/include13.png -------------------------------------------------------------------------------- /IncludeToolbox/Resources/include16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wumpf/IncludeToolbox/048d1471dcad27830e548894e9d0e88e2655c63b/IncludeToolbox/Resources/include16.png -------------------------------------------------------------------------------- /IncludeToolbox/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.Editor; 6 | using Microsoft.VisualStudio.Shell; 7 | using Microsoft.VisualStudio.Text.Editor; 8 | using Microsoft.VisualStudio.TextManager.Interop; 9 | 10 | namespace IncludeToolbox 11 | { 12 | public struct BoolWithReason 13 | { 14 | public bool Result; 15 | public string Reason; 16 | } 17 | 18 | public static class Utils 19 | { 20 | public static string MakeRelative(string absoluteRoot, string absoluteTarget) 21 | { 22 | Uri rootUri, targetUri; 23 | 24 | try 25 | { 26 | rootUri = new Uri(absoluteRoot); 27 | targetUri = new Uri(absoluteTarget); 28 | } 29 | catch(UriFormatException) 30 | { 31 | return absoluteTarget; 32 | } 33 | 34 | if (rootUri.Scheme != targetUri.Scheme) 35 | return ""; 36 | 37 | Uri relativeUri = rootUri.MakeRelativeUri(targetUri); 38 | string relativePath = Uri.UnescapeDataString(relativeUri.ToString()); 39 | 40 | return relativePath; 41 | } 42 | 43 | public static string GetExactPathName(string pathName) 44 | { 45 | if (!File.Exists(pathName) && !Directory.Exists(pathName)) 46 | return pathName; 47 | 48 | var di = new DirectoryInfo(pathName); 49 | 50 | if (di.Parent != null) 51 | { 52 | return Path.Combine( 53 | GetExactPathName(di.Parent.FullName), 54 | di.Parent.GetFileSystemInfos(di.Name)[0].Name); 55 | } 56 | else 57 | { 58 | return di.Name.ToUpper(); 59 | } 60 | } 61 | 62 | /// 63 | /// Retrieves the dominant newline for a given piece of text. 64 | /// 65 | public static string GetDominantNewLineSeparator(string text) 66 | { 67 | string lineEndingToBeUsed = "\n"; 68 | 69 | // For simplicity we're just assuming that every \r has a \n 70 | int numLineEndingCLRF = text.Count(x => x == '\r'); 71 | int numLineEndingLF = text.Count(x => x == '\n') - numLineEndingCLRF; 72 | if (numLineEndingLF < numLineEndingCLRF) 73 | lineEndingToBeUsed = "\r\n"; 74 | 75 | return lineEndingToBeUsed; 76 | } 77 | 78 | /// 79 | /// Prepending a single Item to an to an IEnumerable. 80 | /// 81 | public static IEnumerable Prepend(this IEnumerable seq, T val) 82 | { 83 | yield return val; 84 | foreach (T t in seq) 85 | { 86 | yield return t; 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /IncludeToolbox/VCHelper.cs: -------------------------------------------------------------------------------- 1 | using EnvDTE; 2 | using Microsoft.VisualStudio.Shell; 3 | using Microsoft.VisualStudio.VCProjectEngine; 4 | using System.Threading.Tasks; 5 | using Task = System.Threading.Tasks.Task; 6 | 7 | namespace IncludeToolbox 8 | { 9 | public class VCQueryFailure : System.Exception 10 | { 11 | public VCQueryFailure(string message) : base(message) 12 | { 13 | } 14 | } 15 | 16 | public class VCHelper 17 | { 18 | public bool IsVCProject(Project project) 19 | { 20 | return project?.Object is VCProject; 21 | } 22 | 23 | private static async Task GetVCFileConfigForCompilation(Document document) 24 | { 25 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 26 | 27 | if (document == null) 28 | throw new VCQueryFailure("No document."); 29 | 30 | var vcProject = document.ProjectItem?.ContainingProject?.Object as VCProject; 31 | if (vcProject == null) 32 | throw new VCQueryFailure("The given document does not belong to a VC++ Project."); 33 | 34 | VCFile vcFile = document.ProjectItem?.Object as VCFile; 35 | if (vcFile == null) 36 | throw new VCQueryFailure("The given document is not a VC++ file."); 37 | 38 | if (vcFile.FileType != eFileType.eFileTypeCppCode) 39 | throw new VCQueryFailure("The given document is not a compileable VC++ file."); 40 | 41 | IVCCollection fileConfigCollection = vcFile.FileConfigurations as IVCCollection; 42 | VCFileConfiguration fileConfig = fileConfigCollection?.Item(vcProject.ActiveConfiguration.Name) as VCFileConfiguration; 43 | if (fileConfig == null) 44 | throw new VCQueryFailure("Failed to retrieve file config from document."); 45 | 46 | return fileConfig; 47 | } 48 | 49 | private static VCTool GetToolFromActiveConfiguration(Project project) where VCTool: class 50 | { 51 | ThreadHelper.ThrowIfNotOnUIThread(); 52 | 53 | VCProject vcProject = project?.Object as VCProject; 54 | if (vcProject == null) 55 | throw new VCQueryFailure($"Failed to retrieve VCCLCompilerTool since project \"{project.Name}\" is not a VCProject."); 56 | 57 | VCConfiguration activeConfiguration = vcProject.ActiveConfiguration; 58 | VCTool compilerTool = null; 59 | foreach (var tool in (IVCCollection)activeConfiguration.Tools) 60 | { 61 | compilerTool = tool as VCTool; 62 | if (compilerTool != null) 63 | break; 64 | } 65 | 66 | if (compilerTool == null) 67 | throw new VCQueryFailure($"Couldn't find a {typeof(VCTool).Name} in active configuration of VC++ Project \"{vcProject.Name}\""); 68 | 69 | return compilerTool; 70 | } 71 | 72 | public static VCLinkerTool GetLinkerTool(Project project) 73 | { 74 | VCProject vcProject = project?.Object as VCProject; 75 | if (vcProject == null) 76 | throw new VCQueryFailure("Failed to retrieve VCLinkerTool since project is not a VCProject."); 77 | 78 | VCConfiguration activeConfiguration = vcProject.ActiveConfiguration; 79 | var tools = activeConfiguration.Tools; 80 | VCLinkerTool linkerTool = null; 81 | foreach (var tool in (IVCCollection)activeConfiguration.Tools) 82 | { 83 | linkerTool = tool as VCLinkerTool; 84 | if (linkerTool != null) 85 | break; 86 | } 87 | 88 | if (linkerTool == null) 89 | throw new VCQueryFailure("Couldn't file a VCLinkerTool in VC++ Project."); 90 | 91 | return linkerTool; 92 | } 93 | 94 | public async Task IsCompilableFile(Document document) 95 | { 96 | try 97 | { 98 | await GetVCFileConfigForCompilation(document); 99 | } 100 | catch (VCQueryFailure queryFailure) 101 | { 102 | return new BoolWithReason() 103 | { 104 | Result = false, 105 | Reason = queryFailure.Message, 106 | }; 107 | } 108 | 109 | return new BoolWithReason() 110 | { 111 | Result = true, 112 | Reason = "", 113 | }; 114 | } 115 | 116 | public async Task CompileSingleFile(Document document) 117 | { 118 | var fileConfig = await GetVCFileConfigForCompilation(document); 119 | if (fileConfig != null) 120 | fileConfig.Compile(true, false); // WaitOnBuild==true always fails. 121 | } 122 | 123 | public string GetCompilerSetting_Includes(Project project) 124 | { 125 | VCQueryFailure queryFailure; 126 | try 127 | { 128 | VCCLCompilerTool compilerTool = GetToolFromActiveConfiguration(project); 129 | if (compilerTool != null) 130 | return compilerTool.FullIncludePath; 131 | else 132 | queryFailure = new VCQueryFailure("Unhandled error"); 133 | } 134 | catch (VCQueryFailure e) 135 | { 136 | queryFailure = e; 137 | } 138 | 139 | // If querying the NMake tool fails, keep old reason for failure, since this is what we usually expect. Using NMake is seen as mere fallback. 140 | try 141 | { 142 | VCNMakeTool nmakeTool = GetToolFromActiveConfiguration(project); 143 | if (nmakeTool == null) 144 | throw queryFailure; 145 | 146 | return nmakeTool.IncludeSearchPath; 147 | } 148 | catch 149 | { 150 | throw queryFailure; 151 | } 152 | } 153 | 154 | public void SetCompilerSetting_ShowIncludes(Project project, bool show) 155 | { 156 | GetToolFromActiveConfiguration(project).ShowIncludes = show; 157 | } 158 | 159 | public bool GetCompilerSetting_ShowIncludes(Project project) 160 | { 161 | return GetToolFromActiveConfiguration(project).ShowIncludes; 162 | } 163 | 164 | public string GetCompilerSetting_PreprocessorDefinitions(Project project) 165 | { 166 | VCQueryFailure queryFailure; 167 | try 168 | { 169 | VCCLCompilerTool compilerTool = GetToolFromActiveConfiguration(project); 170 | if (compilerTool != null) 171 | return compilerTool.PreprocessorDefinitions.Replace("\\\"$(INHERIT)\\\";", ""); 172 | else 173 | queryFailure = new VCQueryFailure("Unhandled error"); 174 | } 175 | catch (VCQueryFailure e) 176 | { 177 | queryFailure = e; 178 | } 179 | 180 | // If querying the NMake tool fails, keep old reason for failure, since this is what we usually expect. Using NMake is seen as mere fallback. 181 | try 182 | { 183 | VCNMakeTool nmakeTool = GetToolFromActiveConfiguration(project); 184 | if (nmakeTool == null) 185 | throw queryFailure; 186 | 187 | return nmakeTool.PreprocessorDefinitions; 188 | } 189 | catch 190 | { 191 | throw queryFailure; 192 | } 193 | } 194 | 195 | // https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.vcprojectengine.machinetypeoption.aspx 196 | public enum TargetMachineType 197 | { 198 | NotSet = 0, 199 | X86 = 1, 200 | AM33 = 2, 201 | ARM = 3, 202 | EBC = 4, 203 | IA64 = 5, 204 | M32R = 6, 205 | MIPS = 7, 206 | MIPS16 = 8, 207 | MIPSFPU = 9, 208 | MIPSFPU16 = 10, 209 | MIPSR41XX = 11, 210 | SH3 = 12, 211 | SH3DSP = 13, 212 | SH4 = 14, 213 | SH5 = 15, 214 | THUMB = 16, 215 | AMD64 = 17 216 | } 217 | 218 | public TargetMachineType GetLinkerSetting_TargetMachine(EnvDTE.Project project) 219 | { 220 | return (TargetMachineType)GetLinkerTool(project).TargetMachine; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /IncludeToolbox/VSUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Microsoft.VisualStudio.Editor; 6 | using Microsoft.VisualStudio.Shell; 7 | using Microsoft.VisualStudio.Text.Editor; 8 | using Microsoft.VisualStudio.TextManager.Interop; 9 | using EnvDTE; 10 | using Microsoft.VisualStudio; 11 | using Microsoft.VisualStudio.Shell.Interop; 12 | 13 | namespace IncludeToolbox 14 | { 15 | public static class VSUtils 16 | { 17 | public static EnvDTE80.DTE2 GetDTE() 18 | { 19 | var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2; 20 | if (dte == null) 21 | { 22 | throw new System.Exception("Failed to retrieve DTE2!"); 23 | } 24 | return dte; 25 | } 26 | 27 | // Historically, the GUIDs of the COM interfaces in VCProject/VCProjectEngine would change from version to version. 28 | // To work around this we had several builds of VCHelpers that we could choose from, each with a different dependency. 29 | // With VS2019, the older versions are no longer available and we're stuck with a single version for better or worse. 30 | 31 | public static VCHelper VCUtils = new VCHelper(); 32 | 33 | /// 34 | /// Returns what the C++ macro _MSC_VER should resolve to. 35 | /// 36 | /// 37 | public static string GetMSCVerString() 38 | { 39 | // See http://stackoverflow.com/questions/70013/how-to-detect-if-im-compiling-code-with-visual-studio-2008 40 | var dte = GetDTE(); 41 | var dteVersion = dte.Version; 42 | if (dte.Version.StartsWith("14.")) 43 | return "1900"; 44 | else if (dte.Version.StartsWith("15.")) 45 | return "1915"; 46 | else if (dte.Version.StartsWith("16.")) 47 | return "1920"; 48 | else 49 | throw new NotImplementedException("Unknown MSVC version!"); 50 | } 51 | 52 | /// 53 | /// Tries to retrieve include directories from a project. 54 | /// For each encountered path it will try to resolve the paths to absolute paths. 55 | /// 56 | /// Empty list if include directory retrieval failed. 57 | public static List GetProjectIncludeDirectories(EnvDTE.Project project, bool endWithSeparator = true) 58 | { 59 | List pathStrings = new List(); 60 | if (project == null) 61 | return pathStrings; 62 | 63 | string projectIncludeDirectories; 64 | try 65 | { 66 | projectIncludeDirectories = VCUtils.GetCompilerSetting_Includes(project); 67 | } 68 | catch (VCQueryFailure e) 69 | { 70 | Output.Instance.WriteLine(e.Message); 71 | return pathStrings; 72 | } 73 | 74 | ThreadHelper.ThrowIfNotOnUIThread(); 75 | string projectPath = Path.GetDirectoryName(Path.GetFullPath(project.FileName)); 76 | 77 | // According to documentation FullIncludePath has resolved macros. 78 | 79 | pathStrings.AddRange(projectIncludeDirectories.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries)); 80 | 81 | for (int i = pathStrings.Count - 1; i >= 0; --i) 82 | { 83 | try 84 | { 85 | pathStrings[i] = pathStrings[i].Trim(); 86 | if (!Path.IsPathRooted(pathStrings[i])) 87 | { 88 | pathStrings[i] = Path.Combine(projectPath, pathStrings[i]); 89 | } 90 | pathStrings[i] = Utils.GetExactPathName(Path.GetFullPath(pathStrings[i])); 91 | 92 | if (endWithSeparator) 93 | pathStrings[i] += Path.DirectorySeparatorChar; 94 | } 95 | catch 96 | { 97 | pathStrings.RemoveAt(i); 98 | } 99 | } 100 | return pathStrings; 101 | } 102 | 103 | public static IWpfTextViewHost GetCurrentTextViewHost() 104 | { 105 | IVsTextManager textManager = Package.GetGlobalService(typeof (SVsTextManager)) as IVsTextManager; 106 | 107 | IVsTextView textView = null; 108 | textManager.GetActiveView(1, null, out textView); 109 | 110 | var userData = textView as IVsUserData; 111 | if (userData == null) 112 | { 113 | return null; 114 | } 115 | else 116 | { 117 | Guid guidViewHost = DefGuidList.guidIWpfTextViewHost; 118 | object holder; 119 | userData.GetData(ref guidViewHost, out holder); 120 | var viewHost = (IWpfTextViewHost) holder; 121 | 122 | return viewHost; 123 | } 124 | } 125 | 126 | public static EnvDTE.Window OpenFileAndShowDocument(string filePath) 127 | { 128 | ThreadHelper.ThrowIfNotOnUIThread(); 129 | 130 | if (filePath == null) 131 | return null; 132 | 133 | var dte = VSUtils.GetDTE(); 134 | EnvDTE.Window fileWindow = dte.ItemOperations.OpenFile(filePath); 135 | if (fileWindow == null) 136 | { 137 | Output.Instance.WriteLine("Failed to open File {0}", filePath); 138 | return null; 139 | } 140 | fileWindow.Activate(); 141 | fileWindow.Visible = true; 142 | 143 | return fileWindow; 144 | } 145 | 146 | public static string GetOutputText() 147 | { 148 | ThreadHelper.ThrowIfNotOnUIThread(); 149 | var dte = GetDTE(); 150 | if (dte == null) 151 | return ""; 152 | 153 | 154 | OutputWindowPane buildOutputPane = null; 155 | foreach (OutputWindowPane pane in dte.ToolWindows.OutputWindow.OutputWindowPanes) 156 | { 157 | if (pane.Guid == VSConstants.OutputWindowPaneGuid.BuildOutputPane_string) 158 | { 159 | buildOutputPane = pane; 160 | break; 161 | } 162 | } 163 | if (buildOutputPane == null) 164 | { 165 | _ = Output.Instance.ErrorMsg("Failed to query for build output pane!"); 166 | return null; 167 | } 168 | TextSelection sel = buildOutputPane.TextDocument.Selection; 169 | 170 | sel.StartOfDocument(false); 171 | sel.EndOfDocument(true); 172 | 173 | return sel.Text; 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /IncludeToolbox/license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2019 Andreas Reich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Project inactive, looking for maintainer!** 2 | 3 | # IncludeToolbox 4 | Visual Studio extension for managing C/C++ includes: 5 | * **Format & Sort:** Format include commands to cleanup large lists of includes according to your preferences 6 | * **Purge:** Integrates [Include-What-You-Use](https://github.com/include-what-you-use/include-what-you-use) and comes with simpler but more reliable _Trial and Error Removal_ tool 7 | * **Explore:** _Include Graph_ tool window shows the tree of includes for a given file 8 | 9 | More information and download on the [Visual Studio Extension Gallery page](https://visualstudiogallery.msdn.microsoft.com/28c36d4f-425a-4bfe-9449-03f07b35f7b0) 10 | -------------------------------------------------------------------------------- /Tests/IncludeFormatingTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using IncludeToolbox.Formatter; 4 | 5 | namespace Tests 6 | { 7 | [TestClass] 8 | public class IncludeFormatingTest 9 | { 10 | private static string sourceCode_NoBlanks = 11 | @"#include ""a.h"" 12 | #include 13 | #include ""a.h"" 14 | #include ""filename.h"" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "; 21 | 22 | private static string sourceCode_WithBlanks = 23 | @"#include ""c_third"" 24 | 25 | #include ""filename.h"" 26 | 27 | #include ""z_first"" 28 | 29 | #include 30 | #include 31 | // A comment 32 | #include ""z_first"" 33 | 34 | #include 35 | #include ""filename.h"""; 36 | 37 | [TestMethod] 38 | public void Sorting_BlanksAfterRegexGroup() 39 | { 40 | // Blanks after groups. 41 | string expectedFormatedCode_NoBlanks = 42 | @"#include ""filename.h"" 43 | 44 | #include 45 | #include 46 | 47 | #include ""a.h"" 48 | #include 49 | #include 50 | 51 | 52 | 53 | "; 54 | 55 | string expectedFormatedCode_WithBlanks = 56 | @"#include ""filename.h"" 57 | 58 | #include 59 | 60 | #include ""c_third"" 61 | 62 | #include ""z_first"" 63 | 64 | // A comment 65 | 66 | 67 | 68 | "; 69 | 70 | 71 | var settings = new IncludeToolbox.FormatterOptionsPage(); 72 | settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.None; 73 | settings.PrecedenceRegexes = new string[] 74 | { 75 | IncludeToolbox.RegexUtils.CurrentFileNameKey, 76 | ".+_.+" 77 | }; 78 | settings.BlankAfterRegexGroupMatch = true; 79 | settings.RemoveEmptyLines = false; 80 | 81 | 82 | string formatedCode = IncludeFormatter.FormatIncludes(sourceCode_NoBlanks, "filename.cpp", new string[] { }, settings); 83 | Assert.AreEqual(expectedFormatedCode_NoBlanks, formatedCode); 84 | formatedCode = IncludeFormatter.FormatIncludes(sourceCode_WithBlanks, "filename.cpp", new string[] { }, settings); 85 | Assert.AreEqual(expectedFormatedCode_WithBlanks, formatedCode); 86 | } 87 | 88 | [TestMethod] 89 | public void Sorting_AngleBracketsFirst() 90 | { 91 | // With sort by type. 92 | string expectedFormatedCode_NoBlanks = 93 | @"#include 94 | #include 95 | #include 96 | #include 97 | #include ""filename.h"" 98 | #include ""a.h"" 99 | 100 | 101 | 102 | "; 103 | 104 | 105 | string expectedFormatedCode_WithBlanks = 106 | @"#include 107 | 108 | #include ""filename.h"" 109 | 110 | #include ""c_third"" 111 | 112 | #include ""z_first"" 113 | 114 | // A comment 115 | 116 | 117 | 118 | "; 119 | 120 | 121 | var settings = new IncludeToolbox.FormatterOptionsPage(); 122 | settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.AngleBracketsFirst; 123 | settings.PrecedenceRegexes = new string[] 124 | { 125 | IncludeToolbox.RegexUtils.CurrentFileNameKey, 126 | ".+_.+" 127 | }; 128 | settings.BlankAfterRegexGroupMatch = false; 129 | settings.RemoveEmptyLines = false; 130 | 131 | string formatedCode = IncludeFormatter.FormatIncludes(sourceCode_NoBlanks, "filename.cpp", new string[] { }, settings); 132 | Assert.AreEqual(expectedFormatedCode_NoBlanks, formatedCode); 133 | formatedCode = IncludeFormatter.FormatIncludes(sourceCode_WithBlanks, "filename.cpp", new string[] { }, settings); 134 | Assert.AreEqual(expectedFormatedCode_WithBlanks, formatedCode); 135 | } 136 | 137 | [TestMethod] 138 | public void Sorting_DontRemoveDuplicates() 139 | { 140 | // With sort by type. 141 | string expectedFormatedCode_NoBlanks = 142 | @"#include ""filename.h"" 143 | 144 | #include 145 | #include 146 | #include 147 | #include 148 | 149 | #include ""a.h"" 150 | #include ""a.h"" 151 | #include 152 | #include 153 | #include "; 154 | 155 | 156 | string expectedFormatedCode_WithBlanks = 157 | @"#include ""filename.h"" 158 | #include ""filename.h"" 159 | 160 | #include 161 | #include 162 | #include 163 | // A comment 164 | #include ""c_third"" 165 | #include ""z_first"" 166 | #include ""z_first"""; 167 | 168 | 169 | var settings = new IncludeToolbox.FormatterOptionsPage(); 170 | settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.None; 171 | settings.PrecedenceRegexes = new string[] 172 | { 173 | IncludeToolbox.RegexUtils.CurrentFileNameKey, 174 | ".+_.+" 175 | }; 176 | settings.BlankAfterRegexGroupMatch = true; 177 | settings.RemoveEmptyLines = true; 178 | settings.RemoveDuplicates = false; 179 | 180 | string formatedCode = IncludeFormatter.FormatIncludes(sourceCode_NoBlanks, "filename.cpp", new string[] { }, settings); 181 | Assert.AreEqual(expectedFormatedCode_NoBlanks, formatedCode); 182 | formatedCode = IncludeFormatter.FormatIncludes(sourceCode_WithBlanks, "filename.cpp", new string[] { }, settings); 183 | Assert.AreEqual(expectedFormatedCode_WithBlanks, formatedCode); 184 | } 185 | 186 | [TestMethod] 187 | public void RemoveEmptyLines() 188 | { 189 | string expectedFormatedCode_WithBlanks = 190 | @"#include ""filename.h"" 191 | #include 192 | #include ""c_third"" 193 | #include ""z_first"" 194 | // A comment"; 195 | 196 | var settings = new IncludeToolbox.FormatterOptionsPage(); 197 | settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.None; 198 | settings.PrecedenceRegexes = new string[] { IncludeToolbox.RegexUtils.CurrentFileNameKey }; 199 | settings.BlankAfterRegexGroupMatch = false; 200 | settings.RemoveEmptyLines = true; 201 | 202 | string formatedCode = IncludeFormatter.FormatIncludes(sourceCode_WithBlanks, "filename.cpp", new string[] { }, settings); 203 | Assert.AreEqual(expectedFormatedCode_WithBlanks, formatedCode); 204 | } 205 | 206 | [TestMethod] 207 | public void EmptySelection() 208 | { 209 | // Activate all features 210 | var settings = new IncludeToolbox.FormatterOptionsPage(); 211 | settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.AngleBracketsFirst; 212 | settings.PrecedenceRegexes = new string[] { IncludeToolbox.RegexUtils.CurrentFileNameKey }; 213 | settings.BlankAfterRegexGroupMatch = true; 214 | settings.RemoveEmptyLines = true; 215 | settings.DelimiterFormatting = IncludeToolbox.FormatterOptionsPage.DelimiterMode.AngleBrackets; 216 | settings.SlashFormatting = IncludeToolbox.FormatterOptionsPage.SlashMode.BackSlash; 217 | 218 | string formatedCode = IncludeFormatter.FormatIncludes("", "filename.cpp", new string[] { }, settings); 219 | Assert.AreEqual("", formatedCode); 220 | } 221 | 222 | [TestMethod] 223 | public void OtherPreprocessorDirectives() 224 | { 225 | string source = 226 | @"#pragma once 227 | // SomeComment 228 | #include ""z"" 229 | #include 230 | 231 | #include ""filename.h"" 232 | 233 | #if test 234 | #include 235 | // A comment 236 | #include ""a9"" 237 | #include 238 | #include 239 | #else 240 | #include 241 | 242 | #include // comment 243 | //#endif 244 | 245 | #include 246 | #endif 247 | #include 248 | #include "; 249 | 250 | string expectedFormatedCode = 251 | @"#pragma once 252 | // SomeComment 253 | #include 254 | #include ""filename.h"" 255 | 256 | #include ""z"" 257 | 258 | #if test 259 | #include 260 | // A comment 261 | #include 262 | #include ""a9"" 263 | 264 | #else 265 | #include 266 | 267 | #include // comment 268 | //#endif 269 | 270 | #include 271 | #endif 272 | #include 273 | #include "; 274 | 275 | var settings = new IncludeToolbox.FormatterOptionsPage(); 276 | settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.AngleBracketsFirst; 277 | settings.PrecedenceRegexes = new string[] { IncludeToolbox.RegexUtils.CurrentFileNameKey }; 278 | settings.BlankAfterRegexGroupMatch = false; 279 | settings.RemoveEmptyLines = false; 280 | 281 | string formatedCode = IncludeFormatter.FormatIncludes(source, "filename.cpp", new string[] { }, settings); 282 | Assert.AreEqual(expectedFormatedCode, formatedCode); 283 | } 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /Tests/IncludeWhatYouUseTests.cs: -------------------------------------------------------------------------------- 1 | using IncludeToolbox.IncludeWhatYouUse; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System; 4 | using System.Linq; 5 | using System.IO; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Tests 10 | { 11 | [TestClass] 12 | public class IncludeWhatYouUseTests 13 | { 14 | private void ReportProgress(string section, string status, float percentage) 15 | { 16 | System.Diagnostics.Debug.WriteLine($"{section} - {status} - {percentage}"); 17 | } 18 | 19 | private string GetCleanExecutableDir() 20 | { 21 | var executableDir = Path.Combine(Environment.CurrentDirectory, "testdata", "iwyu", "include-what-you-use.exe"); 22 | try 23 | { 24 | Directory.Delete(Path.GetDirectoryName(executableDir), true); 25 | } 26 | catch { } 27 | return executableDir; 28 | } 29 | 30 | /// 31 | /// Tests the automatic include what you use download and updating function. 32 | /// 33 | /// 34 | /// This indirectly also tests whether our iwyu repository is healthy! 35 | /// 36 | [TestMethod] 37 | public async Task DownloadAsync() 38 | { 39 | var executableDir = GetCleanExecutableDir(); 40 | 41 | Assert.AreEqual(false, File.Exists(executableDir)); 42 | Assert.AreEqual(true, await IWYUDownload.IsNewerVersionAvailableOnline(executableDir)); // Nothing here practically means that there is a new version. 43 | 44 | await IWYUDownload.DownloadIWYU(executableDir, ReportProgress, new CancellationToken()); 45 | 46 | Assert.AreEqual(false, await IWYUDownload.IsNewerVersionAvailableOnline(executableDir)); 47 | Assert.AreEqual(true, File.Exists(executableDir)); 48 | } 49 | 50 | [TestMethod] 51 | public void AddMappingFilesFromDownloadDir() 52 | { 53 | var executableDir = GetCleanExecutableDir(); 54 | var folder = Path.GetDirectoryName(executableDir); 55 | Directory.CreateDirectory(folder); 56 | 57 | string test0Path = Path.Combine(folder, "test0.imp"); 58 | File.Create(test0Path); 59 | string test1Path = Path.Combine(folder, "test1.imp"); 60 | File.Create(test1Path); 61 | File.Create(Path.Combine(folder, "test2.mip")); 62 | 63 | var optionPage = new IncludeToolbox.IncludeWhatYouUseOptionsPage(); 64 | optionPage.MappingFiles = new string[] { "doesn't exist.imp", test1Path }; 65 | 66 | var newMappingFiles = IWYUDownload.GetMappingFilesNextToIwyuPath(executableDir); 67 | 68 | { 69 | var newMappingFilesArray = newMappingFiles.ToArray(); 70 | Assert.AreEqual(2, newMappingFilesArray.Length); 71 | Assert.AreEqual(test0Path, newMappingFilesArray[0]); 72 | Assert.AreEqual(test1Path, newMappingFilesArray[1]); 73 | } 74 | 75 | optionPage.AddMappingFiles(newMappingFiles); 76 | { 77 | Assert.AreEqual(3, optionPage.MappingFiles.Length); 78 | Assert.AreEqual(true, optionPage.MappingFiles.Contains(test0Path)); 79 | Assert.AreEqual(true, optionPage.MappingFiles.Contains(test1Path)); 80 | Assert.AreEqual(true, optionPage.MappingFiles.Contains("doesn't exist.imp")); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Tests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Tests")] 10 | [assembly: AssemblyCopyright("Copyright © 2017")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("b31c729a-9cf7-4dde-8c93-6a671d21807a")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F577F5D2-5E3C-43BE-9030-AF2609A0917A} 8 | Library 9 | Properties 10 | Tests 11 | Tests 12 | v4.8 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 15.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\Debug\ 29 | DEBUG;TRACE 30 | prompt 31 | 4 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 16.10.31321.278 56 | 57 | 58 | 17.3.2088 59 | runtime; build; native; contentfiles; analyzers; buildtransitive 60 | all 61 | 62 | 63 | 1.3.2 64 | 65 | 66 | 1.3.2 67 | 68 | 69 | 70 | 71 | PreserveNewest 72 | 73 | 74 | PreserveNewest 75 | 76 | 77 | PreserveNewest 78 | 79 | 80 | PreserveNewest 81 | 82 | 83 | PreserveNewest 84 | 85 | 86 | 87 | 88 | PreserveNewest 89 | 90 | 91 | PreserveNewest 92 | 93 | 94 | PreserveNewest 95 | 96 | 97 | PreserveNewest 98 | 99 | 100 | PreserveNewest 101 | 102 | 103 | PreserveNewest 104 | 105 | 106 | 107 | 108 | {f9e250c6-a7ad-4888-8f17-6876736b8dcf} 109 | IncludeToolbox 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /Tests/testdata/includegraph.dgml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/testdata/includegraph_grouped.dgml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Tests/testdata/includegraph_withcolors.dgml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/testdata/includegraph_withcolors_grouped.dgml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Tests/testdata/simplegraph.dgml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Tests/testdata/source0.cpp: -------------------------------------------------------------------------------- 1 | #include "subdir/testinclude.h" 2 | /* 3 | // commented includes shouldn't throw off the system of course 4 | #include "testinclude.h" 5 | */ 6 | #include // Same again! Weird, but happens. -------------------------------------------------------------------------------- /Tests/testdata/source1.cpp: -------------------------------------------------------------------------------- 1 | #include "testinclude.h" -------------------------------------------------------------------------------- /Tests/testdata/subdir/inline.inl: -------------------------------------------------------------------------------- 1 | #include "broken!" -------------------------------------------------------------------------------- /Tests/testdata/subdir/subdir/subsub.h: -------------------------------------------------------------------------------- 1 | #include "../inline.inl" -------------------------------------------------------------------------------- /Tests/testdata/subdir/testinclude.h: -------------------------------------------------------------------------------- 1 | #include "inline.inl" 2 | #include "subdir/subsub.h" -------------------------------------------------------------------------------- /Tests/testdata/testinclude.h: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2019 Andreas Reich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | --------------------------------------------------------------------------------