diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..87d244f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,106 @@ +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: "Build" + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +jobs: + build: + outputs: + version: ${{ steps.vsix_version.outputs.version-number }} + name: Build + runs-on: windows-2022 + env: + Configuration: Release + DeployExtension: False + Vsix2022ManifestPath: IncludeToolbox2022\source.extension.vsixmanifest + Vsix2019ManifestPath: IncludeToolbox2019\source.extension.vsixmanifest + VsixManifestSourcePath: IncludeToolbox2022\source.extension.cs + + steps: + - uses: actions/checkout@v2 + + - name: Setup .NET build dependencies + uses: timheuer/bootstrap-dotnet@v1 + with: + nuget: 'false' + sdk: 'false' + msbuild: 'true' + + - name: Increment VSIX version + id: vsix_version + uses: timheuer/vsix-version-stamp@v1 + with: + manifest-file: ${{ env.Vsix2022ManifestPath }} + vsix-token-source-file: ${{ env.VsixManifestSourcePath }} + + - name: Sync 2019 version + uses: cezarypiatek/VsixVersionAction@1.0 + with: + version: ${{ steps.vsix_version.outputs.version-number }} + vsix-manifest-file: ${{ env.Vsix2019ManifestPath }} + + - name: Build + run: msbuild /v:m -restore /p:OutDir=\_built + + - name: Setup test + uses: darenm/Setup-VSTest@v1 + + - name: Test + run: vstest.console.exe \_built\*Tests.dll + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: ${{ github.event.repository.name }}.vsix + path: /_built/**/*.vsix + + publish: + if: ${{ (github.event_name == 'push' && contains(github.event.head_commit.message, '[release]')) || github.event_name == 'workflow_dispatch' }} + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Download Package artifact + uses: actions/download-artifact@v2 + with: + name: ${{ github.event.repository.name }}.vsix + + - name: Upload 2022 to Open VSIX + uses: timheuer/openvsixpublish@v1 + with: + vsix-file: ${{ github.event.repository.name }}2022.vsix + + - name: Upload 2019 to Open VSIX + uses: timheuer/openvsixpublish@v1 + with: + vsix-file: ${{ github.event.repository.name }}2019.vsix + + - name: Tag and Release + id: tag_release + uses: softprops/action-gh-release@v1 + with: + body: Release ${{ needs.build.outputs.version }} + tag_name: ${{ needs.build.outputs.version }} + files: | + **/*.vsix + + - name: Publish 2022 extension to Marketplace + uses: cezarypiatek/VsixPublisherAction@0.2 + with: + extension-file: '${{ github.event.repository.name }}2022.vsix' + publish-manifest-file: 'vs-publish2022.json' + personal-access-code: ${{ secrets.VS_PUBLISHER_ACCESS_TOKEN }} + + - name: Publish 2019 extension to Marketplace + uses: cezarypiatek/VsixPublisherAction@0.2 + with: + extension-file: '${{ github.event.repository.name }}2019.vsix' + publish-manifest-file: 'vs-publish2019.json' + personal-access-code: ${{ secrets.VS_PUBLISHER_ACCESS_TOKEN }} diff --git a/IncludeToolBox.lutconfig b/IncludeToolBox.lutconfig new file mode 100644 index 0000000..596a860 --- /dev/null +++ b/IncludeToolBox.lutconfig @@ -0,0 +1,6 @@ + + + true + true + 180000 + \ No newline at end of file diff --git a/IncludeToolBox.sln b/IncludeToolBox.sln index ac10aa2..0cbecbf 100644 --- a/IncludeToolBox.sln +++ b/IncludeToolBox.sln @@ -1,26 +1,67 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.156 +VisualStudioVersion = 16.0.32630.194 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IncludeToolbox", "IncludeToolbox\IncludeToolbox.csproj", "{F9E250C6-A7AD-4888-8F17-6876736B8DCF}" +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "IncludeToolboxShared", "IncludeToolboxShared\IncludeToolboxShared.shproj", "{C50C4863-6200-4E51-837A-31FEBC09C8B2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{F577F5D2-5E3C-43BE-9030-AF2609A0917A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IncludeToolbox2022", "IncludeToolbox2022\IncludeToolbox2022.csproj", "{7D29CECE-07D3-4417-9D63-1362852F18F3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IncludeToolbox2019", "IncludeToolbox2019\IncludeToolbox2019.csproj", "{A81A5332-6A20-4F3B-90B4-E55985B9CF59}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests\Tests.csproj", "{34631D93-26A2-4682-8C7C-B2042CD7D872}" EndProject Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + IncludeToolboxShared\IncludeToolboxShared.projitems*{7d29cece-07d3-4417-9d63-1362852f18f3}*SharedItemsImports = 4 + IncludeToolboxShared\IncludeToolboxShared.projitems*{a81a5332-6a20-4f3b-90b4-e55985b9cf59}*SharedItemsImports = 4 + IncludeToolboxShared\IncludeToolboxShared.projitems*{c50c4863-6200-4e51-837a-31febc09c8b2}*SharedItemsImports = 13 + EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|arm64 = Debug|arm64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|arm64 = Release|arm64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F9E250C6-A7AD-4888-8F17-6876736B8DCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F9E250C6-A7AD-4888-8F17-6876736B8DCF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F9E250C6-A7AD-4888-8F17-6876736B8DCF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F9E250C6-A7AD-4888-8F17-6876736B8DCF}.Release|Any CPU.Build.0 = Release|Any CPU - {F577F5D2-5E3C-43BE-9030-AF2609A0917A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F577F5D2-5E3C-43BE-9030-AF2609A0917A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F577F5D2-5E3C-43BE-9030-AF2609A0917A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F577F5D2-5E3C-43BE-9030-AF2609A0917A}.Release|Any CPU.Build.0 = Release|Any CPU + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Debug|arm64.ActiveCfg = Debug|Any CPU + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Debug|arm64.Build.0 = Debug|Any CPU + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Debug|x86.ActiveCfg = Debug|x86 + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Debug|x86.Build.0 = Debug|x86 + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Release|Any CPU.Build.0 = Release|Any CPU + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Release|arm64.ActiveCfg = Release|Any CPU + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Release|arm64.Build.0 = Release|Any CPU + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Release|x86.ActiveCfg = Release|x86 + {7D29CECE-07D3-4417-9D63-1362852F18F3}.Release|x86.Build.0 = Release|x86 + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Debug|arm64.ActiveCfg = Debug|Any CPU + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Debug|arm64.Build.0 = Debug|Any CPU + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Debug|x86.ActiveCfg = Debug|x86 + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Debug|x86.Build.0 = Debug|x86 + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Release|Any CPU.Build.0 = Release|Any CPU + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Release|arm64.ActiveCfg = Release|Any CPU + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Release|arm64.Build.0 = Release|Any CPU + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Release|x86.ActiveCfg = Release|x86 + {A81A5332-6A20-4F3B-90B4-E55985B9CF59}.Release|x86.Build.0 = Release|x86 + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Debug|arm64.ActiveCfg = Debug|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Debug|arm64.Build.0 = Debug|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Debug|x86.ActiveCfg = Debug|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Debug|x86.Build.0 = Debug|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Release|Any CPU.Build.0 = Release|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Release|arm64.ActiveCfg = Release|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Release|arm64.Build.0 = Release|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Release|x86.ActiveCfg = Release|Any CPU + {34631D93-26A2-4682-8C7C-B2042CD7D872}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/IncludeToolbox.vsix b/IncludeToolbox.vsix deleted file mode 100644 index 740b214..0000000 --- a/IncludeToolbox.vsix +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d410334bec97304d46120a2621251cefa9e703237c249f5891d3cac2c4b6eb36 -size 71090 diff --git a/IncludeToolbox/Commands/CommandBase.cs b/IncludeToolbox/Commands/CommandBase.cs deleted file mode 100644 index f8a4c2c..0000000 --- a/IncludeToolbox/Commands/CommandBase.cs +++ /dev/null @@ -1,74 +0,0 @@ - -using System; -using System.ComponentModel.Design; -using Microsoft.VisualStudio.Shell; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox.Commands -{ - internal abstract class CommandBase where T : CommandBase, new() - { - /// - /// Initializes the singleton instance of the command. - /// - /// Owner package, not null. - public static void Initialize(Package package) - { - if (package == null) - throw new ArgumentNullException("package"); - ThreadHelper.ThrowIfNotOnUIThread(); - - Instance = new T(); - Instance.Package = package; - Instance.SetupMenuCommand(); - } - - protected virtual void SetupMenuCommand() - { - OleMenuCommandService commandService = ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService; - if(commandService == null) - { - Output.Instance.WriteLine("Failed to retrieve MenuCommandService. No commands could be registered!"); - return; - } - - EventHandler callback = async (sender, e) => - { - try - { - await this.MenuItemCallback(sender, e); - } - catch (Exception exception) - { - await Output.Instance.ErrorMsg("Unexpected Error: {0}", exception.ToString()); - } - }; - - menuCommand = new OleMenuCommand(callback, CommandID); - commandService.AddCommand(menuCommand); - } - - - /// - /// Gets the instance of the command. - /// - public static T Instance - { - get; - private set; - } - - /// - /// VS Package that provides this command, not null. - /// - protected Package Package { get; private set; } - - protected IServiceProvider ServiceProvider => Package; - - protected OleMenuCommand menuCommand; - - public abstract CommandID CommandID { get; } - - protected abstract Task MenuItemCallback(object sender, EventArgs e); - } -} diff --git a/IncludeToolbox/Commands/CommandSetGuids.cs b/IncludeToolbox/Commands/CommandSetGuids.cs deleted file mode 100644 index f68c636..0000000 --- a/IncludeToolbox/Commands/CommandSetGuids.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace IncludeToolbox.Commands -{ - static class CommandSetGuids - { - /// - /// Command menu group (command set GUID) for document menu. - /// - public static readonly Guid MenuGroup = new Guid("aef3a531-8af4-4b7b-800a-e32503dfc6e2"); - - /// - /// Command menu group (command set GUID) for tool menu. - /// - public static readonly Guid ToolGroup = new Guid("032eb795-1f1c-440d-af98-43cdc1de7a8b"); - - /// - /// Command menu group for commands in the project menu. - /// - public static readonly Guid ProjectGroup = new Guid("1970ECF3-6C03-4CCF-B422-8DD07F774ED8"); - - /// - /// Commandset for all toolbar elements in the include graph toolwindow. - /// - public static readonly Guid GraphWindowToolbarCmdSet = new Guid("0B242452-870A-489B-8336-88FD01AEF0C1"); - } -} diff --git a/IncludeToolbox/Commands/FormatIncludes.cs b/IncludeToolbox/Commands/FormatIncludes.cs deleted file mode 100644 index 80ca1d5..0000000 --- a/IncludeToolbox/Commands/FormatIncludes.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.ComponentModel.Design; -using System.Linq; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox.Commands -{ - /// - /// Command handler - /// - internal sealed class FormatIncludes : CommandBase - { - public override CommandID CommandID => new CommandID(CommandSetGuids.MenuGroup, 0x0100); - - public FormatIncludes() - { - } - - protected override void SetupMenuCommand() - { - base.SetupMenuCommand(); - menuCommand.BeforeQueryStatus += UpdateVisibility; - } - - private void UpdateVisibility(object sender, EventArgs e) - { - // Check whether any includes are selected. - var viewHost = VSUtils.GetCurrentTextViewHost(); - var selectionSpan = GetSelectionSpan(viewHost); - var lines = Formatter.IncludeLineInfo.ParseIncludes(selectionSpan.GetText(), Formatter.ParseOptions.RemoveEmptyLines); - - menuCommand.Visible = lines.Any(x => x.ContainsActiveInclude); - } - - /// - /// Returns process selection range - whole lines! - /// - SnapshotSpan GetSelectionSpan(IWpfTextViewHost viewHost) - { - var sel = viewHost.TextView.Selection.StreamSelectionSpan; - var start = new SnapshotPoint(viewHost.TextView.TextSnapshot, sel.Start.Position).GetContainingLine().Start; - var end = new SnapshotPoint(viewHost.TextView.TextSnapshot, sel.End.Position).GetContainingLine().End; - - return new SnapshotSpan(start, end); - } - - /// - /// This function is the callback used to execute the command when the menu item is clicked. - /// See the constructor to see how the menu item is associated with this function using - /// OleMenuCommandService service and MenuCommand class. - /// - /// Event sender. - /// Event args. - protected override async Task MenuItemCallback(object sender, EventArgs e) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - var settings = (FormatterOptionsPage)Package.GetDialogPage(typeof(FormatterOptionsPage)); - - // Try to find absolute paths - var document = VSUtils.GetDTE().ActiveDocument; - var project = document.ProjectItem?.ContainingProject; - if (project == null) - { - Output.Instance.WriteLine("The document '{0}' is not part of a project.", document.Name); - return; - } - var includeDirectories = VSUtils.GetProjectIncludeDirectories(project); - - // Read. - var viewHost = VSUtils.GetCurrentTextViewHost(); - var selectionSpan = GetSelectionSpan(viewHost); - - // Format - string formatedText = Formatter.IncludeFormatter.FormatIncludes(selectionSpan.GetText(), document.FullName, includeDirectories, settings); - - // Overwrite. - using (var edit = viewHost.TextView.TextBuffer.CreateEdit()) - { - edit.Replace(selectionSpan, formatedText); - edit.Apply(); - } - } - } -} diff --git a/IncludeToolbox/Commands/IncludeGraphToolWindow.cs b/IncludeToolbox/Commands/IncludeGraphToolWindow.cs deleted file mode 100644 index 7dc6e9f..0000000 --- a/IncludeToolbox/Commands/IncludeGraphToolWindow.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.ComponentModel.Design; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox.Commands -{ - /// - /// Command handler - /// - internal sealed class IncludeGraphToolWindow : CommandBase - { - public override CommandID CommandID => new CommandID(CommandSetGuids.ToolGroup, 0x0102); - - /// - /// Shows the tool window when the menu item is clicked. - /// - /// The event sender. - /// The event args. - protected override async Task MenuItemCallback(object sender, EventArgs e) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - // Get the instance number 0 of this tool window. This window is single instance so this instance - // is actually the only one. - // The last flag is set to true so that if the tool window does not exists it will be created. - ToolWindowPane window = Package.FindToolWindow(typeof(GraphWindow.IncludeGraphToolWindow), 0, true); - if (window?.Frame == null) - { - await Output.Instance.ErrorMsg("Failed to open Include Graph window!"); - } - else - { - IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame; - windowFrame.SetProperty((int)__VSFPROPID.VSFPROPID_CmdUIGuid, GraphWindow.IncludeGraphToolWindow.GUIDString); - Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show()); - } - } - } -} diff --git a/IncludeToolbox/Commands/IncludeWhatYouUse.cs b/IncludeToolbox/Commands/IncludeWhatYouUse.cs deleted file mode 100644 index 2871ff7..0000000 --- a/IncludeToolbox/Commands/IncludeWhatYouUse.cs +++ /dev/null @@ -1,209 +0,0 @@ -using IncludeToolbox.IncludeWhatYouUse; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using System; -using System.ComponentModel.Design; -using System.IO; -using System.Threading.Tasks; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox.Commands -{ - /// - /// Command handler - /// - internal sealed class IncludeWhatYouUse : CommandBase - { - public override CommandID CommandID => new CommandID(CommandSetGuids.MenuGroup, 0x0103); - - /// - /// Whether we already checked for updates. - /// - private bool checkedForUpdatesThisSession = false; - - public IncludeWhatYouUse() - { - } - - protected override void SetupMenuCommand() - { - base.SetupMenuCommand(); - menuCommand.BeforeQueryStatus += UpdateVisibility; - } - - private void UpdateVisibility(object sender, EventArgs e) - { - // Needs to be part of a VCProject to be applicable. - var document = VSUtils.GetDTE()?.ActiveDocument; - menuCommand.Visible = VSUtils.VCUtils.IsVCProject(document?.ProjectItem?.ContainingProject); - } - - private async Task DownloadIWYUWithProgressBar(string executablePath, IVsThreadedWaitDialogFactory dialogFactory) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - IVsThreadedWaitDialog2 progressDialog; - dialogFactory.CreateInstance(out progressDialog); - if (progressDialog == null) - { - Output.Instance.WriteLine("Failed to get create wait dialog."); - return false; - } - - progressDialog.StartWaitDialogWithPercentageProgress( - szWaitCaption: "Include Toolbox - Downloading include-what-you-use", - szWaitMessage: "", // comes in later. - szProgressText: null, - varStatusBmpAnim: null, - szStatusBarText: "Downloading include-what-you-use", - fIsCancelable: true, - iDelayToShowDialog: 0, - iTotalSteps: 100, - iCurrentStep: 0); - - var cancellationToken = new System.Threading.CancellationTokenSource(); - - try - { - await IWYUDownload.DownloadIWYU(executablePath, delegate (string section, string status, float percentage) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - bool canceled; - progressDialog.UpdateProgress( - szUpdatedWaitMessage: section, - szProgressText: status, - szStatusBarText: $"Downloading include-what-you-use - {section} - {status}", - iCurrentStep: (int)(percentage * 100), - iTotalSteps: 100, - fDisableCancel: true, - pfCanceled: out canceled); - if (canceled) - { - cancellationToken.Cancel(); - } - }, cancellationToken.Token); - } - catch (Exception e) - { - await Output.Instance.ErrorMsg("Failed to download include-what-you-use: {0}", e); - return false; - } - finally - { - progressDialog.EndWaitDialog(); - } - - return true; - } - - private async Task OptionalDownloadOrUpdate(IncludeWhatYouUseOptionsPage settings, IVsThreadedWaitDialogFactory dialogFactory) - { - // Check existence, offer to download if it's not there. - bool downloadedNewIwyu = false; - if (!File.Exists(settings.ExecutablePath)) - { - 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) - { - return; - } - - downloadedNewIwyu = await DownloadIWYUWithProgressBar(settings.ExecutablePath, dialogFactory); - if (!downloadedNewIwyu) - return; - } - else if (settings.AutomaticCheckForUpdates && !checkedForUpdatesThisSession) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - IVsThreadedWaitDialog2 dialog = null; - dialogFactory.CreateInstance(out dialog); - dialog?.StartWaitDialog("Include Toolbox", "Running Include-What-You-Use", null, null, "Checking for Updates for include-what-you-use", 0, false, true); - bool newVersionAvailable = await IWYUDownload.IsNewerVersionAvailableOnline(settings.ExecutablePath); - dialog?.EndWaitDialog(); - - if (newVersionAvailable) - { - checkedForUpdatesThisSession = true; - 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) - { - downloadedNewIwyu = await DownloadIWYUWithProgressBar(settings.ExecutablePath, dialogFactory); - } - } - } - if (downloadedNewIwyu) - settings.AddMappingFiles(IWYUDownload.GetMappingFilesNextToIwyuPath(settings.ExecutablePath)); - } - - /// - /// This function is the callback used to execute the command when the menu item is clicked. - /// See the constructor to see how the menu item is associated with this function using - /// OleMenuCommandService service and MenuCommand class. - /// - /// Event sender. - /// Event args. - protected override async Task MenuItemCallback(object sender, EventArgs e) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - var settingsIwyu = (IncludeWhatYouUseOptionsPage)Package.GetDialogPage(typeof(IncludeWhatYouUseOptionsPage)); - Output.Instance.Clear(); - - var document = VSUtils.GetDTE().ActiveDocument; - if (document == null) - { - Output.Instance.WriteLine("No active document!"); - return; - } - var project = document.ProjectItem?.ContainingProject; - if (project == null) - { - Output.Instance.WriteLine("The document {0} is not part of a project.", document.Name); - return; - } - - var dialogFactory = ServiceProvider.GetService(typeof(SVsThreadedWaitDialogFactory)) as IVsThreadedWaitDialogFactory; - if (dialogFactory == null) - { - Output.Instance.WriteLine("Failed to get IVsThreadedWaitDialogFactory service."); - return; - } - - await OptionalDownloadOrUpdate(settingsIwyu, dialogFactory); - - // We should really have it now, but just in case our update or download method screwed up. - if (!File.Exists(settingsIwyu.ExecutablePath)) - { - await Output.Instance.ErrorMsg("Unexpected error: Can't find include-what-you-use.exe after download/update."); - return; - } - checkedForUpdatesThisSession = true; - - // Save all documents. - try - { - document.DTE.Documents.SaveAll(); - } - catch(Exception saveException) - { - Output.Instance.WriteLine("Failed to get save all documents: {0}", saveException); - } - - // Start wait dialog. - { - IVsThreadedWaitDialog2 dialog = null; - dialogFactory.CreateInstance(out dialog); - dialog?.StartWaitDialog("Include Toolbox", "Running include-what-you-use", null, null, "Running include-what-you-use", 0, false, true); - - string output = await IWYU.RunIncludeWhatYouUse(document.FullName, project, settingsIwyu); - if (settingsIwyu.ApplyProposal && output != null) - { - var settingsFormatting = (FormatterOptionsPage)Package.GetDialogPage(typeof(FormatterOptionsPage)); - await IWYU.Apply(output, settingsIwyu.RunIncludeFormatter, settingsFormatting); - } - - dialog?.EndWaitDialog(); - } - } - } -} diff --git a/IncludeToolbox/Commands/TrialAndErrorRemoval_CodeWindow.cs b/IncludeToolbox/Commands/TrialAndErrorRemoval_CodeWindow.cs deleted file mode 100644 index 99b27f7..0000000 --- a/IncludeToolbox/Commands/TrialAndErrorRemoval_CodeWindow.cs +++ /dev/null @@ -1,57 +0,0 @@ - - -using System; -using System.ComponentModel.Design; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Windows; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using EnvDTE; -using Microsoft.VisualStudio.Text; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox.Commands -{ - /// - /// Command handler - /// - internal sealed class TrialAndErrorRemoval_CodeWindow : CommandBase - { - public override CommandID CommandID => new CommandID(CommandSetGuids.MenuGroup, 0x0104); - - private TrialAndErrorRemoval impl; - - public TrialAndErrorRemoval_CodeWindow() - { - } - - protected override void SetupMenuCommand() - { - base.SetupMenuCommand(); - - impl = new TrialAndErrorRemoval(); - menuCommand.BeforeQueryStatus += UpdateVisibility; - } - - private async void UpdateVisibility(object sender, EventArgs e) - { - menuCommand.Visible = (await VSUtils.VCUtils.IsCompilableFile(VSUtils.GetDTE().ActiveDocument)).Result; - } - - /// - /// This function is the callback used to execute the command when the menu item is clicked. - /// See the constructor to see how the menu item is associated with this function using - /// OleMenuCommandService service and MenuCommand class. - /// - /// Event sender. - /// Event args. - protected override async Task MenuItemCallback(object sender, EventArgs e) - { - var document = VSUtils.GetDTE().ActiveDocument; - if (document != null) - await impl.PerformTrialAndErrorIncludeRemoval(document, (TrialAndErrorRemovalOptionsPage)Package.GetDialogPage(typeof(TrialAndErrorRemovalOptionsPage))); - } - } -} \ No newline at end of file diff --git a/IncludeToolbox/Commands/TrialAndErrorRemoval_Project.cs b/IncludeToolbox/Commands/TrialAndErrorRemoval_Project.cs deleted file mode 100644 index 06a8917..0000000 --- a/IncludeToolbox/Commands/TrialAndErrorRemoval_Project.cs +++ /dev/null @@ -1,204 +0,0 @@ -using EnvDTE; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; -using System; -using System.Collections.Generic; -using System.ComponentModel.Design; -using System.Threading.Tasks; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox.Commands -{ - /// - /// Command handler - /// - internal sealed class TrialAndErrorRemoval_Project : CommandBase - { - public override CommandID CommandID => new CommandID(CommandSetGuids.ProjectGroup, 0x0100); - - private TrialAndErrorRemoval impl; - private TrialAndErrorRemovalOptionsPage settings; - - private ProjectItems projectItems = null; - private int numTotalRemovedIncludes = 0; - private Queue projectFiles; - - public TrialAndErrorRemoval_Project() - { - projectFiles = new Queue(); - } - - protected override void SetupMenuCommand() - { - base.SetupMenuCommand(); - - impl = new TrialAndErrorRemoval(); - impl.OnFileFinished += OnDocumentIncludeRemovalFinished; - menuCommand.BeforeQueryStatus += UpdateVisibility; - - settings = (TrialAndErrorRemovalOptionsPage)Package.GetDialogPage(typeof(TrialAndErrorRemovalOptionsPage)); - } - - private void OnDocumentIncludeRemovalFinished(int removedIncludes, bool canceled) - { - _ = Task.Run(async () => - { - numTotalRemovedIncludes += removedIncludes; - if (canceled || !await ProcessNextFile()) - { - _ = Output.Instance.InfoMsg("Removed total of {0} #include directives from project.", numTotalRemovedIncludes); - numTotalRemovedIncludes = 0; - } - }); - } - - private void UpdateVisibility(object sender, EventArgs e) - { - ThreadHelper.ThrowIfNotOnUIThread(); - string reason; - var project = GetSelectedCppProject(out reason); - menuCommand.Visible = project != null; - } - - static Project GetSelectedCppProject(out string reasonForFailure) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - reasonForFailure = ""; - - var selectedItems = VSUtils.GetDTE().SelectedItems; - if (selectedItems.Count < 1) - { - reasonForFailure = "Selection is empty!"; - return null; - } - - // Reading .Item(object) behaves weird, but iterating works. - foreach (SelectedItem item in selectedItems) - { - Project vcProject = item?.Project; - if (VSUtils.VCUtils.IsVCProject(vcProject)) - { - return vcProject; - } - } - - reasonForFailure = "Selection does not contain a C++ project!"; - return null; - } - - private async Task ProcessNextFile() - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - while (projectFiles.Count > 0) - { - ProjectItem projectItem = projectFiles.Dequeue(); - - Document document = null; - try - { - document = projectItem.Open().Document; - } - catch (Exception) - { - } - if (document == null) - continue; - - bool started = await impl.PerformTrialAndErrorIncludeRemoval(document, settings); - if (started) - return true; - } - return false; - } - - private static void RecursiveFindFilesInProject(ProjectItems items, ref Queue projectFiles) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - var e = items.GetEnumerator(); - while (e.MoveNext()) - { - var item = e.Current; - if (item == null) - continue; - var projectItem = item as ProjectItem; - if (projectItem == null) - continue; - - Guid projectItemKind = new Guid(projectItem.Kind); - if (projectItemKind == VSConstants.GUID_ItemType_VirtualFolder || - projectItemKind == VSConstants.GUID_ItemType_PhysicalFolder) - { - RecursiveFindFilesInProject(projectItem.ProjectItems, ref projectFiles); - } - else if (projectItemKind == VSConstants.GUID_ItemType_PhysicalFile) - { - projectFiles.Enqueue(projectItem); - } - else - { - Output.Instance.WriteLine("Unexpected Error: Unknown projectItem {0} of Kind {1}", projectItem.Name, projectItem.Kind); - } - } - } - - private async Task PerformTrialAndErrorRemoval(Project project) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - projectItems = project.ProjectItems; - - projectFiles.Clear(); - RecursiveFindFilesInProject(projectItems, ref projectFiles); - - if (projectFiles.Count > 2) - { - 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?") - != Output.MessageResult.Yes) - { - return; - } - } - - numTotalRemovedIncludes = 0; - await ProcessNextFile(); - } - - - /// - /// This function is the callback used to execute the command when the menu item is clicked. - /// See the constructor to see how the menu item is associated with this function using - /// OleMenuCommandService service and MenuCommand class. - /// - /// Event sender. - /// Event args. - protected override async Task MenuItemCallback(object sender, EventArgs e) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - if (TrialAndErrorRemoval.WorkInProgress) - { - await Output.Instance.ErrorMsg("Trial and error include removal already in progress!"); - return; - } - - try - { - Project project = GetSelectedCppProject(out string reasonForFailure); - if (project == null) - { - Output.Instance.WriteLine(reasonForFailure); - return; - } - - await PerformTrialAndErrorRemoval(project); - } - finally - { - projectItems = null; - } - } - } -} diff --git a/IncludeToolbox/Formatter/IncludeFormatter.cs b/IncludeToolbox/Formatter/IncludeFormatter.cs deleted file mode 100644 index 0b5f61f..0000000 --- a/IncludeToolbox/Formatter/IncludeFormatter.cs +++ /dev/null @@ -1,254 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; - -namespace IncludeToolbox.Formatter -{ - public static class IncludeFormatter - { - public static string FormatPath(string absoluteIncludeFilename, FormatterOptionsPage.PathMode pathformat, IEnumerable includeDirectories) - { - if (pathformat == FormatterOptionsPage.PathMode.Absolute) - { - return absoluteIncludeFilename; - } - else - { - // todo: Treat std library files special? - if (absoluteIncludeFilename != null) - { - int bestLength = Int32.MaxValue; - string bestCandidate = null; - - foreach (string includeDirectory in includeDirectories) - { - string proposal = Utils.MakeRelative(includeDirectory, absoluteIncludeFilename); - - if (proposal.Length < bestLength) - { - if (pathformat == FormatterOptionsPage.PathMode.Shortest || - (proposal.IndexOf("../") < 0 && proposal.IndexOf("..\\") < 0)) - { - bestCandidate = proposal; - bestLength = proposal.Length; - } - } - } - - return bestCandidate; - } - } - - return null; - } - - /// - /// Formats the paths of a given list of include line info. - /// - private static void FormatPaths(IEnumerable lines, FormatterOptionsPage.PathMode pathformat, IEnumerable includeDirectories) - { - if (pathformat == FormatterOptionsPage.PathMode.Unchanged) - return; - - foreach (var line in lines) - { - string absoluteIncludeDir = line.TryResolveInclude(includeDirectories, out bool resolvedPath); - if (resolvedPath) - line.IncludeContent = FormatPath(absoluteIncludeDir, pathformat, includeDirectories) ?? line.IncludeContent; - } - } - - private static void FormatDelimiters(IEnumerable lines, FormatterOptionsPage.DelimiterMode delimiterMode) - { - switch (delimiterMode) - { - case FormatterOptionsPage.DelimiterMode.AngleBrackets: - foreach (var line in lines) - line.SetDelimiterType(IncludeLineInfo.DelimiterType.AngleBrackets); - break; - case FormatterOptionsPage.DelimiterMode.Quotes: - foreach (var line in lines) - line.SetDelimiterType(IncludeLineInfo.DelimiterType.Quotes); - break; - } - } - - private static void FormatSlashes(IEnumerable lines, FormatterOptionsPage.SlashMode slashMode) - { - switch (slashMode) - { - case FormatterOptionsPage.SlashMode.ForwardSlash: - foreach (var line in lines) - line.IncludeContent = line.IncludeContent.Replace('\\', '/'); - break; - case FormatterOptionsPage.SlashMode.BackSlash: - foreach (var line in lines) - line.IncludeContent = line.IncludeContent.Replace('/', '\\'); - break; - } - } - - private static List SortIncludes(IList lines, FormatterOptionsPage settings, string documentName) - { - string[] precedenceRegexes = RegexUtils.FixupRegexes(settings.PrecedenceRegexes, documentName); - - List outSortedList = new List(lines.Count); - - IEnumerable includeBatch; - int numConsumedItems = 0; - - do - { - // Fill in all non-include items between batches. - var nonIncludeItems = lines.Skip(numConsumedItems).TakeWhile(x => !x.ContainsActiveInclude); - numConsumedItems += nonIncludeItems.Count(); - outSortedList.AddRange(nonIncludeItems); - - // Process until we hit a preprocessor directive that is not an include. - // Those are boundaries for the sorting which we do not want to cross. - includeBatch = lines.Skip(numConsumedItems).TakeWhile(x => x.ContainsActiveInclude || !x.ContainsPreProcessorDirective); - numConsumedItems += includeBatch.Count(); - - } while (SortIncludeBatch(settings, precedenceRegexes, outSortedList, includeBatch) && numConsumedItems != lines.Count); - - return outSortedList; - } - - private static bool SortIncludeBatch(FormatterOptionsPage settings, string[] precedenceRegexes, - List outSortedList, IEnumerable includeBatch) - { - // Get enumerator and cancel if batch is empty. - if (!includeBatch.Any()) - return false; - - // Fetch settings. - FormatterOptionsPage.TypeSorting typeSorting = settings.SortByType; - bool regexIncludeDelimiter = settings.RegexIncludeDelimiter; - bool blankAfterRegexGroupMatch = settings.BlankAfterRegexGroupMatch; - - // Select only valid include lines and sort them. They'll stay in this relative sorted - // order when rearranged by regex precedence groups. - var includeLines = includeBatch - .Where(x => x.ContainsActiveInclude) - .OrderBy(x => x.IncludeContent) - .ToList(); - - if (settings.RemoveDuplicates) - { - HashSet uniqueIncludes = new HashSet(); - includeLines.RemoveAll(x => !x.ShouldBePreserved && - !uniqueIncludes.Add(x.GetIncludeContentWithDelimiters())); - } - - // Group the includes by the index of the precedence regex they match, or - // precedenceRegexes.Length for no match, and sort the groups by index. - var includeGroups = includeLines - .GroupBy(x => - { - var includeContent = regexIncludeDelimiter ? x.GetIncludeContentWithDelimiters() : x.IncludeContent; - for (int precedence = 0; precedence < precedenceRegexes.Count(); ++precedence) - { - if (Regex.Match(includeContent, precedenceRegexes[precedence]).Success) - return precedence; - } - - return precedenceRegexes.Length; - }, x => x) - .OrderBy(x => x.Key); - - // Optional newlines between regex match groups - var groupStarts = new HashSet(); - if (blankAfterRegexGroupMatch && precedenceRegexes.Length > 0 && includeLines.Count() > 1) - { - // Set flag to prepend a newline to each group's first include - foreach (var grouping in includeGroups) - groupStarts.Add(grouping.First()); - } - - // Flatten the groups - var sortedIncludes = includeGroups.SelectMany(x => x.Select(y => y)); - - // Sort by angle or quoted delimiters if either of those options were selected - if (typeSorting == FormatterOptionsPage.TypeSorting.AngleBracketsFirst) - sortedIncludes = sortedIncludes.OrderBy(x => x.LineDelimiterType == IncludeLineInfo.DelimiterType.AngleBrackets ? 0 : 1); - else if (typeSorting == FormatterOptionsPage.TypeSorting.QuotedFirst) - sortedIncludes = sortedIncludes.OrderBy(x => x.LineDelimiterType == IncludeLineInfo.DelimiterType.Quotes ? 0 : 1); - - // Merge sorted includes with original non-include lines - var sortedIncludeEnumerator = sortedIncludes.GetEnumerator(); - var sortedLines = includeBatch.Select(originalLine => - { - if (originalLine.ContainsActiveInclude) - { - // Replace original include with sorted includes - return sortedIncludeEnumerator.MoveNext() ? sortedIncludeEnumerator.Current : new IncludeLineInfo(); - } - return originalLine; - }); - - if (settings.RemoveEmptyLines) - { - // Removing duplicates may have introduced new empty lines - sortedLines = sortedLines.Where(sortedLine => !string.IsNullOrWhiteSpace(sortedLine.RawLine)); - } - - // Finally, update the actual lines - { - bool firstLine = true; - foreach (var sortedLine in sortedLines) - { - // Handle prepending a newline if requested, as long as: - // - this include is the begin of a new group - // - it's not the first line - // - the previous line isn't already a non-include - if (groupStarts.Contains(sortedLine) && !firstLine && outSortedList[outSortedList.Count - 1].ContainsActiveInclude) - { - outSortedList.Add(new IncludeLineInfo()); - } - outSortedList.Add(sortedLine); - firstLine = false; - } - } - - return true; - } - - /// - /// Formats all includes in a given piece of text. - /// - /// Text to be parsed for includes. - /// Path to the document the edit is occuring in. - /// A list of include directories - /// Settings that determine how the formating should be done. - /// Formated text. - public static string FormatIncludes(string text, string documentPath, IEnumerable includeDirectories, FormatterOptionsPage settings) - { - string documentDir = Path.GetDirectoryName(documentPath); - string documentName = Path.GetFileNameWithoutExtension(documentPath); - - includeDirectories = new string[] { Microsoft.VisualStudio.PlatformUI.PathUtil.Normalize(documentDir) + Path.DirectorySeparatorChar }.Concat(includeDirectories); - - string newLineChars = Utils.GetDominantNewLineSeparator(text); - - var lines = IncludeLineInfo.ParseIncludes(text, settings.RemoveEmptyLines ? ParseOptions.RemoveEmptyLines : ParseOptions.None); - - // Format. - IEnumerable formatingDirs = includeDirectories; - if (settings.IgnoreFileRelative) - { - formatingDirs = formatingDirs.Skip(1); - } - FormatPaths(lines, settings.PathFormat, formatingDirs); - FormatDelimiters(lines, settings.DelimiterFormatting); - FormatSlashes(lines, settings.SlashFormatting); - - // Sorting. Ignores non-include lines. - lines = SortIncludes(lines, settings, documentName); - - // Combine again. - return string.Join(newLineChars, lines.Select(x => x.RawLine)); - } - } -} diff --git a/IncludeToolbox/Formatter/IncludeLineInfo.cs b/IncludeToolbox/Formatter/IncludeLineInfo.cs deleted file mode 100644 index b9a8867..0000000 --- a/IncludeToolbox/Formatter/IncludeLineInfo.cs +++ /dev/null @@ -1,338 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace IncludeToolbox.Formatter -{ - [Flags] - public enum ParseOptions - { - None = 0, - - /// - /// Whether IncludeLineInfo objects should be created for empty lines. - /// - RemoveEmptyLines = 1, - - /// - /// Marks all includes that are within preprocessor conditionals as inactive/non-includes - /// - IgnoreIncludesInPreprocessorConditionals = 2, - - /// - /// Keep only lines that contain valid includes. - /// - KeepOnlyValidIncludes = 4 | RemoveEmptyLines, - } - - /// - /// A line of text + information about the include directive in this line if any. - /// Allows for manipulation of the former. - /// - /// - /// This is obviously not a high performance representation of text, but very easy to use for our purposes here. - /// - public class IncludeLineInfo - { - /// - /// Parses a given text into IncludeLineInfo objects. - /// - /// A list of parsed lines. - public static List ParseIncludes(string text, ParseOptions options) - { - StringReader reader = new StringReader(text); - - var outInfo = new List(); - - // Simplistic parsing. - int openMultiLineComments = 0; - int openIfdefs = 0; - string lineText; - for (int lineNumber = 0; true; ++lineNumber) - { - lineText = reader.ReadLine(); - if (lineText == null) - break; - - if (options.HasFlag(ParseOptions.RemoveEmptyLines) && string.IsNullOrWhiteSpace(lineText)) - continue; - - int commentedSectionStart = int.MaxValue; - int commentedSectionEnd = int.MaxValue; - - // Check for single line comment. - { - int singleLineCommentStart = lineText.IndexOf("//"); - if (singleLineCommentStart != -1) - commentedSectionStart = singleLineCommentStart; - } - - // Check for multi line comments. - { - int multiLineCommentStart = lineText.IndexOf("/*"); - if (multiLineCommentStart > -1 && multiLineCommentStart < commentedSectionStart) - { - ++openMultiLineComments; - commentedSectionStart = multiLineCommentStart; - } - - int multiLineCommentEnd = lineText.IndexOf("*/"); - if (multiLineCommentEnd > -1) - { - --openMultiLineComments; - commentedSectionEnd = multiLineCommentEnd; - } - } - - bool isCommented(int pos) => (commentedSectionStart == int.MaxValue && openMultiLineComments > 0) || (pos > commentedSectionStart && pos < commentedSectionEnd); - - // Check for #if / #ifdefs. - if (options.HasFlag(ParseOptions.IgnoreIncludesInPreprocessorConditionals)) - { - // There can be only a single preprocessor directive per line, so no need to parse more than this. - // (in theory it must be the first thing in the line, but MSVC is not strict on this, so we aren't either. - int ifdefStart = lineText.IndexOf("#if"); - int ifdefEnd = lineText.IndexOf("#endif"); - if (ifdefStart > -1 && !isCommented(ifdefStart)) - { - ++openIfdefs; - } - else if (ifdefEnd > -1 && !isCommented(ifdefEnd)) - { - --openIfdefs; - } - } - - int includeOccurence = lineText.IndexOf("#include"); - - // Not a valid include. - if (includeOccurence == -1 || // Include not found - isCommented(includeOccurence) || // Include commented out - openIfdefs > 0) // Inside an #ifdef block - { - if (!options.HasFlag(ParseOptions.KeepOnlyValidIncludes)) - outInfo.Add(new IncludeLineInfo() { lineText = lineText, LineNumber = lineNumber }); - } - // A valid include - else - { - // Parse include delimiters. - int delimiter1 = -1; - int delimiter0 = lineText.IndexOf('\"', includeOccurence + "#include".Length); - if (delimiter0 == -1) - { - delimiter0 = lineText.IndexOf('<', includeOccurence + "#include".Length); - if (delimiter0 != -1) - delimiter1 = lineText.IndexOf('>', delimiter0 + 1); - } - else - { - delimiter1 = lineText.IndexOf('\"', delimiter0 + 1); - } - - // Might not be valid after all! - if (delimiter0 != -1 && delimiter1 != -1) - outInfo.Add(new IncludeLineInfo() { lineText = lineText, LineNumber = lineNumber, delimiter0 = delimiter0, delimiter1 = delimiter1 }); - else if (!options.HasFlag(ParseOptions.KeepOnlyValidIncludes)) - outInfo.Add(new IncludeLineInfo() { lineText = lineText, LineNumber = lineNumber }); - } - } - - return outInfo; - } - - /// - /// Whether the line includes an enabled include. - /// - /// - /// A line that contains a valid #include may still be ContainsActiveInclude==false if it is commented or (depending on parsing options) #if(def)'ed out. - /// - public bool ContainsActiveInclude => delimiter0 != -1; - - public enum DelimiterType - { - Quotes, - AngleBrackets, - None - } - - public DelimiterType LineDelimiterType - { - get - { - if (ContainsActiveInclude) - { - DelimiterSanityCheck(); - - if (lineText[delimiter0] == '<') - return DelimiterType.AngleBrackets; - else if (lineText[delimiter0] == '\"') - return DelimiterType.Quotes; - } - return DelimiterType.None; - } - } - - /// - /// Changes the type of this line. - /// Has only an effect if ContainsActiveInclude is true. - /// - public void SetDelimiterType(DelimiterType newDelimiterType) - { - if (LineDelimiterType != newDelimiterType && ContainsActiveInclude) - { - DelimiterSanityCheck(); - - if (newDelimiterType == DelimiterType.AngleBrackets) - { - StringBuilder sb = new StringBuilder(lineText); - sb[delimiter0] = '<'; - sb[delimiter1] = '>'; - lineText = sb.ToString(); - } - else if (newDelimiterType == DelimiterType.Quotes) - { - StringBuilder sb = new StringBuilder(lineText); - sb[delimiter0] = '"'; - sb[delimiter1] = '"'; - lineText = sb.ToString(); - } - } - } - - /// - /// Whether the line contains a preprocessor directive. - /// Does not take into account surrounding block comments. - /// - public bool ContainsPreProcessorDirective - { - get - { - // In theory the '#' of a preprocessor directive MUST come first, but just like MSVC we relax the rules a bit here. - foreach (char c in lineText) - { - if (c == '#') - return true; - else if (!char.IsWhiteSpace(c)) - return false; - } - - return false; - } - } - - /// - /// Tries to resolve the include (if any) using a list of directories. - /// - /// Include directories. Keep in mind that IncludeLineInfo does not know the path of the file this include is from. - /// Empty string if this is not an include, absolute include path if possible or raw include if not. - public string TryResolveInclude(IEnumerable includeDirectories, out bool success) - { - if (!ContainsActiveInclude) - { - success = false; - return ""; - } - - string includeContent = IncludeContent; - - foreach (string dir in includeDirectories) - { - string candidate = Path.Combine(dir, includeContent); - if (File.Exists(candidate)) - { - success = true; - return Utils.GetExactPathName(candidate); - } - } - - Output.Instance.WriteLine("Unable to resolve include: '{0}'", includeContent); - success = false; - return includeContent; - } - - /// - /// Include content with added delimiters. - /// - public string GetIncludeContentWithDelimiters() - { - if (ContainsActiveInclude) - { - DelimiterSanityCheck(); - return lineText.Substring(delimiter0, delimiter1 - delimiter0 + 1); - } - else - return string.Empty; - } - - - /// - /// Changes in the include content will NOT be reflected immediately in the raw line text. - /// - /// - public string IncludeContent - { - get - { - if (ContainsActiveInclude) - { - DelimiterSanityCheck(); - int length = delimiter1 - delimiter0 - 1; - return length > 0 ? RawLine.Substring(delimiter0 + 1, length) : ""; - } - else - return string.Empty; - } - set - { - if (!ContainsActiveInclude) - return; - - lineText = lineText.Remove(delimiter0 + 1, delimiter1 - delimiter0 - 1); - lineText = lineText.Insert(delimiter0 + 1, value); - delimiter1 = delimiter0 + value.Length + 1; - } - } - - /// - /// Raw line text as found. - /// - public string RawLine - { - get { return lineText; } - } - private string lineText = ""; - - /// - /// Line number in which this include line occurred within its original file. - /// - /// - /// Starts of course with 0 unlike displayed line numbers. - /// - public int LineNumber { get; private set; } = -1; - - public static bool ContainsPreserveFlag(string lineText) - { - return lineText.Contains("$include-toolbox-preserve$"); - } - - /// - /// Whether the include line should not be removed by iwyu and Trial & Error Removal. - /// - public bool ShouldBePreserved { get { return ContainsPreserveFlag(lineText); } } - - private int delimiter0 = -1; - private int delimiter1 = -1; - - [System.Diagnostics.Conditional("DEBUG")] - private void DelimiterSanityCheck() - { - System.Diagnostics.Debug.Assert(delimiter0 >= 0 && delimiter0 < lineText.Length); - System.Diagnostics.Debug.Assert(delimiter1 >= 0 && delimiter1 < lineText.Length); - System.Diagnostics.Debug.Assert(delimiter0 < delimiter1); - } - } -} diff --git a/IncludeToolbox/IncludeToolbox.args.json b/IncludeToolbox/IncludeToolbox.args.json deleted file mode 100644 index 7c62244..0000000 --- a/IncludeToolbox/IncludeToolbox.args.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "DataCollection": [ - { - "Id": "3856631e-87ac-4b3d-8d21-fccdd977a1ae", - "Command": "/rootsuffix Exp" - } - ] -} \ No newline at end of file diff --git a/IncludeToolbox/IncludeToolbox.csproj b/IncludeToolbox/IncludeToolbox.csproj deleted file mode 100644 index 4bb30e5..0000000 --- a/IncludeToolbox/IncludeToolbox.csproj +++ /dev/null @@ -1,183 +0,0 @@ - - - - 16.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - Debug - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {F9E250C6-A7AD-4888-8F17-6876736B8DCF} - Library - Properties - IncludeToolbox - IncludeToolbox - v4.8 - true - true - true - false - false - true - true - Program - $(DevEnvDir)devenv.exe - /rootsuffix Exp - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - False - - - VSTHRD200 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - Component - - - Component - - - Component - - - Component - - - Component - - - - - - - - - - - - - - - - - - - - - - - - IncludeGraphControl.xaml - - - - - - - - - - - - - - - - - - - - Designer - - - - - - - - - - - - - - - - - - - compile; build; native; contentfiles; analyzers; buildtransitive - - - 17.2.32505.173 - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - Always - true - - - - - - true - - - - - - Menus.ctmenu - - - - - - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - - - - copy "$(TargetDir)IncludeToolbox.vsix" "$(SolutionDir)IncludeToolbox.vsix" - - - \ No newline at end of file diff --git a/IncludeToolbox/IncludeWhatYouUse/IWYU.cs b/IncludeToolbox/IncludeWhatYouUse/IWYU.cs deleted file mode 100644 index 93b2e7d..0000000 --- a/IncludeToolbox/IncludeWhatYouUse/IWYU.cs +++ /dev/null @@ -1,360 +0,0 @@ -using EnvDTE; -using Microsoft.VisualStudio.Shell; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox.IncludeWhatYouUse -{ - /// - /// Command handler for include what you use. - /// - static public class IWYU - { - private static readonly Regex RegexRemoveLine = new Regex(@"^-\s+.+\s+\/\/ lines (\d+)-(\d+)$"); - - private class FormatTask - { - public override string ToString() - { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.Append("Remove:\n"); - foreach (int i in linesToRemove) - stringBuilder.AppendFormat("{0},", i); - stringBuilder.Append("\nAdd:\n"); - foreach (string s in linesToAdd) - stringBuilder.AppendFormat("{0}\n", s); - return stringBuilder.ToString(); - } - - public readonly HashSet linesToRemove = new HashSet(); - public readonly List linesToAdd = new List(); - }; - - - static private Dictionary ParseOutput(string iwyuOutput) - { - Dictionary fileTasks = new Dictionary(); - FormatTask currentTask = null; - bool removeCommands = true; - - //- #include // lines 3-3 - - // Parse what to do. - var lines = Regex.Split(iwyuOutput, "\r\n|\r|\n"); - bool lastLineWasEmpty = false; - foreach (string line in lines) - { - if (line.Length == 0) - { - if (lastLineWasEmpty) - { - currentTask = null; - } - - lastLineWasEmpty = true; - continue; - } - - int i = line.IndexOf(" should add these lines:"); - if (i < 0) - { - i = line.IndexOf(" should remove these lines:"); - if (i >= 0) - removeCommands = true; - } - else - { - removeCommands = false; - } - - if (i >= 0) - { - string file = line.Substring(0, i); - - if (!fileTasks.TryGetValue(file, out currentTask)) - { - currentTask = new FormatTask(); - fileTasks.Add(file, currentTask); - } - } - else if (currentTask != null) - { - if (removeCommands) - { - var match = RegexRemoveLine.Match(line); - if (match.Success) - { - int removeStart, removeEnd; - if (int.TryParse(match.Groups[1].Value, out removeStart) && - int.TryParse(match.Groups[2].Value, out removeEnd)) - { - for (int lineIdx = removeStart; lineIdx <= removeEnd; ++lineIdx) - currentTask.linesToRemove.Add(lineIdx - 1); - } - } - else if (lastLineWasEmpty) - { - currentTask = null; - } - } - else - { - if (!string.IsNullOrWhiteSpace(line)) - { - currentTask.linesToAdd.Add(line); - } - else if (lastLineWasEmpty) - { - currentTask = null; - } - } - } - - lastLineWasEmpty = false; - } - - return fileTasks; - } - - static private async Task ApplyTasks(Dictionary tasks, bool applyFormatting, FormatterOptionsPage formatSettings) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - var dte = VSUtils.GetDTE(); - - foreach (KeyValuePair entry in tasks) - { - string filename = entry.Key.Replace('/', '\\'); // Classy. But Necessary. - EnvDTE.Window fileWindow = dte.ItemOperations.OpenFile(filename); - if (fileWindow == null) - { - await Output.Instance.ErrorMsg("Failed to open File {0}", filename); - continue; - } - fileWindow.Activate(); - - var viewHost = VSUtils.GetCurrentTextViewHost(); - using (var edit = viewHost.TextView.TextBuffer.CreateEdit()) - { - var originalLines = edit.Snapshot.Lines.ToArray(); - - // Determine which line ending to use by majority. - string lineEndingToBeUsed = Utils.GetDominantNewLineSeparator(edit.Snapshot.GetText()); - - // Add lines. - { - // Find last include. - // Will find even if commented out, but we don't care. - int lastIncludeLine = -1; - for (int line = originalLines.Length - 1; line >= 0; --line) - { - if (originalLines[line].GetText().Contains("#include")) - { - lastIncludeLine = line; - break; - } - } - - // Build replacement string - StringBuilder stringToInsertBuilder = new StringBuilder(); - foreach (string lineToAdd in entry.Value.linesToAdd) - { - stringToInsertBuilder.Append(lineToAdd); - stringToInsertBuilder.Append(lineEndingToBeUsed); - } - string stringToInsert = stringToInsertBuilder.ToString(); - - - // optional, format before adding. - if (applyFormatting) - { - var includeDirectories = VSUtils.GetProjectIncludeDirectories(fileWindow.Document.ProjectItem?.ContainingProject); - stringToInsert = Formatter.IncludeFormatter.FormatIncludes(stringToInsert, fileWindow.Document.FullName, includeDirectories, formatSettings); - - // Add a newline if we removed it. - if (formatSettings.RemoveEmptyLines) - stringToInsert += lineEndingToBeUsed; - } - - // Insert. - int insertPosition = 0; - if (lastIncludeLine >= 0 && lastIncludeLine < originalLines.Length) - { - insertPosition = originalLines[lastIncludeLine].EndIncludingLineBreak; - } - edit.Insert(insertPosition, stringToInsert.ToString()); - } - - // Remove lines. - // It should safe to do that last since we added includes at the bottom, this way there is no confusion with the text snapshot. - { - foreach (int lineToRemove in entry.Value.linesToRemove.Reverse()) - { - if (!Formatter.IncludeLineInfo.ContainsPreserveFlag(originalLines[lineToRemove].GetText())) - edit.Delete(originalLines[lineToRemove].ExtentIncludingLineBreak); - } - } - - edit.Apply(); - } - - // For Debugging: - //Output.Instance.WriteLine(""); - //Output.Instance.WriteLine(entry.Key); - //Output.Instance.WriteLine(entry.Value.ToString()); - } - } - - static public async Task Apply(string iwyuOutput, bool applyFormatter, FormatterOptionsPage formatOptions) - { - var tasks = ParseOutput(iwyuOutput); - await ApplyTasks(tasks, applyFormatter, formatOptions); - } - - /// - /// Runs iwyu. Blocks until finished. - /// - static public async Task RunIncludeWhatYouUse(string fullFileName, EnvDTE.Project project, IncludeWhatYouUseOptionsPage settings) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - string preprocessorDefintions; - try - { - preprocessorDefintions = VSUtils.VCUtils.GetCompilerSetting_PreprocessorDefinitions(project); - } - catch (VCQueryFailure e) - { - await Output.Instance.ErrorMsg("Can't run IWYU: {0}", e.Message); - return null; - } - - string output = ""; - using (var process = new System.Diagnostics.Process()) - { - process.StartInfo.UseShellExecute = false; - process.StartInfo.CreateNoWindow = true; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.RedirectStandardError = true; - process.StartInfo.FileName = settings.ExecutablePath; - - // Clang options - var clangOptionList = new List(); - // Disable all diagnostics - clangOptionList.Add("-w"); - // ... despite of that "invalid token paste" comes through a lot. Disable it. - clangOptionList.Add("-Wno-invalid-token-paste"); - // MSVC specific. See https://clang.llvm.org/docs/UsersManual.html#microsoft-extensions - clangOptionList.Add("-fms-compatibility -fms-extensions -fdelayed-template-parsing"); - - // icwyu options - var iwyuOptionList = new List(); - iwyuOptionList.Add("--verbose=" + settings.LogVerbosity); - for (int i = 0; i < settings.MappingFiles.Length; ++i) - iwyuOptionList.Add("--mapping_file=\"" + settings.MappingFiles[i] + "\""); - if (settings.NoDefaultMappings) - iwyuOptionList.Add("--no_default_mappings"); - if (settings.PCHInCode) - iwyuOptionList.Add("--pch_in_code"); - switch (settings.PrefixHeaderIncludes) - { - case IncludeWhatYouUseOptionsPage.PrefixHeaderMode.Add: - iwyuOptionList.Add("--prefix_header_includes=add"); - break; - case IncludeWhatYouUseOptionsPage.PrefixHeaderMode.Remove: - iwyuOptionList.Add("--prefix_header_includes=remove"); - break; - case IncludeWhatYouUseOptionsPage.PrefixHeaderMode.Keep: - iwyuOptionList.Add("--prefix_header_includes=keep"); - break; - } - if (settings.TransitiveIncludesOnly) - iwyuOptionList.Add("--transitive_includes_only"); - - - - // Set max line length so something large so we don't loose comment information. - // Documentation: - // --max_line_length: maximum line length for includes. Note that this only affects comments and alignment thereof, - // the maximum line length can still be exceeded with long file names(default: 80). - iwyuOptionList.Add("--max_line_length=1024"); - - /// write support file with includes, defines and the targetgile. Long argument lists lead to an error. Support files are the solution here. - /// https://github.com/Wumpf/IncludeToolbox/issues/36 - // Include-paths and Preprocessor. - var includes = string.Join(" ", VSUtils.GetProjectIncludeDirectories(project, false).Select(x => "-I \"" + x.Replace("\\", "\\\\") + "\"")); - var defines = preprocessorDefintions.Length == 0 ? "" : string.Join(" ", preprocessorDefintions.Split(';').Select(x => "-D" + x)); - var filename = "\"" + fullFileName.Replace("\\", "\\\\") + "\""; - - var iwyuOptions = string.Join(" ", iwyuOptionList.Select(x => " -Xiwyu " + x)); - - var ext = Path.GetExtension(fullFileName); - - string co = ""; - switch (settings.Commentary) - { - case Commentaries.Always: co = (" -Xiwyu --update_comments"); break; - case Commentaries.Never: co = (" -Xiwyu --no_comments"); break; - case Commentaries.Default: break; - } - - iwyuOptions += co; - - if (ext == ".h" || ext == ".hpp") - { - var tmp_cpp = Path.GetTempFileName(); - tmp_cpp = Path.ChangeExtension(tmp_cpp, ".cpp"); - File.WriteAllText(tmp_cpp, "#include \"" + fullFileName + "\""); - iwyuOptions += " -Xiwyu --check_also=" + filename; - filename = "\"" + tmp_cpp.Replace("\\", "\\\\") + "\""; - } - else if (settings.HeaderPrefix != "" && Directory.Exists(Path.GetDirectoryName(fullFileName) + settings.HeaderPrefix)) - { - var correspond_h = settings.HeaderPrefix + '\\' + Path.GetFileNameWithoutExtension(fullFileName) + ".h"; - var correspond_hpp = Path.ChangeExtension(correspond_h, ".hpp"); - if (!File.Exists(correspond_h)) - iwyuOptions += " -Xiwyu --check_also=" + "\"" + correspond_h.Replace("\\", "\\\\") + "\""; - else if (!File.Exists(correspond_hpp)) - iwyuOptions += " -Xiwyu --check_also=" + "\"" + correspond_hpp.Replace("\\", "\\\\") + "\""; - } - - var supportFilePath = Path.GetTempFileName(); - File.WriteAllText(supportFilePath, includes + " " + defines + " " + filename); - - var clangOptions = string.Join(" ", clangOptionList); - // each include-what-you-use parameter has an -Xiwyu prefix - process.StartInfo.Arguments = $"{clangOptions} {iwyuOptions} {settings.AdditionalParameters} \"@{supportFilePath}\""; - - Output.Instance.Write("Running command '{0}' with following arguments:\n{1}\n\n", process.StartInfo.FileName, process.StartInfo.Arguments); - - // Start the child process. - process.EnableRaisingEvents = true; - process.OutputDataReceived += (s, args) => - { - Output.Instance.WriteLine(args.Data); - output += args.Data + "\n"; - }; - process.ErrorDataReceived += (s, args) => - { - Output.Instance.WriteLine(args.Data); - output += args.Data + "\n"; - }; - process.Start(); - - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - process.WaitForExit(); - process.CancelOutputRead(); - process.CancelErrorRead(); - } - - return output; - } - } -} diff --git a/IncludeToolbox/IncludeWhatYouUse/IWYUDownload.cs b/IncludeToolbox/IncludeWhatYouUse/IWYUDownload.cs deleted file mode 100644 index 1e3677a..0000000 --- a/IncludeToolbox/IncludeWhatYouUse/IWYUDownload.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -namespace IncludeToolbox.IncludeWhatYouUse -{ - /// - /// Functions for downloading and versioning of the iwyu installation. - /// - static public class IWYUDownload - { - public const string DisplayRepositorURL = @"https://github.com/Wumpf/iwyu_for_vs_includetoolbox"; - private const string DownloadRepositorURL = @"https://github.com/Wumpf/iwyu_for_vs_includetoolbox/archive/master.zip"; - private const string LatestCommitQuery = @"https://api.github.com/repos/Wumpf/iwyu_for_vs_includetoolbox/git/refs/heads/master"; - - private static async Task GetCurrentVersionOnline() - { - using (var httpClient = new HttpClient()) - { - // User agent is always required for github api. - // https://developer.github.com/v3/#user-agent-required - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("IncludeToolbox"); - - string latestCommitResponse; - try - { - latestCommitResponse = await httpClient.GetStringAsync(LatestCommitQuery); - } - catch (HttpRequestException e) - { - Output.Instance.WriteLine($"Failed to query IWYU version from {DownloadRepositorURL}: {e}"); - return ""; - } - - // Poor man's json parsing in lack of a json parser. - var shaRegex = new Regex(@"\""sha\""\w*:\w*\""([a-z0-9]+)\"""); - return shaRegex.Match(latestCommitResponse).Groups[1].Value; - } - } - - public static string GetVersionFilePath(string iwyuExectuablePath) - { - string directory = Path.GetDirectoryName(iwyuExectuablePath); - return Path.Combine(directory, "version"); - } - - private static string GetCurrentVersionHarddrive(string iwyuExectuablePath) - { - // Read current version. - try - { - return File.ReadAllText(GetVersionFilePath(iwyuExectuablePath)); - } - catch - { - return ""; - } - } - - public static async Task IsNewerVersionAvailableOnline(string executablePath) - { - string currentVersion = GetCurrentVersionHarddrive(executablePath); - string onlineVersion = await GetCurrentVersionOnline(); - return currentVersion != onlineVersion; - } - - /// - /// Callback for download progress. - /// - /// General stage. - /// Sub status, may be empty. - /// Progress in percent for current section. -1 is there is none. - public delegate void DownloadProgressUpdate(string section, string status, float percentage); - - /// - /// Downloads iwyu from default download repository. - /// - /// - /// Throws an exception if anything goes wrong (and there's a lot that can!) - /// - /// If cancellation token is used. - static public async Task DownloadIWYU(string executablePath, DownloadProgressUpdate onProgressUpdate, CancellationToken cancellationToken) - { - string targetDirectory = Path.GetDirectoryName(executablePath); - Directory.CreateDirectory(targetDirectory); - string targetZipFile = Path.Combine(targetDirectory, "download.zip"); - - // Delete existing zip file. - try - { - File.Delete(targetZipFile); - } - catch { } - - // Download. - onProgressUpdate("Connecting...", "", -1.0f); - - // In contrast to GetCurrentVersionOnline we're not using HttpClient here since WebClient makes downloading files so much nicer. - // (in HttpClient we would need to do the whole buffering + queuing and file writing ourselves) - using (var client = new WebClient()) - { - var cancelRegistration = cancellationToken.Register(() => - { - client.CancelAsync(); - throw new TaskCanceledException(); - }); - - client.DownloadProgressChanged += (object sender, DownloadProgressChangedEventArgs e) => - { - int kbTodo = (int)System.Math.Ceiling((double)e.TotalBytesToReceive / 1024); - int kbDownloaded = (int)System.Math.Ceiling((double)e.BytesReceived / 1024); - onProgressUpdate("Downloading", kbTodo > 0 ? $"{kbTodo} / {kbDownloaded} kB" : $"{kbDownloaded} kB", e.ProgressPercentage * 0.01f); - }; - - await client.DownloadFileTaskAsync(DownloadRepositorURL, targetZipFile); - - cancelRegistration.Dispose(); - } - - // Unpacking. Looks like there is no async api, so we're just moving this to a task. - onProgressUpdate("Unpacking...", "", -1.0f); - await Task.Run(() => - { - using (var zipArchive = new ZipArchive(File.OpenRead(targetZipFile), ZipArchiveMode.Read)) - { - // Don't want to have the top level folder if any, - string topLevelFolderName = ""; - - for (int i = 0; i < zipArchive.Entries.Count; ++i) - { - var file = zipArchive.Entries[i]; - - string targetName = file.FullName.Substring(topLevelFolderName.Length); - string completeFileName = Path.Combine(targetDirectory, targetName); - - // If name is empty it should be a directory. - if (file.Name == "") - { - if (i == 0) // We assume that if the first thing we encounter is a folder, it is a toplevel one. - topLevelFolderName = file.FullName; - else - Directory.CreateDirectory(Path.GetDirectoryName(completeFileName)); - } - else - { - using (var destination = File.Open(completeFileName, FileMode.Create, FileAccess.Write, FileShare.None)) - { - using (var stream = file.Open()) - stream.CopyTo(destination); - } - } - - if (cancellationToken.IsCancellationRequested) - return; - } - } - - }, cancellationToken); - - // Save version. - onProgressUpdate("Saving Version", "", -1.0f); - string version = await GetCurrentVersionOnline(); - File.WriteAllText(GetVersionFilePath(executablePath), version); - } - - static public IEnumerable GetMappingFilesNextToIwyuPath(string executablePath) - { - string targetDirectory = Path.GetDirectoryName(executablePath); - - var impFiles = Directory.EnumerateFiles(targetDirectory). - Where(file => Path.GetExtension(file).Equals(".imp", System.StringComparison.InvariantCultureIgnoreCase)); - foreach (string dirs in Directory.EnumerateDirectories(targetDirectory)) - { - impFiles.Concat( - Directory.EnumerateFiles(targetDirectory). - Where(file => Path.GetExtension(file).Equals(".imp", System.StringComparison.InvariantCultureIgnoreCase)) - ); - } - - return impFiles; - } - } -} diff --git a/IncludeToolbox/Options/Constants.cs b/IncludeToolbox/Options/Constants.cs deleted file mode 100644 index ff696d1..0000000 --- a/IncludeToolbox/Options/Constants.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace IncludeToolbox.Options -{ - public static class Constants - { - public const string Category = "Include Toolbox"; - } -} diff --git a/IncludeToolbox/Options/FormatterOptionsPage.cs b/IncludeToolbox/Options/FormatterOptionsPage.cs deleted file mode 100644 index 3f2bff5..0000000 --- a/IncludeToolbox/Options/FormatterOptionsPage.cs +++ /dev/null @@ -1,166 +0,0 @@ -using Microsoft.VisualStudio.Shell; -using System; -using System.ComponentModel; -using System.Linq; -using System.Runtime.InteropServices; - -namespace IncludeToolbox -{ - [Guid("B822F53B-32C0-4560-9A84-2F9DA7AB0E4C")] - public class FormatterOptionsPage : OptionsPage - { - public const string SubCategory = "Include Formatter"; - private const string collectionName = "IncludeFormatter"; - - #region Path - - public enum PathMode - { - Unchanged, - Shortest, - Shortest_AvoidUpSteps, - Absolute, - } - [Category("Path")] - [DisplayName("Mode")] - [Description("Changes the path mode to the given pattern.")] - public PathMode PathFormat { get; set; } = PathMode.Shortest_AvoidUpSteps; - - [Category("Path")] - [DisplayName("Ignore File Relative")] - [Description("If true, include directives will not take the path of the file into account.")] - public bool IgnoreFileRelative { get; set; } = false; - - //[Category("Path")] - //[DisplayName("Ignore Standard Library")] - //[Description("")] - //public bool IgnorePathForStdLib { get; set; } = true; - - #endregion - - #region Formatting - - public enum DelimiterMode - { - Unchanged, - AngleBrackets, - Quotes, - } - [Category("Formatting")] - [DisplayName("Delimiter Mode")] - [Description("Optionally changes all delimiters to either angle brackets <...> or quotes \"...\".")] - public DelimiterMode DelimiterFormatting { get; set; } = DelimiterMode.Unchanged; - - public enum SlashMode - { - Unchanged, - ForwardSlash, - BackSlash, - } - [Category("Formatting")] - [DisplayName("Slash Mode")] - [Description("Changes all slashes to the given type.")] - public SlashMode SlashFormatting { get; set; } = SlashMode.ForwardSlash; - - [Category("Formatting")] - [DisplayName("Remove Empty Lines")] - [Description("If true, all empty lines of a include selection will be removed.")] - public bool RemoveEmptyLines { get; set; } = true; - - #endregion - - #region Sorting - - [Category("Sorting")] - [DisplayName("Include delimiters in precedence regexes")] - [Description("If true, precedence regexes will consider delimiters (angle brackets or quotes.)")] - public bool RegexIncludeDelimiter { get; set; } = false; - - [Category("Sorting")] - [DisplayName("Insert blank line between precedence regex match groups")] - [Description("If true, a blank line will be inserted after each group matching one of the precedence regexes.")] - public bool BlankAfterRegexGroupMatch { get; set; } = false; - - [Category("Sorting")] - [DisplayName("Precedence Regexes")] - [Description("Earlier match means higher sorting priority.\n\"" + RegexUtils.CurrentFileNameKey + "\" will be replaced with the current file name without extension.")] - public string[] PrecedenceRegexes { - get { return precedenceRegexes; } - set { precedenceRegexes = value.Where(x => x.Length > 0).ToArray(); } // Remove empty lines. - } - private string[] precedenceRegexes = new string[] { $"(?i){RegexUtils.CurrentFileNameKey}\\.(h|hpp|hxx|inl|c|cpp|cxx)(?-i)" }; - - public enum TypeSorting - { - None, - AngleBracketsFirst, - QuotedFirst, - } - [Category("Sorting")] - [DisplayName("Sort by Include Type")] - [Description("Optionally put either includes with angle brackets <...> or quotes \"...\" first.")] - public TypeSorting SortByType { get; set; } = TypeSorting.QuotedFirst; - - [Category("Sorting")] - [DisplayName("Remove duplicates")] - [Description("If true, duplicate includes will be removed.")] - public bool RemoveDuplicates { get; set; } = true; - - #endregion - - public override void SaveSettingsToStorage() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var settingsStore = GetSettingsStore(); - - if (!settingsStore.CollectionExists(collectionName)) - settingsStore.CreateCollection(collectionName); - - settingsStore.SetInt32(collectionName, nameof(PathFormat), (int)PathFormat); - settingsStore.SetBoolean(collectionName, nameof(IgnoreFileRelative), IgnoreFileRelative); - - settingsStore.SetInt32(collectionName, nameof(DelimiterFormatting), (int)DelimiterFormatting); - settingsStore.SetInt32(collectionName, nameof(SlashFormatting), (int)SlashFormatting); - settingsStore.SetBoolean(collectionName, nameof(RemoveEmptyLines), RemoveEmptyLines); - - settingsStore.SetBoolean(collectionName, nameof(RegexIncludeDelimiter), RegexIncludeDelimiter); - settingsStore.SetBoolean(collectionName, nameof(BlankAfterRegexGroupMatch), BlankAfterRegexGroupMatch); - var value = string.Join("\n", PrecedenceRegexes); - settingsStore.SetString(collectionName, nameof(PrecedenceRegexes), value); - settingsStore.SetInt32(collectionName, nameof(SortByType), (int)SortByType); - settingsStore.SetBoolean(collectionName, nameof(RemoveDuplicates), RemoveDuplicates); - } - - public override void LoadSettingsFromStorage() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var settingsStore = GetSettingsStore(); - - if (settingsStore.PropertyExists(collectionName, nameof(PathFormat))) - PathFormat = (PathMode)settingsStore.GetInt32(collectionName, nameof(PathFormat)); - if (settingsStore.PropertyExists(collectionName, nameof(IgnoreFileRelative))) - IgnoreFileRelative = settingsStore.GetBoolean(collectionName, nameof(IgnoreFileRelative)); - - if (settingsStore.PropertyExists(collectionName, nameof(DelimiterFormatting))) - DelimiterFormatting = (DelimiterMode) settingsStore.GetInt32(collectionName, nameof(DelimiterFormatting)); - if (settingsStore.PropertyExists(collectionName, nameof(SlashFormatting))) - SlashFormatting = (SlashMode) settingsStore.GetInt32(collectionName, nameof(SlashFormatting)); - if (settingsStore.PropertyExists(collectionName, nameof(RemoveEmptyLines))) - RemoveEmptyLines = settingsStore.GetBoolean(collectionName, nameof(RemoveEmptyLines)); - - if (settingsStore.PropertyExists(collectionName, nameof(RegexIncludeDelimiter))) - RegexIncludeDelimiter = settingsStore.GetBoolean(collectionName, nameof(RegexIncludeDelimiter)); - if (settingsStore.PropertyExists(collectionName, nameof(BlankAfterRegexGroupMatch))) - BlankAfterRegexGroupMatch = settingsStore.GetBoolean(collectionName, nameof(BlankAfterRegexGroupMatch)); - if (settingsStore.PropertyExists(collectionName, nameof(PrecedenceRegexes))) - { - var value = settingsStore.GetString(collectionName, nameof(PrecedenceRegexes)); - PrecedenceRegexes = value.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); - } - if (settingsStore.PropertyExists(collectionName, nameof(SortByType))) - SortByType = (TypeSorting) settingsStore.GetInt32(collectionName, nameof(SortByType)); - if (settingsStore.PropertyExists(collectionName, nameof(RemoveDuplicates))) - RemoveDuplicates = settingsStore.GetBoolean(collectionName, nameof(RemoveDuplicates)); - } - } -} diff --git a/IncludeToolbox/Options/IncludeWhatYouUseOptionsPage.cs b/IncludeToolbox/Options/IncludeWhatYouUseOptionsPage.cs deleted file mode 100644 index 32ab9e1..0000000 --- a/IncludeToolbox/Options/IncludeWhatYouUseOptionsPage.cs +++ /dev/null @@ -1,192 +0,0 @@ -using Microsoft.VisualStudio.Shell; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; - -namespace IncludeToolbox -{ - [Guid("69CFD797-2E2B-497E-9231-334BCDC41407")] - public class IncludeWhatYouUseOptionsPage : OptionsPage - { - public const string SubCategory = "Include-What-You-Use"; - private const string collectionName = "IncludeFormatter"; - - #region iwyu source - - [Category("iwyu general")] - [DisplayName("Executable Path")] - [Description("File path of include-what-you-use.exe. If automatic download is active, this folder will be used.")] - public string ExecutablePath { get; set; } = ""; - - [Category("iwyu general")] - [DisplayName("Automatic Updates")] - [Description("If true, automatic check for updates will be done on first use each session. Will download from https://github.com/Wumpf/iwyu_for_vs_includetoolbox. " + - "Set this to false if you want to use your own include-what-you-use version.")] - public bool AutomaticCheckForUpdates { get; set; } = true; - - static public string GetDefaultExecutablePath() - { - return System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "iwyu", "include-what-you-use.exe"); - } - - #endregion - - #region iwyu options - - [Category("iwyu options")] - [DisplayName("Log Verbosity")] - [Description("The higher the level, the more output. A level lower 1 might disable automatic include replacing (--verbose)")] - public int LogVerbosity { get; set; } = 2; - - [Category("iwyu options")] - [DisplayName("Mapping File")] - [Description("Gives iwyu a mapping file. (--mapping_file)")] - public string[] MappingFiles { get; set; } = new string[0]; - - /// - /// Adds a list of mapping files and eliminates duplicates. - /// - public void AddMappingFiles(IEnumerable mappingFilesPaths) - { - var resolvedNewFiles = mappingFilesPaths.Select(x => new KeyValuePair(Path.GetFullPath(x), x)); - var resolvedOldFiles = MappingFiles.Select(x => new KeyValuePair(Path.GetFullPath(x), x)); - MappingFiles = resolvedNewFiles.Union(resolvedOldFiles).Select(x => x.Value).ToArray(); - } - - [Category("iwyu options")] - [DisplayName("No Default Mappings")] - [Description("Do not add iwyu's default mappings. (--no_default_mappings)")] - public bool NoDefaultMappings { get; set; } = false; - - [Category("iwyu options")] - [DisplayName("Commentaries")] - [Description("Change the output mode of the commentaries from IWYU.")] - [TypeConverter(typeof(EnumConverter))] - public Commentaries Commentary { get; set; } = Commentaries.Default; - - [Category("iwyu options")] - [DisplayName("PCH in Code")] - [Description("Mark the first include in a translation unit as a precompiled header. Use to prevent IWYU from removing necessary PCH includes. Though Clang forces PCHs to be listed as prefix headers, the PCH in code pattern can be used with GCC and is standard practice on MSVC (e.g.stdafx.h). (--pch_in_code)")] - public bool PCHInCode { get; set; } = true; - - public enum PrefixHeaderMode - { - Add, - Keep, - Remove - } - [Category("iwyu options")] - [DisplayName("Prefix Header Include Mode")] - [Description("Tells iwyu what to do with in-source includes and forward declarations involving prefix headers. Prefix header is a file included via command-line option -include. If prefix header makes include or forward declaration obsolete, presence of such include can be controlled with the following values:\nAdd: new lines are added\nKeep: new lines aren't added, existing are kept intact\nRemove: new lines aren't added, existing are removed. (--prefix_header_includes)")] - public PrefixHeaderMode PrefixHeaderIncludes { get; set; } = PrefixHeaderMode.Add; - - [Category("iwyu options")] - [DisplayName("Transitive Includes Only")] - [Description("Do not suggest that a file add foo.h unless foo.h is already visible in the file's transitive includes. (--transitive_includes_only)")] - public bool TransitiveIncludesOnly { get; set; } = false; - - [Category("iwyu options")] - [DisplayName("Additional Parameters")] - [Description("This string is inserted after all other parameters and before the filename of the file iwyu is running on.")] - public string AdditionalParameters { get; set; } = ""; - - - [Category("iwyu options")] - [DisplayName("Header file prefix")] - [Description("Specify Header file prefix folder. Used to find pair header eg. a.cpp has an include/proj/a.h path to it, so the value should be ./include/proj. Works only relative to file.")] - public string HeaderPrefix { get; set; } = ""; - - #endregion - - #region postprocessing - - [Category("Post Processing")] - [DisplayName("Apply Proposal")] - [Description("Applies iwyu's proposal directly to all files. Requires at least Log Verbosity of 1.")] - public bool ApplyProposal { get; set; } = true; - - [Category("Post Processing")] - [DisplayName("Run Include Formatter on Changes")] - [Description("Runs the Include Formatter on all changed lines.")] - public bool RunIncludeFormatter { get; set; } = true; - - - #endregion - - public override void SaveSettingsToStorage() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var settingsStore = GetSettingsStore(); - - if (!settingsStore.CollectionExists(collectionName)) - settingsStore.CreateCollection(collectionName); - - settingsStore.SetString(collectionName, nameof(ExecutablePath), ExecutablePath); - settingsStore.SetBoolean(collectionName, nameof(AutomaticCheckForUpdates), AutomaticCheckForUpdates); - - settingsStore.SetInt32(collectionName, nameof(LogVerbosity), LogVerbosity); - - var value = string.Join("\n", MappingFiles); - settingsStore.SetString(collectionName, nameof(MappingFiles), value); - - settingsStore.SetBoolean(collectionName, nameof(NoDefaultMappings), NoDefaultMappings); - settingsStore.SetBoolean(collectionName, nameof(PCHInCode), PCHInCode); - settingsStore.SetInt32(collectionName, nameof(PrefixHeaderIncludes), (int)PrefixHeaderIncludes); - settingsStore.SetBoolean(collectionName, nameof(TransitiveIncludesOnly), TransitiveIncludesOnly); - settingsStore.SetString(collectionName, nameof(AdditionalParameters), AdditionalParameters); - settingsStore.SetString(collectionName, nameof(HeaderPrefix), HeaderPrefix); - - settingsStore.SetBoolean(collectionName, nameof(ApplyProposal), ApplyProposal); - settingsStore.SetBoolean(collectionName, nameof(RunIncludeFormatter), RunIncludeFormatter); - } - - public override void LoadSettingsFromStorage() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var settingsStore = GetSettingsStore(); - - if (settingsStore.PropertyExists(collectionName, nameof(ExecutablePath))) - ExecutablePath = settingsStore.GetString(collectionName, nameof(ExecutablePath)); - else - ExecutablePath = GetDefaultExecutablePath(); - if (settingsStore.PropertyExists(collectionName, nameof(AutomaticCheckForUpdates))) - AutomaticCheckForUpdates = settingsStore.GetBoolean(collectionName, nameof(AutomaticCheckForUpdates)); - - if (settingsStore.PropertyExists(collectionName, nameof(LogVerbosity))) - LogVerbosity = settingsStore.GetInt32(collectionName, nameof(LogVerbosity)); - if (settingsStore.PropertyExists(collectionName, nameof(MappingFiles))) - { - var value = settingsStore.GetString(collectionName, nameof(MappingFiles)); - MappingFiles = value.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); - } - if (settingsStore.PropertyExists(collectionName, nameof(NoDefaultMappings))) - NoDefaultMappings = settingsStore.GetBoolean(collectionName, nameof(NoDefaultMappings)); - if (settingsStore.PropertyExists(collectionName, nameof(PCHInCode))) - PCHInCode = settingsStore.GetBoolean(collectionName, nameof(PCHInCode)); - if (settingsStore.PropertyExists(collectionName, nameof(PrefixHeaderIncludes))) - PrefixHeaderIncludes = (PrefixHeaderMode)settingsStore.GetInt32(collectionName, nameof(PrefixHeaderIncludes)); - if (settingsStore.PropertyExists(collectionName, nameof(TransitiveIncludesOnly))) - TransitiveIncludesOnly = settingsStore.GetBoolean(collectionName, nameof(TransitiveIncludesOnly)); - if (settingsStore.PropertyExists(collectionName, nameof(AdditionalParameters))) - AdditionalParameters = settingsStore.GetString(collectionName, nameof(AdditionalParameters)); - if (settingsStore.PropertyExists(collectionName, nameof(HeaderPrefix))) - HeaderPrefix = settingsStore.GetString(collectionName, nameof(HeaderPrefix)); - - if (settingsStore.PropertyExists(collectionName, nameof(ApplyProposal))) - ApplyProposal = settingsStore.GetBoolean(collectionName, nameof(ApplyProposal)); - if (settingsStore.PropertyExists(collectionName, nameof(RunIncludeFormatter))) - RunIncludeFormatter = settingsStore.GetBoolean(collectionName, nameof(RunIncludeFormatter)); - } - } - - public enum Commentaries - { - Default, - Always, - Never - } - -} diff --git a/IncludeToolbox/Options/OptionsPage.cs b/IncludeToolbox/Options/OptionsPage.cs deleted file mode 100644 index e00c83f..0000000 --- a/IncludeToolbox/Options/OptionsPage.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Microsoft.VisualStudio.Settings; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Settings; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; - -namespace IncludeToolbox -{ - /// - /// Base class for all option pages. - /// - public abstract class OptionsPage : DialogPage - { - /// - /// Initializes either with a in place created TaskContext for tests or, if our VS package is acutally active with the standard context. - /// - public OptionsPage() : base(IncludeToolboxPackage.Instance == null ? -#pragma warning disable VSSDK005 // Avoid instantiating JoinableTaskContext - new Microsoft.VisualStudio.Threading.JoinableTaskContext() : ThreadHelper.JoinableTaskContext) -#pragma warning restore VSSDK005 // Avoid instantiating JoinableTaskContext - { } - - // In theory the whole save/load mechanism should be done automatically. - // But *something* is or was broken there. - // see http://stackoverflow.com/questions/32751040/store-array-in-options-using-dialogpage - - static protected WritableSettingsStore GetSettingsStore() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var settingsManager = new ShellSettingsManager(ServiceProvider.GlobalProvider); - return settingsManager.GetWritableSettingsStore(SettingsScope.UserSettings); - } - - abstract public override void SaveSettingsToStorage(); - abstract public override void LoadSettingsFromStorage(); - } -} diff --git a/IncludeToolbox/Options/TrialAndErrorRemovalOptionsPage.cs b/IncludeToolbox/Options/TrialAndErrorRemovalOptionsPage.cs deleted file mode 100644 index 9eba438..0000000 --- a/IncludeToolbox/Options/TrialAndErrorRemovalOptionsPage.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Microsoft.VisualStudio.Shell; -using System; -using System.ComponentModel; -using System.Runtime.InteropServices; - -namespace IncludeToolbox -{ - [Guid("DBC8A65D-8B86-4296-9F1F-E785B182B550")] - public class TrialAndErrorRemovalOptionsPage : OptionsPage - { - public const string SubCategory = "Trial and Error Include Removal"; - 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. - - public enum IncludeRemovalOrder - { - BottomToTop, - TopToBottom, - } - [Category(SubCategory)] - [DisplayName("Removal Order")] - [Description("Gives the order which #includes are removed.")] - public IncludeRemovalOrder RemovalOrder { get; set; } = IncludeRemovalOrder.BottomToTop; - - [Category(SubCategory)] - [DisplayName("Ignore First Include")] - [Description("If true, the first include of a file will never be removed (useful for ignoring PCH).")] - public bool IgnoreFirstInclude { get; set; } = true; - - [Category(SubCategory)] - [DisplayName("Ignore List")] - [Description("List of regexes. If the content of a #include directive match with any of these, it will be ignored." + - "\n\"" + RegexUtils.CurrentFileNameKey + "\" will be replaced with the current file name without extension.")] - public string[] IgnoreList { get; set; } = new string[] { $"(\\/|\\\\|^){RegexUtils.CurrentFileNameKey}\\.(h|hpp|hxx|inl|c|cpp|cxx)$", ".inl", "_inl.h" }; - - [Category(SubCategory)] - [DisplayName("Keep Line Breaks")] - [Description("If true, removed includes will leave an empty line.")] - public bool KeepLineBreaks { get; set; } = false; - - - public override void SaveSettingsToStorage() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var settingsStore = GetSettingsStore(); - - if (!settingsStore.CollectionExists(collectionName)) - settingsStore.CreateCollection(collectionName); - - settingsStore.SetInt32(collectionName, nameof(RemovalOrder), (int)RemovalOrder); - settingsStore.SetBoolean(collectionName, nameof(IgnoreFirstInclude), IgnoreFirstInclude); - - var value = string.Join("\n", IgnoreList); - settingsStore.SetString(collectionName, nameof(IgnoreList), value); - - settingsStore.SetBoolean(collectionName, nameof(KeepLineBreaks), KeepLineBreaks); - } - - public override void LoadSettingsFromStorage() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var settingsStore = GetSettingsStore(); - - if (settingsStore.PropertyExists(collectionName, nameof(RemovalOrder))) - RemovalOrder = (IncludeRemovalOrder)settingsStore.GetInt32(collectionName, nameof(RemovalOrder)); - if (settingsStore.PropertyExists(collectionName, nameof(IgnoreFirstInclude))) - IgnoreFirstInclude = settingsStore.GetBoolean(collectionName, nameof(IgnoreFirstInclude)); - - if (settingsStore.PropertyExists(collectionName, nameof(IgnoreList))) - { - var value = settingsStore.GetString(collectionName, nameof(IgnoreList)); - IgnoreList = value.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); - } - - if (settingsStore.PropertyExists(collectionName, nameof(KeepLineBreaks))) - KeepLineBreaks = settingsStore.GetBoolean(collectionName, nameof(KeepLineBreaks)); - } - } -} diff --git a/IncludeToolbox/Options/ViewerOptionsPage.cs b/IncludeToolbox/Options/ViewerOptionsPage.cs deleted file mode 100644 index fb7d159..0000000 --- a/IncludeToolbox/Options/ViewerOptionsPage.cs +++ /dev/null @@ -1,111 +0,0 @@ -using Microsoft.VisualStudio.Shell; -using System; -using System.ComponentModel; -using System.Runtime.InteropServices; - -namespace IncludeToolbox -{ - [Guid("769AFCC2-25E2-459A-B2A3-89D7308800BD")] - public class ViewerOptionsPage : OptionsPage - { - public const string SubCategory = "Include Viewer & Graph"; - private const string collectionName = "IncludeViewer"; - - [Category("Include Graph Parsing")] - [DisplayName("Graph Endpoint Directories")] - [Description("List of absolute directory paths. For any include below these paths, the graph parsing will stop.")] - public string[] NoParsePaths - { - get { return noParsePaths; } - set - { - // It is critical that the paths are "exact" since we want to use them as with string comparison. - noParsePaths = value; - for (int i = 0; i < noParsePaths.Length; ++i) - noParsePaths[i] = Utils.GetExactPathName(noParsePaths[i]); - } - } - private string[] noParsePaths; - - [Category("Include Graph DGML")] - [DisplayName("Create Group Nodes by Folders")] - [Description("Creates folders like in the folder hierarchy view of Include Graph.")] - public bool CreateGroupNodesForFolders { get; set; } = true; - - [Category("Include Graph DGML")] - [DisplayName("Expand Folder Group Nodes")] - [Description("If true all folder nodes start out expanded, otherwise they are collapsed.")] - public bool ExpandFolderGroupNodes { get; set; } = false; - - [Category("Include Graph DGML")] - [DisplayName("Colorize by Number of Includes")] - [Description("If true each node gets color coded according to its number of unique transitive includes.")] - public bool ColorCodeNumTransitiveIncludes { get; set; } = true; - - [Category("Include Graph DGML")] - [DisplayName("No Children Color")] - [Description("See \"Colorize by Number of Includes\". Color for no children at all.")] - public System.Drawing.Color NoChildrenColor { get; set; } = System.Drawing.Color.White; - - [Category("Include Graph DGML")] - [DisplayName("Max Children Color")] - [Description("See \"Colorize by Number of Includes\". Color for highest number of children.")] - public System.Drawing.Color MaxChildrenColor { get; set; } = System.Drawing.Color.Red; - - public override void SaveSettingsToStorage() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var settingsStore = GetSettingsStore(); - - if (!settingsStore.CollectionExists(collectionName)) - settingsStore.CreateCollection(collectionName); - - var value = string.Join("\n", NoParsePaths); - settingsStore.SetString(collectionName, nameof(NoParsePaths), value); - - - settingsStore.SetBoolean(collectionName, nameof(CreateGroupNodesForFolders), CreateGroupNodesForFolders); - settingsStore.SetBoolean(collectionName, nameof(ExpandFolderGroupNodes), ExpandFolderGroupNodes); - settingsStore.SetBoolean(collectionName, nameof(ColorCodeNumTransitiveIncludes), ColorCodeNumTransitiveIncludes); - settingsStore.SetInt32(collectionName, nameof(NoChildrenColor), NoChildrenColor.ToArgb()); - settingsStore.SetInt32(collectionName, nameof(MaxChildrenColor), MaxChildrenColor.ToArgb()); - } - - public override void LoadSettingsFromStorage() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var settingsStore = GetSettingsStore(); - - if (settingsStore.PropertyExists(collectionName, nameof(NoParsePaths))) - { - var value = settingsStore.GetString(collectionName, nameof(NoParsePaths)); - NoParsePaths = value.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); - } - else - { - // It is surprisingly hard to get to the standard library paths. - // Even finding the VS install path is not easy and - as it turns out - not necessarily where the standard library files reside. - // So in lack of a better idea, we just put everything under the program files folder in here. - string programFiles = Environment.ExpandEnvironmentVariables("%ProgramW6432%"); - string programFilesX86 = Environment.ExpandEnvironmentVariables("%ProgramFiles(x86)%"); - if(programFiles == programFilesX86) // If somebody uses still x86 system. - NoParsePaths = new string[] { programFiles }; - else - NoParsePaths = new string[] { programFiles, programFilesX86 }; - } - - - if (settingsStore.PropertyExists(collectionName, nameof(CreateGroupNodesForFolders))) - CreateGroupNodesForFolders = settingsStore.GetBoolean(collectionName, nameof(CreateGroupNodesForFolders)); - if (settingsStore.PropertyExists(collectionName, nameof(ExpandFolderGroupNodes))) - ExpandFolderGroupNodes = settingsStore.GetBoolean(collectionName, nameof(ExpandFolderGroupNodes)); - - if (settingsStore.PropertyExists(collectionName, nameof(ColorCodeNumTransitiveIncludes))) - ColorCodeNumTransitiveIncludes = settingsStore.GetBoolean(collectionName, nameof(ColorCodeNumTransitiveIncludes)); - if (settingsStore.PropertyExists(collectionName, nameof(NoChildrenColor))) - NoChildrenColor = System.Drawing.Color.FromArgb(settingsStore.GetInt32(collectionName, nameof(NoChildrenColor))); - if (settingsStore.PropertyExists(collectionName, nameof(MaxChildrenColor))) - MaxChildrenColor = System.Drawing.Color.FromArgb(settingsStore.GetInt32(collectionName, nameof(MaxChildrenColor))); - } - } -} diff --git a/IncludeToolbox/Output.cs b/IncludeToolbox/Output.cs deleted file mode 100644 index 630cddc..0000000 --- a/IncludeToolbox/Output.cs +++ /dev/null @@ -1,115 +0,0 @@ -using EnvDTE; -using EnvDTE80; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using System.Threading.Tasks; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox -{ - public class Output - { - static public Output Instance { private set; get; } = new Output(); - - public enum MessageResult - { - Yes, - No - } - - private const int VsMessageBoxResult_Yes = 6; - - private Output() - { - } - - private OutputWindowPane outputWindowPane = null; - - private void Init() - { - ThreadHelper.ThrowIfNotOnUIThread(); - - DTE2 dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as DTE2; - if (dte == null) - return; - - OutputWindow outputWindow = dte.ToolWindows.OutputWindow; - outputWindowPane = outputWindow.OutputWindowPanes.Add("IncludeToolbox"); - } - - public void Clear() - { - ThreadHelper.ThrowIfNotOnUIThread(); - - if (outputWindowPane == null) - Init(); - outputWindowPane.Clear(); - } - - public async Task WriteInternal(string text) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - if (outputWindowPane == null) - Init(); - - if (outputWindowPane != null) - { - System.Diagnostics.Debug.Assert(outputWindowPane != null); - outputWindowPane.OutputString(text); - } - } - - public void Write(string text) - { - // Typically we don't care if the message was already written, so let's move this to a different (main-thread) task. - _ = WriteInternal(text); - } - - public void Write(string text, params object[] stringParams) - { - string output = string.Format(text, stringParams); - Write(output); - } - - public void WriteLine(string line) - { - Write(line + '\n'); - } - - public void WriteLine(string line, params object[] stringParams) - { - string output = string.Format(line, stringParams); - WriteLine(output); - } - - public async Task ErrorMsg(string message, params object[] stringParams) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - string output = string.Format(message, stringParams); - WriteLine(output); - VsShellUtilities.ShowMessageBox(ServiceProvider.GlobalProvider, output, "Include Toolbox", OLEMSGICON.OLEMSGICON_CRITICAL, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); - } - - public async Task InfoMsg(string message, params object[] stringParams) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - string output = string.Format(message, stringParams); - WriteLine(output); - VsShellUtilities.ShowMessageBox(ServiceProvider.GlobalProvider, output, "Include Toolbox", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); - } - - public async Task YesNoMsg(string message) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - int result = VsShellUtilities.ShowMessageBox(ServiceProvider.GlobalProvider, message, "Include Toolbox", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_YESNO, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); - return result == VsMessageBoxResult_Yes ? MessageResult.Yes : MessageResult.No; - } - - public void OutputToForeground() - { - ThreadHelper.ThrowIfNotOnUIThread(); - outputWindowPane.Activate(); - } - } -} diff --git a/IncludeToolbox/Package/CommandDefinitions.vsct b/IncludeToolbox/Package/CommandDefinitions.vsct deleted file mode 100644 index 7180394..0000000 --- a/IncludeToolbox/Package/CommandDefinitions.vsct +++ /dev/null @@ -1,210 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - DefaultDocked - - Include Graph Toolbar - Include Graph Toolbar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DropDown Combo: - Graph Retrieval Method - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/IncludeToolbox/Package/IncludeToolboxPackage.cs b/IncludeToolbox/Package/IncludeToolboxPackage.cs deleted file mode 100644 index 0ed6224..0000000 --- a/IncludeToolbox/Package/IncludeToolboxPackage.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Threading; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox -{ - [ProvideBindingPath(SubPath = "")] // Necessary to find packaged assemblies. - - [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] - [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] - [ProvideMenuResource("Menus.ctmenu", 1)] - - [ProvideOptionPage(typeof(FormatterOptionsPage), Options.Constants.Category, FormatterOptionsPage.SubCategory, 1000, 1001, true)] - [ProvideOptionPage(typeof(IncludeWhatYouUseOptionsPage), Options.Constants.Category, IncludeWhatYouUseOptionsPage.SubCategory, 1000, 1002, true)] - [ProvideOptionPage(typeof(TrialAndErrorRemovalOptionsPage), Options.Constants.Category, TrialAndErrorRemovalOptionsPage.SubCategory, 1000, 1003, true)] - [ProvideOptionPage(typeof(ViewerOptionsPage), Options.Constants.Category, ViewerOptionsPage.SubCategory, 1000, 1004, true)] - - [ProvideToolWindow(typeof(GraphWindow.IncludeGraphToolWindow))] - [Guid(IncludeToolboxPackage.PackageGuidString)] - [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] - [InstalledProductRegistration("#110", "#112", "0.2", IconResourceID = 400)] - public sealed class IncludeToolboxPackage : AsyncPackage - { - /// - /// IncludeToolboxPackage GUID string. - /// - public const string PackageGuidString = "5c2743c4-1b3f-4edd-b6a0-4379f867d47f"; - - static public Package Instance { get; private set; } - - public IncludeToolboxPackage() - { - // Inside this method you can place any initialization code that does not require - // any Visual Studio service because at this point the package object is created but - // not sited yet inside Visual Studio environment. The place to do all the other - // initialization is the Initialize method. - Instance = this; - } - - #region Package Members - - protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) - { - await base.InitializeAsync(cancellationToken, progress); - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - Commands.IncludeGraphToolWindow.Initialize(this); - Commands.FormatIncludes.Initialize(this); - Commands.IncludeWhatYouUse.Initialize(this); - Commands.TrialAndErrorRemoval_CodeWindow.Initialize(this); - Commands.TrialAndErrorRemoval_Project.Initialize(this); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - } - - #endregion - } -} diff --git a/IncludeToolbox/Package/Key.snk b/IncludeToolbox/Package/Key.snk deleted file mode 100644 index f4625de..0000000 Binary files a/IncludeToolbox/Package/Key.snk and /dev/null differ diff --git a/IncludeToolbox/Package/VSPackage.resx b/IncludeToolbox/Package/VSPackage.resx deleted file mode 100644 index 04a830f..0000000 --- a/IncludeToolbox/Package/VSPackage.resx +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - IncludeToolbox Extension - - - Tools around C/C++ #includes - - - \ No newline at end of file diff --git a/IncludeToolbox/Package/source.extension.vsixmanifest b/IncludeToolbox/Package/source.extension.vsixmanifest deleted file mode 100644 index 5ef82bd..0000000 --- a/IncludeToolbox/Package/source.extension.vsixmanifest +++ /dev/null @@ -1,29 +0,0 @@ - - - - - IncludeToolbox - Various tools for managing C/C++ #includes: Formatting, sorting, exploring, pruning. - license.txt - Resources\IncludeFormatterPackage.png - Resources\IncludeFormatterPackage.png - Include, Include What You Use, IWYU, Include Formatting, Include Sorting, #include, Include Removal - - - - - amd64 - - - - - - - - - - - - - - diff --git a/IncludeToolbox/Properties/AssemblyInfo.cs b/IncludeToolbox/Properties/AssemblyInfo.cs deleted file mode 100644 index e6839c2..0000000 --- a/IncludeToolbox/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("IncludeToolbox")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("IncludeToolbox")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/IncludeToolbox/Resources/IncludeFormatterIcons.pdn b/IncludeToolbox/Resources/IncludeFormatterIcons.pdn deleted file mode 100644 index 4619200..0000000 Binary files a/IncludeToolbox/Resources/IncludeFormatterIcons.pdn and /dev/null differ diff --git a/IncludeToolbox/Resources/IncludeFormatterPackage.png b/IncludeToolbox/Resources/IncludeFormatterPackage.png deleted file mode 100644 index 042d01b..0000000 Binary files a/IncludeToolbox/Resources/IncludeFormatterPackage.png and /dev/null differ diff --git a/IncludeToolbox/Resources/include13.png b/IncludeToolbox/Resources/include13.png deleted file mode 100644 index 6f5e2b4..0000000 Binary files a/IncludeToolbox/Resources/include13.png and /dev/null differ diff --git a/IncludeToolbox/Resources/include16.png b/IncludeToolbox/Resources/include16.png deleted file mode 100644 index da17114..0000000 Binary files a/IncludeToolbox/Resources/include16.png and /dev/null differ diff --git a/IncludeToolbox/TrialAndErrorRemoval.cs b/IncludeToolbox/TrialAndErrorRemoval.cs deleted file mode 100644 index 40a2753..0000000 --- a/IncludeToolbox/TrialAndErrorRemoval.cs +++ /dev/null @@ -1,320 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Windows; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using EnvDTE; -using Microsoft.VisualStudio.Text; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox -{ - /// - /// Command handler for trial and error include removal - /// - internal sealed class TrialAndErrorRemoval - { - public delegate void FinishedEvent(int numRemovedIncludes, bool canceled); - public event FinishedEvent OnFileFinished; - - public static bool WorkInProgress { get; private set; } - - private volatile bool lastBuildSuccessful; - private AutoResetEvent outputWaitEvent = new AutoResetEvent(false); - private const int timeoutMS = 600000; // 600 seconds, 10 minutes per file - - /// - /// Need to keep build events object around as long as it is used, otherwise the events may not be fired! - /// - private BuildEvents buildEvents; - - public async Task PerformTrialAndErrorIncludeRemoval(EnvDTE.Document document, TrialAndErrorRemovalOptionsPage settings) - { - if (document == null) - return false; - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - var canCompile = await VSUtils.VCUtils.IsCompilableFile(document); - if (canCompile.Result == false) - { - Output.Instance.WriteLine($"Can't compile file '{canCompile.Reason}': {document.Name}"); - return false; - } - - if (WorkInProgress) - { - _ = Output.Instance.ErrorMsg("Trial and error include removal already in progress!"); - return false; - } - WorkInProgress = true; - - // Start wait dialog. - IVsThreadedWaitDialog2 progressDialog = await StartProgressDialog(document.Name); - if (progressDialog == null) - return false; - - // Extract all includes. - ITextBuffer textBuffer; - Formatter.IncludeLineInfo[] includeLines; - try - { - ExtractSelectionAndIncludes(document, settings, out textBuffer, out includeLines); - } - catch (Exception ex) - { - Output.Instance.WriteLine("Unexpected error while extracting include selection: {0}", ex); - progressDialog.EndWaitDialog(); - return false; - } - - // Hook into build events. - SubscribeBuildEvents(); - - // The rest runs in a separate thread since the compile function is non blocking and we want to use BuildEvents - // We are not using Task, since we want to make use of WaitHandles - using this together with Task is a bit more complicated to get right. - outputWaitEvent.Reset(); - var removalThread = new System.Threading.Thread(() => TrialAndErrorRemovalThreadFunc(document, settings, includeLines, progressDialog, textBuffer)); - removalThread.Start(); - return true; - } - - private async Task StartProgressDialog(string documentName) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - var dialogFactory = Package.GetGlobalService(typeof(SVsThreadedWaitDialogFactory)) as IVsThreadedWaitDialogFactory; - if (dialogFactory == null) - { - Output.Instance.WriteLine("Failed to get Dialog Factory for wait dialog."); - return null; - } - - IVsThreadedWaitDialog2 progressDialog; - dialogFactory.CreateInstance(out progressDialog); - if (progressDialog == null) - { - Output.Instance.WriteLine("Failed to get create wait dialog."); - return null; - } - string waitMessage = $"Parsing '{documentName}' ... "; - progressDialog.StartWaitDialogWithPercentageProgress( - szWaitCaption: "Include Toolbox - Running Trial & Error Include Removal", - szWaitMessage: waitMessage, - szProgressText: null, - varStatusBmpAnim: null, - szStatusBarText: "Running Trial & Error Removal - " + waitMessage, - fIsCancelable: true, - iDelayToShowDialog: 0, - iTotalSteps: 20, // Will be replaced. - iCurrentStep: 0); - - return progressDialog; - } - - private void ExtractSelectionAndIncludes(EnvDTE.Document document, TrialAndErrorRemovalOptionsPage settings, - out ITextBuffer textBuffer, out Formatter.IncludeLineInfo[] includeLinesArray) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - // Parsing. - document.Activate(); - var documentTextView = VSUtils.GetCurrentTextViewHost(); - textBuffer = documentTextView.TextView.TextBuffer; - string documentText = documentTextView.TextView.TextSnapshot.GetText(); - IEnumerable includeLines = Formatter.IncludeLineInfo.ParseIncludes(documentText, Formatter.ParseOptions.IgnoreIncludesInPreprocessorConditionals | Formatter.ParseOptions.KeepOnlyValidIncludes); - - // Optionally skip top most include. - if (settings.IgnoreFirstInclude) - includeLines = includeLines.Skip(1); - - // Skip everything with preserve flag. - includeLines = includeLines.Where(x => !x.ShouldBePreserved); - - // Apply filter ignore regex. - { - string documentName = Path.GetFileNameWithoutExtension(document.FullName); - string[] ignoreRegexList = RegexUtils.FixupRegexes(settings.IgnoreList, documentName); - includeLines = includeLines.Where(line => !ignoreRegexList.Any(regexPattern => - new System.Text.RegularExpressions.Regex(regexPattern).Match(line.IncludeContent).Success)); - } - // Reverse order if necessary. - if (settings.RemovalOrder == TrialAndErrorRemovalOptionsPage.IncludeRemovalOrder.BottomToTop) - includeLines = includeLines.Reverse(); - - includeLinesArray = includeLines.ToArray(); - } - - private void TrialAndErrorRemovalThreadFunc(EnvDTE.Document document, TrialAndErrorRemovalOptionsPage settings, - Formatter.IncludeLineInfo[] includeLines, IVsThreadedWaitDialog2 progressDialog, ITextBuffer textBuffer) - { - int numRemovedIncludes = 0; - bool canceled = false; - - try - { - int currentProgressStep = 0; - - // For ever include line.. - foreach (Formatter.IncludeLineInfo line in includeLines) - { - // If we are working from top to bottom, the line number may have changed! - int currentLine = line.LineNumber; - if (settings.RemovalOrder == TrialAndErrorRemovalOptionsPage.IncludeRemovalOrder.TopToBottom) - currentLine -= numRemovedIncludes; - - ThreadHelper.JoinableTaskFactory.Run(async () => - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - // Update progress. - string waitMessage = $"Removing #includes from '{document.Name}'"; - string progressText = $"Trying to remove '{line.IncludeContent}' ..."; - progressDialog.UpdateProgress( - szUpdatedWaitMessage: waitMessage, - szProgressText: progressText, - szStatusBarText: "Running Trial & Error Removal - " + waitMessage + " - " + progressText, - iCurrentStep: currentProgressStep + 1, - iTotalSteps: includeLines.Length + 1, - fDisableCancel: false, - pfCanceled: out canceled); - if (!canceled) - { - ++currentProgressStep; - - // Remove include - using (var edit = textBuffer.CreateEdit()) - { - if (settings.KeepLineBreaks) - edit.Delete(edit.Snapshot.Lines.ElementAt(currentLine).Extent); - else - edit.Delete(edit.Snapshot.Lines.ElementAt(currentLine).ExtentIncludingLineBreak); - edit.Apply(); - } - } - outputWaitEvent.Set(); - }); - outputWaitEvent.WaitOne(); - - if (canceled) - break; - - // Compile - In rare cases VS tells us that we are still building which should not be possible because we have received OnBuildFinished - // As a workaround we just try again a few times. - ThreadHelper.JoinableTaskFactory.Run(async () => - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - const int maxNumCompileAttempts = 3; - for (int numCompileFails = 0; numCompileFails < maxNumCompileAttempts; ++numCompileFails) - { - // TODO: This happens on the main thread. Making the whole thread thing a bit pointless!!! - try - { - await VSUtils.VCUtils.CompileSingleFile(document); - } - catch (Exception e) - { - Output.Instance.WriteLine("Compile Failed:\n{0}", e); - - if (numCompileFails == maxNumCompileAttempts - 1) - { - document.Undo(); - throw e; - } - else - { - // Try again. - await System.Threading.Tasks.Task.Delay(100); - continue; - } - } - break; - } - }); - - // Wait till woken. - bool noTimeout = outputWaitEvent.WaitOne(timeoutMS); - - // Undo removal if compilation failed. - if (!noTimeout || !lastBuildSuccessful) - { - Output.Instance.WriteLine("Could not remove #include: '{0}'", line.IncludeContent); - ThreadHelper.JoinableTaskFactory.Run(async () => - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - document.Undo(); - if (!noTimeout) - await Output.Instance.ErrorMsg("Compilation of {0} timeouted!", document.Name); - }); - - if (!noTimeout) - break; - } - else - { - Output.Instance.WriteLine("Successfully removed #include: '{0}'", line.IncludeContent); - ++numRemovedIncludes; - } - } - } - catch (Exception ex) - { - Output.Instance.WriteLine("Unexpected error: {0}", ex); - } - finally - { - _ = OnTrialAndErrorRemovalDone(progressDialog, document, numRemovedIncludes, canceled); - } - } - - private async Task OnTrialAndErrorRemovalDone(IVsThreadedWaitDialog2 progressDialog, EnvDTE.Document document, int numRemovedIncludes, bool canceled) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - // Close Progress bar. - progressDialog.EndWaitDialog(); - - // Remove build hook again. - UnsubscribeBuildEvents(); - - // Message. - Output.Instance.WriteLine("Removed {0} #include directives from '{1}'", numRemovedIncludes, document.Name); - Output.Instance.OutputToForeground(); - - // Notify that we are done. - WorkInProgress = false; - OnFileFinished?.Invoke(numRemovedIncludes, canceled); - } - - private void SubscribeBuildEvents() - { - ThreadHelper.ThrowIfNotOnUIThread(); - buildEvents = VSUtils.GetDTE().Events.BuildEvents; - buildEvents.OnBuildDone += OnBuildFinished; - buildEvents.OnBuildProjConfigDone += OnBuildConfigFinished; - } - - private void UnsubscribeBuildEvents() - { - buildEvents.OnBuildDone -= OnBuildFinished; - buildEvents.OnBuildProjConfigDone -= OnBuildConfigFinished; - buildEvents = null; - } - - private void OnBuildFinished(vsBuildScope scope, vsBuildAction action) - { - //Output.Instance.WriteLine("OnBuildFinished. scope: {0}, action: {1}", scope, action); - outputWaitEvent.Set(); - } - - private void OnBuildConfigFinished(string project, string projectConfig, string platform, string solutionConfig, bool success) - { - //Output.Instance.WriteLine("OnBuildConfigFinished. project {0}, projectConfig {1}, platform {2}, solutionConfig {3}, success {4}", project, projectConfig, platform, solutionConfig, success); - lastBuildSuccessful = success; - } - } -} \ No newline at end of file diff --git a/IncludeToolbox/VCHelper.cs b/IncludeToolbox/VCHelper.cs deleted file mode 100644 index 8dfd72e..0000000 --- a/IncludeToolbox/VCHelper.cs +++ /dev/null @@ -1,223 +0,0 @@ -using EnvDTE; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.VCProjectEngine; -using System.Threading.Tasks; -using Task = System.Threading.Tasks.Task; - -namespace IncludeToolbox -{ - public class VCQueryFailure : System.Exception - { - public VCQueryFailure(string message) : base(message) - { - } - } - - public class VCHelper - { - public bool IsVCProject(Project project) - { - return project?.Object is VCProject; - } - - private static async Task GetVCFileConfigForCompilation(Document document) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - if (document == null) - throw new VCQueryFailure("No document."); - - var vcProject = document.ProjectItem?.ContainingProject?.Object as VCProject; - if (vcProject == null) - throw new VCQueryFailure("The given document does not belong to a VC++ Project."); - - VCFile vcFile = document.ProjectItem?.Object as VCFile; - if (vcFile == null) - throw new VCQueryFailure("The given document is not a VC++ file."); - - if (vcFile.FileType != eFileType.eFileTypeCppCode) - throw new VCQueryFailure("The given document is not a compileable VC++ file."); - - IVCCollection fileConfigCollection = vcFile.FileConfigurations as IVCCollection; - VCFileConfiguration fileConfig = fileConfigCollection?.Item(vcProject.ActiveConfiguration.Name) as VCFileConfiguration; - if (fileConfig == null) - throw new VCQueryFailure("Failed to retrieve file config from document."); - - return fileConfig; - } - - private static VCTool GetToolFromActiveConfiguration(Project project) where VCTool: class - { - ThreadHelper.ThrowIfNotOnUIThread(); - - VCProject vcProject = project?.Object as VCProject; - if (vcProject == null) - throw new VCQueryFailure($"Failed to retrieve VCCLCompilerTool since project \"{project.Name}\" is not a VCProject."); - - VCConfiguration activeConfiguration = vcProject.ActiveConfiguration; - VCTool compilerTool = null; - foreach (var tool in (IVCCollection)activeConfiguration.Tools) - { - compilerTool = tool as VCTool; - if (compilerTool != null) - break; - } - - if (compilerTool == null) - throw new VCQueryFailure($"Couldn't find a {typeof(VCTool).Name} in active configuration of VC++ Project \"{vcProject.Name}\""); - - return compilerTool; - } - - public static VCLinkerTool GetLinkerTool(Project project) - { - VCProject vcProject = project?.Object as VCProject; - if (vcProject == null) - throw new VCQueryFailure("Failed to retrieve VCLinkerTool since project is not a VCProject."); - - VCConfiguration activeConfiguration = vcProject.ActiveConfiguration; - var tools = activeConfiguration.Tools; - VCLinkerTool linkerTool = null; - foreach (var tool in (IVCCollection)activeConfiguration.Tools) - { - linkerTool = tool as VCLinkerTool; - if (linkerTool != null) - break; - } - - if (linkerTool == null) - throw new VCQueryFailure("Couldn't file a VCLinkerTool in VC++ Project."); - - return linkerTool; - } - - public async Task IsCompilableFile(Document document) - { - try - { - await GetVCFileConfigForCompilation(document); - } - catch (VCQueryFailure queryFailure) - { - return new BoolWithReason() - { - Result = false, - Reason = queryFailure.Message, - }; - } - - return new BoolWithReason() - { - Result = true, - Reason = "", - }; - } - - public async Task CompileSingleFile(Document document) - { - var fileConfig = await GetVCFileConfigForCompilation(document); - if (fileConfig != null) - fileConfig.Compile(true, false); // WaitOnBuild==true always fails. - } - - public string GetCompilerSetting_Includes(Project project) - { - VCQueryFailure queryFailure; - try - { - VCCLCompilerTool compilerTool = GetToolFromActiveConfiguration(project); - if (compilerTool != null) - return compilerTool.FullIncludePath; - else - queryFailure = new VCQueryFailure("Unhandled error"); - } - catch (VCQueryFailure e) - { - queryFailure = e; - } - - // 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. - try - { - VCNMakeTool nmakeTool = GetToolFromActiveConfiguration(project); - if (nmakeTool == null) - throw queryFailure; - - return nmakeTool.IncludeSearchPath; - } - catch - { - throw queryFailure; - } - } - - public void SetCompilerSetting_ShowIncludes(Project project, bool show) - { - GetToolFromActiveConfiguration(project).ShowIncludes = show; - } - - public bool GetCompilerSetting_ShowIncludes(Project project) - { - return GetToolFromActiveConfiguration(project).ShowIncludes; - } - - public string GetCompilerSetting_PreprocessorDefinitions(Project project) - { - VCQueryFailure queryFailure; - try - { - VCCLCompilerTool compilerTool = GetToolFromActiveConfiguration(project); - if (compilerTool != null) - return compilerTool.PreprocessorDefinitions.Replace("\\\"$(INHERIT)\\\";", ""); - else - queryFailure = new VCQueryFailure("Unhandled error"); - } - catch (VCQueryFailure e) - { - queryFailure = e; - } - - // 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. - try - { - VCNMakeTool nmakeTool = GetToolFromActiveConfiguration(project); - if (nmakeTool == null) - throw queryFailure; - - return nmakeTool.PreprocessorDefinitions; - } - catch - { - throw queryFailure; - } - } - - // https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.vcprojectengine.machinetypeoption.aspx - public enum TargetMachineType - { - NotSet = 0, - X86 = 1, - AM33 = 2, - ARM = 3, - EBC = 4, - IA64 = 5, - M32R = 6, - MIPS = 7, - MIPS16 = 8, - MIPSFPU = 9, - MIPSFPU16 = 10, - MIPSR41XX = 11, - SH3 = 12, - SH3DSP = 13, - SH4 = 14, - SH5 = 15, - THUMB = 16, - AMD64 = 17 - } - - public TargetMachineType GetLinkerSetting_TargetMachine(EnvDTE.Project project) - { - return (TargetMachineType)GetLinkerTool(project).TargetMachine; - } - } -} diff --git a/IncludeToolbox/VSUtils.cs b/IncludeToolbox/VSUtils.cs deleted file mode 100644 index f1f9653..0000000 --- a/IncludeToolbox/VSUtils.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.TextManager.Interop; -using EnvDTE; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.Interop; - -namespace IncludeToolbox -{ - public static class VSUtils - { - public static EnvDTE80.DTE2 GetDTE() - { - var dte = Package.GetGlobalService(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2; - if (dte == null) - { - throw new System.Exception("Failed to retrieve DTE2!"); - } - return dte; - } - - // Historically, the GUIDs of the COM interfaces in VCProject/VCProjectEngine would change from version to version. - // To work around this we had several builds of VCHelpers that we could choose from, each with a different dependency. - // With VS2019, the older versions are no longer available and we're stuck with a single version for better or worse. - - public static VCHelper VCUtils = new VCHelper(); - - /// - /// Returns what the C++ macro _MSC_VER should resolve to. - /// - /// - public static string GetMSCVerString() - { - // See http://stackoverflow.com/questions/70013/how-to-detect-if-im-compiling-code-with-visual-studio-2008 - var dte = GetDTE(); - var dteVersion = dte.Version; - if (dte.Version.StartsWith("14.")) - return "1900"; - else if (dte.Version.StartsWith("15.")) - return "1915"; - else if (dte.Version.StartsWith("16.")) - return "1920"; - else - throw new NotImplementedException("Unknown MSVC version!"); - } - - /// - /// Tries to retrieve include directories from a project. - /// For each encountered path it will try to resolve the paths to absolute paths. - /// - /// Empty list if include directory retrieval failed. - public static List GetProjectIncludeDirectories(EnvDTE.Project project, bool endWithSeparator = true) - { - List pathStrings = new List(); - if (project == null) - return pathStrings; - - string projectIncludeDirectories; - try - { - projectIncludeDirectories = VCUtils.GetCompilerSetting_Includes(project); - } - catch (VCQueryFailure e) - { - Output.Instance.WriteLine(e.Message); - return pathStrings; - } - - ThreadHelper.ThrowIfNotOnUIThread(); - string projectPath = Path.GetDirectoryName(Path.GetFullPath(project.FileName)); - - // According to documentation FullIncludePath has resolved macros. - - pathStrings.AddRange(projectIncludeDirectories.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries)); - - for (int i = pathStrings.Count - 1; i >= 0; --i) - { - try - { - pathStrings[i] = pathStrings[i].Trim(); - if (!Path.IsPathRooted(pathStrings[i])) - { - pathStrings[i] = Path.Combine(projectPath, pathStrings[i]); - } - pathStrings[i] = Utils.GetExactPathName(Path.GetFullPath(pathStrings[i])); - - if (endWithSeparator) - pathStrings[i] += Path.DirectorySeparatorChar; - } - catch - { - pathStrings.RemoveAt(i); - } - } - return pathStrings; - } - - public static IWpfTextViewHost GetCurrentTextViewHost() - { - IVsTextManager textManager = Package.GetGlobalService(typeof (SVsTextManager)) as IVsTextManager; - - IVsTextView textView = null; - textManager.GetActiveView(1, null, out textView); - - var userData = textView as IVsUserData; - if (userData == null) - { - return null; - } - else - { - Guid guidViewHost = DefGuidList.guidIWpfTextViewHost; - object holder; - userData.GetData(ref guidViewHost, out holder); - var viewHost = (IWpfTextViewHost) holder; - - return viewHost; - } - } - - public static EnvDTE.Window OpenFileAndShowDocument(string filePath) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - if (filePath == null) - return null; - - var dte = VSUtils.GetDTE(); - EnvDTE.Window fileWindow = dte.ItemOperations.OpenFile(filePath); - if (fileWindow == null) - { - Output.Instance.WriteLine("Failed to open File {0}", filePath); - return null; - } - fileWindow.Activate(); - fileWindow.Visible = true; - - return fileWindow; - } - - public static string GetOutputText() - { - ThreadHelper.ThrowIfNotOnUIThread(); - var dte = GetDTE(); - if (dte == null) - return ""; - - - OutputWindowPane buildOutputPane = null; - foreach (OutputWindowPane pane in dte.ToolWindows.OutputWindow.OutputWindowPanes) - { - if (pane.Guid == VSConstants.OutputWindowPaneGuid.BuildOutputPane_string) - { - buildOutputPane = pane; - break; - } - } - if (buildOutputPane == null) - { - _ = Output.Instance.ErrorMsg("Failed to query for build output pane!"); - return null; - } - TextSelection sel = buildOutputPane.TextDocument.Selection; - - sel.StartOfDocument(false); - sel.EndOfDocument(true); - - return sel.Text; - } - } -} diff --git a/IncludeToolbox/license.txt b/IncludeToolbox/license.txt deleted file mode 100644 index 3ae5479..0000000 --- a/IncludeToolbox/license.txt +++ /dev/null @@ -1,9 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017-2019 Andreas Reich - -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: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -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. diff --git a/IncludeToolbox2019/IncludeToolbox2019.csproj b/IncludeToolbox2019/IncludeToolbox2019.csproj new file mode 100644 index 0000000..9d1fe75 --- /dev/null +++ b/IncludeToolbox2019/IncludeToolbox2019.csproj @@ -0,0 +1,147 @@ + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + latest + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {A81A5332-6A20-4F3B-90B4-E55985B9CF59} + Library + Properties + IncludeToolbox + IncludeToolbox2019 + v4.8 + true + true + true + true + false + true + true + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + False + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + False + + + + MyImageIds.vsct + True + True + + + + True + True + source.extension.vsixmanifest + + + + + Menus.ctmenu + VsctGenerator + VSCommandTable.cs + + + True + True + VSCommandTable.vsct + + + + + + + + + + + + License.txt + true + Always + + + true + + + Designer + VsixManifestGenerator + source.extension.cs + + + + + + + + + + + + 16.0.451 + + + + 16.10.31320.204 + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + 4.5.5 + + + + + + + + + + + Always + true + + + + + VsctGenerator + MyImageIds.cs + + + + + + + \ No newline at end of file diff --git a/IncludeToolbox2019/Monikers.imagemanifest b/IncludeToolbox2019/Monikers.imagemanifest new file mode 100644 index 0000000..ba2c5bf --- /dev/null +++ b/IncludeToolbox2019/Monikers.imagemanifest @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IncludeToolbox2019/MyImageIds.cs b/IncludeToolbox2019/MyImageIds.cs new file mode 100644 index 0000000..20fa28f --- /dev/null +++ b/IncludeToolbox2019/MyImageIds.cs @@ -0,0 +1,34 @@ +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer +// +// ------------------------------------------------------------------------------ +namespace IncludeToolbox +{ + using System; + + /// + /// Helper class that exposes all GUIDs used across VS Package. + /// + internal sealed partial class PackageGuids + { + public const string MonikersGuidString = "36358583-08b5-4870-b61e-13efcebfd341"; + public static Guid MonikersGuid = new Guid(MonikersGuidString); + } + /// + /// Helper class that encapsulates all CommandIDs uses across VS Package. + /// + internal sealed partial class PackageIds + { + public const int hash = 0x000C; + public const int IncludeFormatterIcons_0 = 0x0001; + public const int IncludeFormatterIcons_1 = 0x0002; + public const int IncludeFormatterIcons_2 = 0x0003; + public const int IncludeFormatterIcons_3 = 0x0004; + public const int IncludeFormatterPackage = 0x0005; + public const int IncludeGraphToolbarIcons_0 = 0x0006; + public const int IncludeGraphToolbarIcons_1 = 0x0007; + public const int IncludeFormatterIcons = 0x0000; + public const int IncludeGraphToolbarIcons = 0x0001; + } +} \ No newline at end of file diff --git a/IncludeToolbox2019/MyImageIds.vsct b/IncludeToolbox2019/MyImageIds.vsct new file mode 100644 index 0000000..b3f00dd --- /dev/null +++ b/IncludeToolbox2019/MyImageIds.vsct @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IncludeToolbox2019/Properties/AssemblyInfo.cs b/IncludeToolbox2019/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..69b7933 --- /dev/null +++ b/IncludeToolbox2019/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +using IncludeToolbox; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle(Vsix.Name)] +[assembly: AssemblyDescription(Vsix.Description)] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany(Vsix.Author)] +[assembly: AssemblyProduct(Vsix.Name)] +[assembly: AssemblyCopyright(Vsix.Author)] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: AssemblyVersion(Vsix.Version)] +[assembly: AssemblyFileVersion(Vsix.Version)] + +namespace System.Runtime.CompilerServices +{ + public class IsExternalInit { } +} \ No newline at end of file diff --git a/IncludeToolbox/Resources/IncludeFormatterIcons.png b/IncludeToolbox2019/Resources/IncludeFormatterIcons.png similarity index 100% rename from IncludeToolbox/Resources/IncludeFormatterIcons.png rename to IncludeToolbox2019/Resources/IncludeFormatterIcons.png diff --git a/IncludeToolbox2019/Resources/IncludeFormatterIcons_0.png b/IncludeToolbox2019/Resources/IncludeFormatterIcons_0.png new file mode 100644 index 0000000..1620c38 Binary files /dev/null and b/IncludeToolbox2019/Resources/IncludeFormatterIcons_0.png differ diff --git a/IncludeToolbox2019/Resources/IncludeFormatterIcons_1.png b/IncludeToolbox2019/Resources/IncludeFormatterIcons_1.png new file mode 100644 index 0000000..9cc4986 Binary files /dev/null and b/IncludeToolbox2019/Resources/IncludeFormatterIcons_1.png differ diff --git a/IncludeToolbox2019/Resources/IncludeFormatterIcons_2.png b/IncludeToolbox2019/Resources/IncludeFormatterIcons_2.png new file mode 100644 index 0000000..5194c02 Binary files /dev/null and b/IncludeToolbox2019/Resources/IncludeFormatterIcons_2.png differ diff --git a/IncludeToolbox2019/Resources/IncludeFormatterIcons_3.png b/IncludeToolbox2019/Resources/IncludeFormatterIcons_3.png new file mode 100644 index 0000000..18c76d8 Binary files /dev/null and b/IncludeToolbox2019/Resources/IncludeFormatterIcons_3.png differ diff --git a/IncludeToolbox2019/Resources/IncludeFormatterPackage.png b/IncludeToolbox2019/Resources/IncludeFormatterPackage.png new file mode 100644 index 0000000..de73e8d Binary files /dev/null and b/IncludeToolbox2019/Resources/IncludeFormatterPackage.png differ diff --git a/IncludeToolbox/Resources/IncludeGraphToolbarIcons.png b/IncludeToolbox2019/Resources/IncludeGraphToolbarIcons.png similarity index 100% rename from IncludeToolbox/Resources/IncludeGraphToolbarIcons.png rename to IncludeToolbox2019/Resources/IncludeGraphToolbarIcons.png diff --git a/IncludeToolbox2019/Resources/IncludeGraphToolbarIcons_0.png b/IncludeToolbox2019/Resources/IncludeGraphToolbarIcons_0.png new file mode 100644 index 0000000..2b66ea7 Binary files /dev/null and b/IncludeToolbox2019/Resources/IncludeGraphToolbarIcons_0.png differ diff --git a/IncludeToolbox2019/Resources/IncludeGraphToolbarIcons_1.png b/IncludeToolbox2019/Resources/IncludeGraphToolbarIcons_1.png new file mode 100644 index 0000000..dea3f17 Binary files /dev/null and b/IncludeToolbox2019/Resources/IncludeGraphToolbarIcons_1.png differ diff --git a/IncludeToolbox2019/Resources/hash.png b/IncludeToolbox2019/Resources/hash.png new file mode 100644 index 0000000..33218b4 Binary files /dev/null and b/IncludeToolbox2019/Resources/hash.png differ diff --git a/IncludeToolbox2019/VSCommandTable.cs b/IncludeToolbox2019/VSCommandTable.cs new file mode 100644 index 0000000..a2fac4e --- /dev/null +++ b/IncludeToolbox2019/VSCommandTable.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer +// +// ------------------------------------------------------------------------------ +namespace IncludeToolbox +{ + using System; + + /// + /// Helper class that exposes all GUIDs used across VS Package. + /// + internal sealed partial class PackageGuids + { + public const string IncludeToolboxString = "7473f721-f6c8-4e39-98c2-e08d79a89525"; + public static Guid IncludeToolbox = new Guid(IncludeToolboxString); + + public const string GHeaderOnlyString = "a34e853e-8679-4a07-918e-982a1b3b0a6b"; + public static Guid GHeaderOnly = new Guid(GHeaderOnlyString); + + public const string GOnlyVCString = "1175290a-3e8f-4718-868c-c08b5d2b09a7"; + public static Guid GOnlyVC = new Guid(GOnlyVCString); + } + /// + /// Helper class that encapsulates all CommandIDs uses across VS Package. + /// + internal sealed partial class PackageIds + { + public const int ContextMenuGroup = 0x0001; + public const int MapMenuGroup = 0x0002; + public const int IncludeGraphGroup = 0x0003; + public const int ItemContextGroup = 0x021A; + public const int ProjectContextGroup = 0x021B; + public const int IncludeWhatYouUseId = 0x0100; + public const int FormatIncludesId = 0x0101; + public const int TrialAndError = 0x0102; + public const int IWYUProjId = 0x0103; + public const int GenMap = 0x0104; + public const int RemMap = 0x0105; + public const int IncludeGraphId = 0x0106; + public const int IncludeGraphCodeId = 0x0107; + public const int CompileHeader = 0x0108; + public const int ProjectWideTrialAndErrorRemoval = 0x0110; + } +} \ No newline at end of file diff --git a/IncludeToolbox2019/VSCommandTable.vsct b/IncludeToolbox2019/VSCommandTable.vsct new file mode 100644 index 0000000..0b17cc1 --- /dev/null +++ b/IncludeToolbox2019/VSCommandTable.vsct @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IncludeToolbox2019/source.extension.cs b/IncludeToolbox2019/source.extension.cs new file mode 100644 index 0000000..1124384 --- /dev/null +++ b/IncludeToolbox2019/source.extension.cs @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer +// +// ------------------------------------------------------------------------------ +namespace IncludeToolbox +{ + internal sealed partial class Vsix + { + public const string Id = "IncludeToolbox.Andreas Reich.075c2e2b-7b71-45ba-b2e6-c1dadc81cfac"; + public const string Name = "Include Toolbox 2019"; + public const string Description = @"Various tools for managing C/C++ #includes: Formatting, sorting, exploring, pruning. VS2019 compatibility version. For VS2022 visit https://marketplace.visualstudio.com/items?itemName=Agrael.IncludeToolbox2022"; + public const string Language = "en-US"; + public const string Version = "3.2.67"; + public const string Author = "Wumpf"; + public const string Tags = "Include;IWYU;Include Formatting;Include Sorting;C++;C;Coding"; + } +} diff --git a/IncludeToolbox2019/source.extension.vsixmanifest b/IncludeToolbox2019/source.extension.vsixmanifest new file mode 100644 index 0000000..f40cf89 --- /dev/null +++ b/IncludeToolbox2019/source.extension.vsixmanifest @@ -0,0 +1,21 @@ + + + + + Include Toolbox 2019 + Various tools for managing C/C++ #includes: Formatting, sorting, exploring, pruning. VS2019 compatibility version. For VS2022 visit https://marketplace.visualstudio.com/items?itemName=Agrael.IncludeToolbox2022 + License.txt + Resources\IncludeFormatterPackage.png + Resources\IncludeFormatterPackage.png + Include;IWYU;Include Formatting;Include Sorting;C++;C;Coding + + + + + + + + + + + diff --git a/IncludeToolbox2022/IncludeToolbox2022.csproj b/IncludeToolbox2022/IncludeToolbox2022.csproj new file mode 100644 index 0000000..c3429f6 --- /dev/null +++ b/IncludeToolbox2022/IncludeToolbox2022.csproj @@ -0,0 +1,146 @@ + + + + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + latest + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {7D29CECE-07D3-4417-9D63-1362852F18F3} + Library + Properties + IncludeToolbox + IncludeToolbox2022 + v4.8 + true + true + true + true + false + true + true + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + True + True + MyImageIds.vsct + + + + True + True + source.extension.vsixmanifest + + + + + Menus.ctmenu + VsctGenerator + VSCommandTable.cs + + + True + True + VSCommandTable.vsct + + + + + true + + + + + VsctGenerator + MyImageIds.cs + + + + + + + + + Always + true + + + + Designer + VsixManifestGenerator + source.extension.cs + + + + + + + + + + + + + + + + + + + compile; build; native; contentfiles; analyzers; buildtransitive + + + 17.3.32804.24 + + + 17.3.32804.24 + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + License.txt + true + Always + + + + + + + \ No newline at end of file diff --git a/IncludeToolbox2022/Monikers.imagemanifest b/IncludeToolbox2022/Monikers.imagemanifest new file mode 100644 index 0000000..01cfa8c --- /dev/null +++ b/IncludeToolbox2022/Monikers.imagemanifest @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IncludeToolbox2022/MyImageIds.cs b/IncludeToolbox2022/MyImageIds.cs new file mode 100644 index 0000000..ee4c73e --- /dev/null +++ b/IncludeToolbox2022/MyImageIds.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer +// +// ------------------------------------------------------------------------------ +namespace IncludeToolbox +{ + using System; + + /// + /// Helper class that exposes all GUIDs used across VS Package. + /// + internal sealed partial class PackageGuids + { + public const string MonikersGuidString = "41e64718-3ed1-4ace-aeb4-fbe550d81000"; + public static Guid MonikersGuid = new Guid(MonikersGuidString); + } + /// + /// Helper class that encapsulates all CommandIDs uses across VS Package. + /// + internal sealed partial class PackageIds + { + public const int hash = 0x000C; + public const int include13 = 0x0001; + public const int include16 = 0x0002; + public const int IncludeFormatterIcons_0 = 0x0003; + public const int IncludeFormatterIcons_1 = 0x0004; + public const int IncludeFormatterIcons_2 = 0x0005; + public const int IncludeFormatterIcons_3 = 0x0006; + public const int IncludeFormatterPackage = 0x0007; + public const int IncludeGraphToolbarIcons_0 = 0x0008; + public const int IncludeGraphToolbarIcons_1 = 0x0009; + public const int IncludeFormatterIcons = 0x000A; + public const int IncludeGraphToolbarIcons = 0x000B; + } +} \ No newline at end of file diff --git a/IncludeToolbox2022/MyImageIds.vsct b/IncludeToolbox2022/MyImageIds.vsct new file mode 100644 index 0000000..f3834c2 --- /dev/null +++ b/IncludeToolbox2022/MyImageIds.vsct @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IncludeToolbox2022/Properties/AssemblyInfo.cs b/IncludeToolbox2022/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..69b7933 --- /dev/null +++ b/IncludeToolbox2022/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +using IncludeToolbox; +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle(Vsix.Name)] +[assembly: AssemblyDescription(Vsix.Description)] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany(Vsix.Author)] +[assembly: AssemblyProduct(Vsix.Name)] +[assembly: AssemblyCopyright(Vsix.Author)] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: AssemblyVersion(Vsix.Version)] +[assembly: AssemblyFileVersion(Vsix.Version)] + +namespace System.Runtime.CompilerServices +{ + public class IsExternalInit { } +} \ No newline at end of file diff --git a/IncludeToolbox2022/Resources/IncludeFormatterIcons.png b/IncludeToolbox2022/Resources/IncludeFormatterIcons.png new file mode 100644 index 0000000..1b5a301 Binary files /dev/null and b/IncludeToolbox2022/Resources/IncludeFormatterIcons.png differ diff --git a/IncludeToolbox2022/Resources/IncludeFormatterIcons_0.png b/IncludeToolbox2022/Resources/IncludeFormatterIcons_0.png new file mode 100644 index 0000000..1620c38 Binary files /dev/null and b/IncludeToolbox2022/Resources/IncludeFormatterIcons_0.png differ diff --git a/IncludeToolbox2022/Resources/IncludeFormatterIcons_1.png b/IncludeToolbox2022/Resources/IncludeFormatterIcons_1.png new file mode 100644 index 0000000..9cc4986 Binary files /dev/null and b/IncludeToolbox2022/Resources/IncludeFormatterIcons_1.png differ diff --git a/IncludeToolbox2022/Resources/IncludeFormatterIcons_2.png b/IncludeToolbox2022/Resources/IncludeFormatterIcons_2.png new file mode 100644 index 0000000..5194c02 Binary files /dev/null and b/IncludeToolbox2022/Resources/IncludeFormatterIcons_2.png differ diff --git a/IncludeToolbox2022/Resources/IncludeFormatterIcons_3.png b/IncludeToolbox2022/Resources/IncludeFormatterIcons_3.png new file mode 100644 index 0000000..18c76d8 Binary files /dev/null and b/IncludeToolbox2022/Resources/IncludeFormatterIcons_3.png differ diff --git a/IncludeToolbox2022/Resources/IncludeFormatterPackage.png b/IncludeToolbox2022/Resources/IncludeFormatterPackage.png new file mode 100644 index 0000000..de73e8d Binary files /dev/null and b/IncludeToolbox2022/Resources/IncludeFormatterPackage.png differ diff --git a/IncludeToolbox2022/Resources/IncludeGraphToolbarIcons.png b/IncludeToolbox2022/Resources/IncludeGraphToolbarIcons.png new file mode 100644 index 0000000..8a857ba Binary files /dev/null and b/IncludeToolbox2022/Resources/IncludeGraphToolbarIcons.png differ diff --git a/IncludeToolbox2022/Resources/IncludeGraphToolbarIcons_0.png b/IncludeToolbox2022/Resources/IncludeGraphToolbarIcons_0.png new file mode 100644 index 0000000..2b66ea7 Binary files /dev/null and b/IncludeToolbox2022/Resources/IncludeGraphToolbarIcons_0.png differ diff --git a/IncludeToolbox2022/Resources/IncludeGraphToolbarIcons_1.png b/IncludeToolbox2022/Resources/IncludeGraphToolbarIcons_1.png new file mode 100644 index 0000000..dea3f17 Binary files /dev/null and b/IncludeToolbox2022/Resources/IncludeGraphToolbarIcons_1.png differ diff --git a/IncludeToolbox2022/Resources/hash.png b/IncludeToolbox2022/Resources/hash.png new file mode 100644 index 0000000..33218b4 Binary files /dev/null and b/IncludeToolbox2022/Resources/hash.png differ diff --git a/IncludeToolbox2022/VSCommandTable.cs b/IncludeToolbox2022/VSCommandTable.cs new file mode 100644 index 0000000..f244e28 --- /dev/null +++ b/IncludeToolbox2022/VSCommandTable.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer +// +// ------------------------------------------------------------------------------ +namespace IncludeToolbox +{ + using System; + + /// + /// Helper class that exposes all GUIDs used across VS Package. + /// + internal sealed partial class PackageGuids + { + public const string IncludeToolboxString = "2e77f2e4-5f04-4052-8e63-ca2b41cd0315"; + public static Guid IncludeToolbox = new Guid(IncludeToolboxString); + + public const string GHeaderOnlyString = "a34e853e-8679-4a07-918e-982a1b3b0a6b"; + public static Guid GHeaderOnly = new Guid(GHeaderOnlyString); + + public const string GOnlyVCString = "1175290a-3e8f-4718-868c-c08b5d2b09a7"; + public static Guid GOnlyVC = new Guid(GOnlyVCString); + } + /// + /// Helper class that encapsulates all CommandIDs uses across VS Package. + /// + internal sealed partial class PackageIds + { + public const int ContextMenuGroup = 0x0001; + public const int MapMenuGroup = 0x0002; + public const int IncludeGraphGroup = 0x0003; + public const int ItemContextGroup = 0x021A; + public const int ProjectContextGroup = 0x021B; + public const int IncludeWhatYouUseId = 0x0100; + public const int FormatIncludesId = 0x0101; + public const int TrialAndError = 0x0102; + public const int IWYUProjId = 0x0103; + public const int GenMap = 0x0104; + public const int RemMap = 0x0105; + public const int IncludeGraphId = 0x0106; + public const int IncludeGraphCodeId = 0x0107; + public const int CompileHeader = 0x0108; + public const int ProjectWideTrialAndErrorRemoval = 0x0110; + } +} \ No newline at end of file diff --git a/IncludeToolbox2022/VSCommandTable.vsct b/IncludeToolbox2022/VSCommandTable.vsct new file mode 100644 index 0000000..d7f67cc --- /dev/null +++ b/IncludeToolbox2022/VSCommandTable.vsct @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IncludeToolbox2022/source.extension.cs b/IncludeToolbox2022/source.extension.cs new file mode 100644 index 0000000..c6ca726 --- /dev/null +++ b/IncludeToolbox2022/source.extension.cs @@ -0,0 +1,18 @@ +// ------------------------------------------------------------------------------ +// +// This file was generated by VSIX Synchronizer +// +// ------------------------------------------------------------------------------ +namespace IncludeToolbox +{ + internal sealed partial class Vsix + { + public const string Id = "IncludeToolbox2022.d3cba4fe-8d65-479b-8436-18d743ee7b53"; + public const string Name = "Include Toolbox 2022"; + public const string Description = @"Various tools for managing C/C++ #includes: Formatting, sorting, exploring, pruning."; + public const string Language = "en-US"; + public const string Version = "3.2.67"; + public const string Author = "Agrael"; + public const string Tags = "Include;IWYU;Include Formatting;Include Sorting;C++;C;Coding"; + } +} diff --git a/IncludeToolbox2022/source.extension.vsixmanifest b/IncludeToolbox2022/source.extension.vsixmanifest new file mode 100644 index 0000000..57ed73a --- /dev/null +++ b/IncludeToolbox2022/source.extension.vsixmanifest @@ -0,0 +1,23 @@ + + + + + Include Toolbox 2022 + Various tools for managing C/C++ #includes: Formatting, sorting, exploring, pruning. + License.txt + Resources\IncludeFormatterPackage.png + Resources\IncludeFormatterPackage.png + Include;IWYU;Include Formatting;Include Sorting;C++;C;Coding + + + + amd64 + + + + + + + + + diff --git a/IncludeToolboxShared/Commands/CompileHeader.cs b/IncludeToolboxShared/Commands/CompileHeader.cs new file mode 100644 index 0000000..9996f0d --- /dev/null +++ b/IncludeToolboxShared/Commands/CompileHeader.cs @@ -0,0 +1,58 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.VCProjectEngine; +using System.IO; +using System.Threading.Tasks; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox.Commands +{ + [Command(PackageIds.CompileHeader)] + internal sealed class CompileHeader : BaseCommand + { + static string support_cpp_path = null; + + protected override Task InitializeCompletedAsync() + { + Command.Supported = false; + support_cpp_path = Path.ChangeExtension(Path.GetTempFileName(), ".cpp"); + return Task.CompletedTask; + } + + private async Task TestCompileAsync(VCFileConfiguration config) + { + using AsyncDispatcher dispatcher = new(); + return await dispatcher.CompileAsync(config); + } + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + var x = await VS.Solutions.GetActiveItemAsync(); + + if ((await x.ToVCProjectItemAsync()) is not VCFile file) + return; + + _ = Output.WriteLineAsync($"Starting Trial And Error Include removal on header file {file.FullPath}"); + string xout = ""; + + var pch = VCUtil.GetPCH((VCProject)file.project); + if (!string.IsNullOrEmpty(pch)) + xout = "#include \"" + pch + "\"\n"; + + File.WriteAllText(support_cpp_path, xout + "#include \"" + file.FullPath + "\""); + + var proj = await VS.Solutions.GetActiveProjectAsync(); + var vc = await proj.ToVCProjectAsync(); + var xfile = (VCFile)vc.AddFile(support_cpp_path); + using TempGuard tg = new(xfile); + + VCFileConfiguration config = VCUtil.GetVCFileConfig(xfile); + if (config != null) + { + await TestCompileAsync(config); + return; + } + _ = Output.WriteLineAsync($"{xfile.Name} has failed to yield a config."); + } + } +} \ No newline at end of file diff --git a/IncludeToolboxShared/Commands/FormatIncludes.cs b/IncludeToolboxShared/Commands/FormatIncludes.cs new file mode 100644 index 0000000..1eb5475 --- /dev/null +++ b/IncludeToolboxShared/Commands/FormatIncludes.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Task = System.Threading.Tasks.Task; + + +namespace IncludeToolbox.Commands +{ + [Command(PackageIds.FormatIncludesId)] + internal sealed class FormatIncludes : BaseCommand + { + SnapshotSpan GetSelectionLines(IWpfTextView viewHost) + { + if (viewHost == null) return new SnapshotSpan(); + var sel = viewHost.Selection.StreamSelectionSpan; + var start = new SnapshotPoint(viewHost.TextSnapshot, sel.Start.Position).GetContainingLine().Start; + var end = new SnapshotPoint(viewHost.TextSnapshot, sel.End.Position).GetContainingLine().EndIncludingLineBreak; + + return new SnapshotSpan(start, end); + } + async Task GetSelectionLinesAsync() + { + IWpfTextView viewHost = (await VS.Documents.GetActiveDocumentViewAsync())?.TextView; + return GetSelectionLines(viewHost); + } + + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Has to be synchronous")] + protected override void BeforeQueryStatus(EventArgs e) + { + var selection_span = GetSelectionLinesAsync().Result; + var lines = Parser.ParseInclues(selection_span.GetText().AsSpan(), false);// faster a times! tested with QPC + Command.Visible = lines.Count() != 0; + } + + + + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs args) + { + var settings = await FormatOptions.GetLiveInstanceAsync(); + var doc = await VS.Documents.GetActiveDocumentViewAsync(); + // Read. + var selection_span = await GetSelectionLinesAsync(); + var include_directories = await VCUtil.GetIncludeDirsAsync(); + var text = selection_span.GetText(); + // Format + var formated_lines = Formatter.IncludeFormatter.FormatIncludes(text.AsSpan(), doc.FilePath, include_directories, settings); + + // Overwrite. + Formatter.IncludeFormatter.ApplyChanges(formated_lines, doc, text, selection_span.Start, settings.RemoveEmptyLines); + } + } +} diff --git a/IncludeToolboxShared/Commands/GenMap.cs b/IncludeToolboxShared/Commands/GenMap.cs new file mode 100644 index 0000000..865a1c9 --- /dev/null +++ b/IncludeToolboxShared/Commands/GenMap.cs @@ -0,0 +1,64 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using System.Linq; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox +{ + [Command(PackageIds.GenMap)] + internal sealed class GenMap : BaseCommand + { + protected override Task InitializeCompletedAsync() + { + Command.Supported = false; + return Task.CompletedTask; + } + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + var settings = await MapperOptions.GetLiveInstanceAsync(); + if (settings.MapFile == "") { VS.MessageBox.ShowErrorAsync("Map output error", "Map output file is empty, go to Tools->Options->Include Minimizer->General and set the output file!").FireAndForget(); return; } + var doc = await VS.Documents.GetActiveDocumentViewAsync(); + if (doc == null) return; + + var path = doc.FilePath; + var relative_path = settings.Prefix != "" ? Utils.MakeRelative(settings.Prefix, path) : path; + relative_path = relative_path.Replace('\\', '/'); + + + var snap = doc.TextBuffer.CurrentSnapshot; + var text = snap.GetText(); + + var sresult = Parser.ParseInclues(Utils.GetIncludeSpanRO(text), settings.Ignoreifdefs) + .Distinct(); + + string file_map = ""; + switch (settings.Preference) + { + case MappingPreference.Quotes: + file_map = string.Format("\t{{ include: [ \"<{0}>\", private, \"\\\"{0}\\\"\", public ] }},\n", relative_path); + break; + case MappingPreference.AngleBrackets: + file_map = string.Format("\t{{ include: [ \"\\\"{0}\\\"\", private, \"<{0}>\", public ] }},\n", relative_path); + break; + default: + break; + } + + foreach (var match in sresult) + switch (settings.Preference) + { + case MappingPreference.Quotes: + file_map += string.Format("\t{{ include: [ \"{0}\", public, \"\\\"{1}\\\"\", public ] }},\n", match.FullFile.Replace('\\', '/'), relative_path); + break; + default: + case MappingPreference.AngleBrackets: + file_map += string.Format("\t{{ include: [ \"{0}\", public, \"<{1}>\", public ] }},\n", match.FullFile.Replace('\\', '/'), relative_path); + break; + } + settings.Map.Map[relative_path] = file_map; + + settings.Map.WriteMapAsync(settings).FireAndForget(); + } + } +} diff --git a/IncludeToolboxShared/Commands/IncludeGraph.cs b/IncludeToolboxShared/Commands/IncludeGraph.cs new file mode 100644 index 0000000..a9440cd --- /dev/null +++ b/IncludeToolboxShared/Commands/IncludeGraph.cs @@ -0,0 +1,15 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox.Commands +{ + [Command(PackageIds.IncludeGraphId)] + internal sealed class IncludeGraph : BaseCommand + { + protected override Task ExecuteAsync(OleMenuCmdEventArgs e) + { + return IncludeGraphToolWindow.ShowAsync(); + } + } +} diff --git a/IncludeToolboxShared/Commands/IncludeGraph_CodeWindow.cs b/IncludeToolboxShared/Commands/IncludeGraph_CodeWindow.cs new file mode 100644 index 0000000..6aa6ab1 --- /dev/null +++ b/IncludeToolboxShared/Commands/IncludeGraph_CodeWindow.cs @@ -0,0 +1,26 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox.Commands +{ + [Command(PackageIds.IncludeGraphCodeId)] + internal class IncludeGraph_CodeWindow : BaseCommand + { + protected override Task InitializeCompletedAsync() + { + Command.Supported = false; + return Task.CompletedTask; + } + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + var pane = await IncludeGraphToolWindow.ShowAsync(); + var cont = (IncludeGraphControl)pane.Content; + var context = (IncludeGraphViewModel)cont.DataContext; + var file = await VS.Documents.GetActiveDocumentViewAsync(); + + context.RecalculateForAsync(file).FireAndForget(); + } + } +} diff --git a/IncludeToolboxShared/Commands/RemoveMap.cs b/IncludeToolboxShared/Commands/RemoveMap.cs new file mode 100644 index 0000000..147dcb1 --- /dev/null +++ b/IncludeToolboxShared/Commands/RemoveMap.cs @@ -0,0 +1,33 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using System.Threading.Tasks; +using System; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox +{ + [Command(PackageIds.RemMap)] + internal sealed class RemoveMap : BaseCommand + { + protected override Task InitializeCompletedAsync() + { + return base.InitializeCompletedAsync(); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Has to be synchronous")] + protected override void BeforeQueryStatus(EventArgs e) + { + Command.Visible = MapperOptions.Instance.IsInMapAsync().Result; + } + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + var settings = await MapperOptions.GetLiveInstanceAsync(); + var doc = await VS.Documents.GetActiveDocumentViewAsync(); + var file = Utils.MakeRelative(settings.Prefix, doc.FilePath).Replace('\\', '/'); + + settings.Map.TryRemoveValue(file); + settings.Map.WriteMapAsync(settings).FireAndForget(); + } + } +} diff --git a/IncludeToolboxShared/Commands/RunIWYU.cs b/IncludeToolboxShared/Commands/RunIWYU.cs new file mode 100644 index 0000000..eee88cf --- /dev/null +++ b/IncludeToolboxShared/Commands/RunIWYU.cs @@ -0,0 +1,102 @@ +using Community.VisualStudio.Toolkit; +using IncludeToolbox.IncludeWhatYouUse; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using System; +using File = System.IO.File; +using Task = System.Threading.Tasks.Task; + + + +namespace IncludeToolbox.Commands +{ + internal class CancelCallback : IVsThreadedWaitDialogCallback + { + public delegate void Cancel(); + public Cancel cancel; + public CancelCallback(Cancel cancel) + { + this.cancel = cancel; + } + + void IVsThreadedWaitDialogCallback.OnCanceled() + { + cancel(); + } + } + + + [Command(PackageIds.IncludeWhatYouUseId)] + internal sealed class RunIWYU : BaseCommand + { + IWYU proc = new(); + CancelCallback cancelCallback; + + + protected override async Task InitializeCompletedAsync() + { + Command.Supported = false; + cancelCallback = new(delegate { proc.CancelAsync().FireAndForget(); }); + var settings = await IWYUOptions.GetLiveInstanceAsync(); + settings.OnChange += proc.BuildCommandLine; + proc.BuildCommandLine(settings); + } + + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var settings = await IWYUOptions.GetLiveInstanceAsync(); + var dlg = (IVsThreadedWaitDialogFactory)await VS.Services.GetThreadedWaitDialogAsync(); + + if ((settings.Executable == "" || !File.Exists(settings.Executable) || await settings.DownloadRequiredAsync()) + && !await IWYUDownload.DownloadAsync(dlg, settings)) + { + VS.MessageBox.ShowErrorAsync("IWYU Error", "No executable found, operation cannot be completed").FireAndForget(); + return; + } + + await VS.Commands.ExecuteAsync(KnownCommands.File_SaveSelectedItems); + + var doc = await VS.Documents.GetActiveDocumentViewAsync(); + if (doc == null) return; + if (settings.IgnoreHeader) IWYU.MoveHeader(doc); + var buf = doc.TextBuffer; + var str = buf.CurrentSnapshot.GetText(); + + + var x = Parser.ParseAsync(str); + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + dlg.CreateInstance(out IVsThreadedWaitDialog2 xdialog); + IVsThreadedWaitDialog4 dialog = xdialog as IVsThreadedWaitDialog4; + + dialog.StartWaitDialogWithCallback("Include Toolbox", "Running include-what-you-use", null, null, "Running include-what-you-use", true, 0, true, 0, 0, cancelCallback); + + bool result = false; + try + { + result = await proc.StartAsync(doc.FilePath, settings.AlwaysRebuid); + } + catch (Exception ex) + { + _ = Output.WriteLineAsync("IWYU Failed with error:" + ex.Message); + } + + if (dialog.EndWaitDialog() || result == false) return; + + if (settings.Sub == Substitution.Precise) + await IWYUApply.ApplyPreciseAsync(settings, await x, proc.ProcOutput, VCUtil.Std); + else + await IWYUApply.ApplyAsync(settings, proc.ProcOutput); + + + if (settings.RemoveENS) + IWYUApply.ClearNamespaces(doc); + if (settings.Format) + await IWYUApply.FormatAsync(doc); + if (settings.FormatDoc) + await VS.Commands.ExecuteAsync(Microsoft.VisualStudio.VSConstants.VSStd2KCmdID.FORMATDOCUMENT); + } + } +} diff --git a/IncludeToolboxShared/Commands/RunIWYUProject.cs b/IncludeToolboxShared/Commands/RunIWYUProject.cs new file mode 100644 index 0000000..3d12c41 --- /dev/null +++ b/IncludeToolboxShared/Commands/RunIWYUProject.cs @@ -0,0 +1,159 @@ +using Community.VisualStudio.Toolkit; +using IncludeToolbox.IncludeWhatYouUse; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using File = System.IO.File; +using Task = System.Threading.Tasks.Task; + + + +namespace IncludeToolbox.Commands +{ + [Command(PackageIds.IWYUProjId)] + internal sealed class RunIWYUProject : BaseCommand + { + static readonly Regex extension = new("^\\.[ch](?:pp|xx)?$"); + IWYU task = new(); + CancelCallback cancelCallback; + + protected override async Task InitializeCompletedAsync() + { + cancelCallback = new(() => { task.CancelAsync().FireAndForget(); }); + var settings = await IWYUOptions.GetLiveInstanceAsync(); + settings.OnChange += task.BuildCommandLine; + task.BuildCommandLine(settings); + } + + + async Task CheckAsync() + { + Command.Visible = false; + var items = await VS.Solutions.GetActiveItemsAsync(); + + HashSet set = new(); + + bool b = items.All(s => + { + if (s.Type == SolutionItemType.Project) + { _ = set.Add((Project)s); return true; } + if (s.Type != SolutionItemType.PhysicalFile) return false; + var parent = s.FindParent(SolutionItemType.Project); + if (parent == null) return false; + _ = set.Add((Project)parent); + return extension.IsMatch(Path.GetExtension(s.Name)); + }); + + if (!b) return; + + foreach (var item in set) + { + if (await item.ToVCProjectAsync() == null) + return; + } + Command.Visible = true; + } + + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Has to be synchronous")] + protected override void BeforeQueryStatus(EventArgs e) + { + CheckAsync().Wait(); + } + + Dictionary>> SetTasks(IEnumerable items) + { + Dictionary>> set = new(); + foreach (var item in items) + { + if (item.Type == SolutionItemType.PhysicalFile) + { + var parent = (Project)item.FindParent(SolutionItemType.Project); + var hash = parent.FullPath; + if (!set.ContainsKey(hash)) + set[hash] = new KeyValuePair>(parent, new List()); + set[hash].Value.Add(item.Name); + } + } + return set; + } + + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + var set = SetTasks(await VS.Solutions.GetActiveItemsAsync()); + var settings = await IWYUOptions.GetLiveInstanceAsync(); + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var dlg = (IVsThreadedWaitDialogFactory)await VS.Services.GetThreadedWaitDialogAsync(); + + if ((settings.Executable == "" || !File.Exists(settings.Executable) || await settings.DownloadRequiredAsync()) + && !await IWYUDownload.DownloadAsync(dlg, settings)) + { + VS.MessageBox.ShowErrorAsync("IWYU Error", "No executable found, operation cannot be completed").FireAndForget(); + return; + } + + _ = dlg.CreateInstance(out IVsThreadedWaitDialog2 xdialog); + IVsThreadedWaitDialog4 dialog = xdialog as IVsThreadedWaitDialog4; + + try + { + dialog.StartWaitDialogWithCallback("Include Toolbox", "Running include-what-you-use", null, null, "Running include-what-you-use", true, 0, true, set.Count, 0, cancelCallback); + + // needs parallelization, but cancellation is doomed + foreach (var v in set) + { + for (int c = 0; c < v.Value.Value.Count;) + { + string f = v.Value.Value[c]; + dialog.UpdateProgress("Working with project:", + $"Running IWYU - Working with {v.Value.Key.Name}; File: {f}", + $"Running IWYU - Working with {v.Value.Key.Name}", + ++c, + v.Value.Value.Count, + false, + out var cancelled); + + var doc = await VS.Documents.OpenAsync(f); + if (doc == null) return; + if (settings.IgnoreHeader) IWYU.MoveHeader(doc); + var buf = doc.TextBuffer; + var str = buf.CurrentSnapshot.GetText(); + await VS.Commands.ExecuteAsync(KnownCommands.File_SaveAll); + + var x = Parser.ParseAsync(str); + + bool result = await task.StartAsync(f, v.Value.Key, true); // process cannot be rerun + + if (cancelled || result == false) return; + + if (settings.Sub == Substitution.Precise) + await IWYUApply.ApplyPreciseAsync(settings, await x, task.ProcOutput, VCUtil.Std); + else + await IWYUApply.ApplyAsync(settings, task.ProcOutput); + + if (settings.RemoveENS) + IWYUApply.ClearNamespaces(doc); + if (settings.Format) + await IWYUApply.FormatAsync(doc); + if (settings.FormatDoc) + { + await doc.WindowFrame.ShowAsync(); + await VS.Commands.ExecuteAsync(Microsoft.VisualStudio.VSConstants.VSStd2KCmdID.FORMATDOCUMENT); + } + } + } + } + catch (Exception ex) + { + VS.MessageBox.ShowErrorAsync(ex.Message).FireAndForget(); + } + _ = dialog.EndWaitDialog(); + } + } +} diff --git a/IncludeToolboxShared/Commands/TrialAndErrorRemoval_CodeWindow.cs b/IncludeToolboxShared/Commands/TrialAndErrorRemoval_CodeWindow.cs new file mode 100644 index 0000000..e8915df --- /dev/null +++ b/IncludeToolboxShared/Commands/TrialAndErrorRemoval_CodeWindow.cs @@ -0,0 +1,104 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.VCProjectEngine; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox.Commands +{ + internal sealed class TempGuard : IDisposable + { + readonly VCFile file; + private bool disposedValue = false; + + private void Dispose() + { + if (!disposedValue) + { + file.Remove(); + disposedValue = true; + } + } + public TempGuard(VCFile file) + { + this.file = file; + } + + ~TempGuard() + { + Dispose(); + } + + void IDisposable.Dispose() + { + Dispose(); + GC.SuppressFinalize(this); + } + } + + + [Command(PackageIds.TrialAndError)] + internal sealed class TrialAndErrorRemoval_CodeWindow : BaseCommand + { + TrialAndErrorRemoval impl = new(); + static string support_cpp_path = null; + + protected override Task InitializeCompletedAsync() + { + Command.Supported = false; + support_cpp_path = Path.ChangeExtension(Path.GetTempFileName(), ".cpp"); + return Task.CompletedTask; + } + + async Task TAERHeaderAsync(VCFile file) + { + _ = Output.WriteLineAsync($"Starting Trial And Error Include removal on header file {file.FullPath}"); + string xout = ""; + + var pch = VCUtil.GetPCH((VCProject)file.project); + if (!string.IsNullOrEmpty(pch)) + xout = "#include \"" + pch + "\"\n"; + + File.WriteAllText(support_cpp_path, xout + "#include \"" + file.FullPath + "\""); + + var proj = await VS.Solutions.GetActiveProjectAsync(); + var vc = await proj.ToVCProjectAsync(); + var xfile = (VCFile)vc.AddFile(support_cpp_path); + using TempGuard tg = new(xfile); + + return await impl.StartHeaderAsync(file, xfile, await TrialAndErrorRemovalOptions.GetLiveInstanceAsync()); + } + async Task TAERCodeAsync(VCFile file) + { + _ = Output.WriteLineAsync($"Starting Trial And Error Include removal on {file.FullPath}"); + return await impl.StartAsync(file, await TrialAndErrorRemovalOptions.GetLiveInstanceAsync()); + } + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + var x = await VS.Solutions.GetActiveItemAsync(); + + if ((await x.ToVCProjectItemAsync()) is not VCFile file) + return; + string err = ""; + + switch (file.FileType) + { + case eFileType.eFileTypeCppCode: + err = await TAERCodeAsync(file); + break; + case eFileType.eFileTypeCppHeader: + err = await TAERHeaderAsync(file); + break; + default: + break; + } + + if (string.IsNullOrEmpty(err)) return; + _ = Output.WriteLineAsync(err); + } + } +} \ No newline at end of file diff --git a/IncludeToolboxShared/Commands/TrialAndErrorRemoval_Project.cs b/IncludeToolboxShared/Commands/TrialAndErrorRemoval_Project.cs new file mode 100644 index 0000000..61011f0 --- /dev/null +++ b/IncludeToolboxShared/Commands/TrialAndErrorRemoval_Project.cs @@ -0,0 +1,94 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.VCProjectEngine; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Project = Community.VisualStudio.Toolkit.Project; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox.Commands +{ + internal class TAERDispatcher + { + public Queue files = new(); + public int numTotalRemovedIncludes = 0; + readonly TrialAndErrorRemoval impl = new(); + + + public async Task FindFilesAsync(Project project) + { + var vcproj = await project.ToVCProjectAsync(); + var xfiles = (IVCCollection)vcproj.Files; + + foreach (var item in xfiles) + { + if (item is not VCFile file || file.FileType != eFileType.eFileTypeCppCode) + continue; + files.Enqueue(file); + } + } + + public async Task ProcessAsync() + { + foreach (var item in files) + { + _ = Output.WriteLineAsync($"\nStarting Trial And Error Include removal on {item.FullPath}"); + string err = await impl.StartAsync(item, await TrialAndErrorRemovalOptions.GetLiveInstanceAsync()); + if (string.IsNullOrEmpty(err)) continue; + _ = Output.WriteLineAsync(err); + } + _ = Output.WriteLineAsync($"\nTrial And Error Over project removed {impl.Removed} includes in total."); + } + } + + + [Command(PackageIds.ProjectWideTrialAndErrorRemoval)] + internal sealed class TrialAndErrorRemoval_Project : BaseCommand + { + protected override Task InitializeCompletedAsync() + { + return Task.CompletedTask; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Has to be synchronous")] + protected override void BeforeQueryStatus(EventArgs e) + { + var project = GetSelectedVCProjectAsync().Result; + Command.Visible = project != null; + } + + + static async Task GetSelectedVCProjectAsync() + { + var selection = await VS.Solutions.GetActiveItemsAsync(); + foreach (Project item in selection.Where(i => i is Project)) + { + var vcp = await item.ToVCProjectAsync(); + if (vcp != null) return vcp; + } + return null; + } + + + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + if (TrialAndErrorRemoval.WorkInProgress) + { + await VS.MessageBox.ShowErrorAsync("Trial and error include removal already in progress!"); + return; + } + + var proj = await VS.Solutions.GetActiveProjectAsync(); + if (proj == null) return; + + if (!await VS.MessageBox.ShowConfirmAsync("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?")) + return; + + TAERDispatcher dispatcher = new(); + await dispatcher.FindFilesAsync(proj); + await dispatcher.ProcessAsync(); + } + } +} diff --git a/IncludeToolboxShared/FolderIncludeTreeItem.cs b/IncludeToolboxShared/FolderIncludeTreeItem.cs new file mode 100644 index 0000000..1524a70 --- /dev/null +++ b/IncludeToolboxShared/FolderIncludeTreeItem.cs @@ -0,0 +1,227 @@ +using IncludeToolbox.Graph; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IncludeToolbox.GraphWindow +{ + public class FolderIncludeTreeViewItem_Root : IncludeTreeViewItem + { + IReadOnlyCollection graphItems; + IncludeGraph.Item includingFile; + + public override IReadOnlyList Children + { + get + { + if (cachedItems == null) + GenerateChildItems(); + return cachedItems; + } + } + protected IReadOnlyList cachedItems; + + + public FolderIncludeTreeViewItem_Root(IReadOnlyCollection graphItems, IncludeGraph.Item includingFile) + { + this.graphItems = graphItems; + this.includingFile = includingFile; + this.Name = "Root"; + this.AbsoluteFilename = "Root"; + } + + public void Reset(IReadOnlyCollection graphItems, IncludeGraph.Item includingFile) + { + this.graphItems = graphItems; + this.includingFile = includingFile; + this.cachedItems = null; + NotifyAllPropertiesChanged(); + } + + private void GenerateChildItems() + { + if (graphItems == null) + { + cachedItems = emptyList; + return; + } + + var rootChildren = new List(); + cachedItems = rootChildren; + + // Create first layer of folder and leaf items + var leafItems = new List(); + foreach (IncludeGraph.Item item in graphItems) + { + if (item == includingFile) + continue; + + leafItems.Add(new FolderIncludeTreeViewItem_Leaf(item)); + } + + // Group by folder. + if (leafItems.Count > 0) + { + leafItems.Sort((x, y) => x.ParentFolder.CompareTo(y.ParentFolder)); + + var root = new FolderIncludeTreeViewItem_Folder("", 0); + GroupIncludeRecursively(root, leafItems, 0, leafItems.Count, 0); + rootChildren.AddRange(root.ChildrenList); + } + } + + private string GetNextPathPrefix(string path, int begin) + { + int nextSlash = begin; + while (path.Length > nextSlash && path[nextSlash] != Path.DirectorySeparatorChar) + ++nextSlash; + return path.Substring(0, nextSlash); + } + + private string LargestCommonFolderPrefixInRange(List allLeafItems, int begin, int end, string commonPrefix) + { + string folderBegin = allLeafItems[begin].ParentFolder; + string folderEnd = allLeafItems[end-1].ParentFolder; + + string prefixCandidate = commonPrefix; + string previousPrefix = null; + do + { + if (folderBegin.Length == prefixCandidate.Length) + return prefixCandidate; + + previousPrefix = prefixCandidate; + prefixCandidate = GetNextPathPrefix(folderBegin, previousPrefix.Length + 1); + + } while (folderEnd.StartsWith(prefixCandidate)); + + return previousPrefix; + } + + private void GroupIncludeRecursively(FolderIncludeTreeViewItem_Folder parentFolder, List allLeafItems, int begin, int end, int commonPrefixLength) + { + System.Diagnostics.Debug.Assert(begin < end); + System.Diagnostics.Debug.Assert(allLeafItems.Count >= end); + + // Look through the sorted subsection of folders and find ranges where the prefix changes. + while(begin < end) + { + // New subgroup to look at! + string currentPrefix = GetNextPathPrefix(allLeafItems[begin].ParentFolder, commonPrefixLength + 1); + + // Find end of the rest of the group and expand recurively. + for (int i = begin; i <= end; ++i) + { + if (i == end || !allLeafItems[i].ParentFolder.StartsWith(currentPrefix)) + { + // Find maximal prefix of this group. + string largestPrefix = LargestCommonFolderPrefixInRange(allLeafItems, begin, i, currentPrefix); + var newGroup = new FolderIncludeTreeViewItem_Folder(largestPrefix, commonPrefixLength); + parentFolder.ChildrenList.Add(newGroup); + + // 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. + while (allLeafItems[begin].ParentFolder.Length == largestPrefix.Length) + { + newGroup.ChildrenList.Add(allLeafItems[begin]); + ++begin; + if (begin == i) + break; + } + + // What's left is non-direct children (== folders!) that we need to handle recursively. + int numFoldersInGroup = i - begin; + if (numFoldersInGroup > 0) + { + GroupIncludeRecursively(newGroup, allLeafItems, begin, i, largestPrefix.Length); + } + + // Next group starts at this element. + begin = i; + break; + } + } + } + } + + public override Task NavigateToInclude() + { + return Task.CompletedTask; + } + } + + public class FolderIncludeTreeViewItem_Folder : IncludeTreeViewItem + { + public override IReadOnlyList Children => ChildrenList; + public List ChildrenList { get; private set; } = new List(); + + public const string UnresolvedFolderName = ""; + + public FolderIncludeTreeViewItem_Folder(string largestPrefix, int commonPrefixLength) + { + AbsoluteFilename = largestPrefix; + + largestPrefix.Substring(commonPrefixLength); + + if (largestPrefix != UnresolvedFolderName) + { + var stringBuilder = new StringBuilder(largestPrefix); + stringBuilder.Remove(0, commonPrefixLength); + stringBuilder.Append(Path.DirectorySeparatorChar); + if (stringBuilder[0] == Path.DirectorySeparatorChar) + stringBuilder.Remove(0, 1); + + Name = stringBuilder.ToString(); + } + else + { + Name = largestPrefix; + } + } + + public override Task NavigateToInclude() + { + // todo? + return Task.CompletedTask; + } + } + + public class FolderIncludeTreeViewItem_Leaf : IncludeTreeViewItem + { + public override IReadOnlyList Children => emptyList; + + /// + /// Parent folder of this leaf. Needed during build-up. + /// + public string ParentFolder { get; private set; } + + public FolderIncludeTreeViewItem_Leaf(IncludeGraph.Item item) + { + try + { + Name = Path.GetFileName(item.AbsoluteFilename); + } + catch + { + Name = item?.FormattedName; + } + AbsoluteFilename = item?.AbsoluteFilename; + + if (string.IsNullOrWhiteSpace(AbsoluteFilename) || !Path.IsPathRooted(AbsoluteFilename)) + ParentFolder = FolderIncludeTreeViewItem_Folder.UnresolvedFolderName; + else + ParentFolder = Path.GetDirectoryName(AbsoluteFilename); + } + + public override async Task NavigateToInclude() + { + if (AbsoluteFilename != null && Path.IsPathRooted(AbsoluteFilename)) + { + await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var fileWindow = VSUtils.OpenFileAndShowDocument(AbsoluteFilename); + } + } + } +} diff --git a/IncludeToolboxShared/Graph/IncludeGraph.cs b/IncludeToolboxShared/Graph/IncludeGraph.cs new file mode 100644 index 0000000..0db3cf4 --- /dev/null +++ b/IncludeToolboxShared/Graph/IncludeGraph.cs @@ -0,0 +1,102 @@ +using Community.VisualStudio.Toolkit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Path = System.IO.Path; + +namespace IncludeToolbox +{ + public interface IGraphModel + { + public Task TryEmplaceAsync(string absolute_filename, string name); + } + + public class IncludeGraph : IGraphModel + { + public class Item + { + public Item(string absolute, string formatted, Include[] includes) + { + AbsoluteFilename = absolute; + FormattedName = formatted; + Includes = includes; + } + + public string AbsoluteFilename { get; private set; } + public string FormattedName { get; set; } + + public Include[] Includes { get; private set; } + } + + public async Task InitializeAsync(string filename) + { + graphItems = new(); + return await TryEmplaceAsync(filename, Path.GetFileNameWithoutExtension(filename)); + } + + + public async Task InitializeAsync(DocumentView document) + { + graphItems = new(); + return await TryEmplaceAsync(document); + } + public async Task TryEmplaceAsync(DocumentView document) + { + var absolute_filename = document.FilePath; + bool is_new = !graphItems.TryGetValue(absolute_filename, out Item outItem); + if (!is_new) return outItem; + + outItem = await ParseFileAsync(document); + graphItems.Add(absolute_filename, outItem); + return outItem; + } + private async Task ParseFileAsync(DocumentView document) + { + var incs = await VCUtil.GetIncludeDirsAsync(); + var path = document.FilePath; + var doc_folder = Path.GetDirectoryName(path); + + var inc_arr = new string[] { + Microsoft.VisualStudio.PlatformUI.PathUtil.Normalize(doc_folder) + + Path.DirectorySeparatorChar } + .Concat(incs).ToArray(); + + var text = document.TextBuffer.CurrentSnapshot.GetText(); + var includes = Parser.ParseInclues(text.AsSpan()); + + return new Item(path, Path.GetFileName(path), + includes.Select(s => new Include(s, inc_arr)).ToArray()); + } + + + public async Task TryEmplaceAsync(string absolute_filename, string name) + { + bool is_new = !graphItems.TryGetValue(absolute_filename, out Item outItem); + if (!is_new) return outItem; + + outItem = await ParseFileAsync(absolute_filename, name); + graphItems.Add(absolute_filename, outItem); + return outItem; + } + + private async Task ParseFileAsync(string absolute_filename, string name) + { + var doc = await VS.Documents.GetDocumentViewAsync(absolute_filename); + doc ??= await VS.Documents.OpenViaProjectAsync(absolute_filename); + + var incs = await VCUtil.GetIncludeDirsAsync(); + var doc_folder = Path.GetDirectoryName(absolute_filename); + + var inc_arr = new string[] { + Microsoft.VisualStudio.PlatformUI.PathUtil.Normalize(doc_folder) + + Path.DirectorySeparatorChar } + .Concat(incs).ToArray(); + + var text = doc.TextBuffer.CurrentSnapshot.GetText(); + return new Item(absolute_filename, name, Parser.ParseInclues(text.AsSpan()).Select(s=>new Include(s, inc_arr)).ToArray()); + } + + private Dictionary graphItems = new(); + } +} diff --git a/IncludeToolbox/Graph/CompilationBasedGraphParser.cs b/IncludeToolboxShared/GraphEx/CompilationBasedGraphParser.cs similarity index 100% rename from IncludeToolbox/Graph/CompilationBasedGraphParser.cs rename to IncludeToolboxShared/GraphEx/CompilationBasedGraphParser.cs diff --git a/IncludeToolbox/Graph/CustomGraphParser.cs b/IncludeToolboxShared/GraphEx/CustomGraphParser.cs similarity index 100% rename from IncludeToolbox/Graph/CustomGraphParser.cs rename to IncludeToolboxShared/GraphEx/CustomGraphParser.cs diff --git a/IncludeToolbox/Graph/DGMLGraph.cs b/IncludeToolboxShared/GraphEx/DGMLGraph.cs similarity index 100% rename from IncludeToolbox/Graph/DGMLGraph.cs rename to IncludeToolboxShared/GraphEx/DGMLGraph.cs diff --git a/IncludeToolbox/Graph/IncludeGraph.cs b/IncludeToolboxShared/GraphEx/IncludeGraph.cs similarity index 100% rename from IncludeToolbox/Graph/IncludeGraph.cs rename to IncludeToolboxShared/GraphEx/IncludeGraph.cs diff --git a/IncludeToolbox/Graph/IncludeGraphToDGML.cs b/IncludeToolboxShared/GraphEx/IncludeGraphToDGML.cs similarity index 100% rename from IncludeToolbox/Graph/IncludeGraphToDGML.cs rename to IncludeToolboxShared/GraphEx/IncludeGraphToDGML.cs diff --git a/IncludeToolbox/GraphWindow/Commands/RefreshIncludeGraph.cs b/IncludeToolboxShared/GraphWindow/Commands/RefreshIncludeGraph.cs similarity index 100% rename from IncludeToolbox/GraphWindow/Commands/RefreshIncludeGraph.cs rename to IncludeToolboxShared/GraphWindow/Commands/RefreshIncludeGraph.cs diff --git a/IncludeToolbox/GraphWindow/Commands/RefreshModeComboBox.cs b/IncludeToolboxShared/GraphWindow/Commands/RefreshModeComboBox.cs similarity index 100% rename from IncludeToolbox/GraphWindow/Commands/RefreshModeComboBox.cs rename to IncludeToolboxShared/GraphWindow/Commands/RefreshModeComboBox.cs diff --git a/IncludeToolbox/GraphWindow/Commands/RefreshModeComboBoxOptions.cs b/IncludeToolboxShared/GraphWindow/Commands/RefreshModeComboBoxOptions.cs similarity index 100% rename from IncludeToolbox/GraphWindow/Commands/RefreshModeComboBoxOptions.cs rename to IncludeToolboxShared/GraphWindow/Commands/RefreshModeComboBoxOptions.cs diff --git a/IncludeToolbox/GraphWindow/Commands/SaveDGML.cs b/IncludeToolboxShared/GraphWindow/Commands/SaveDGML.cs similarity index 100% rename from IncludeToolbox/GraphWindow/Commands/SaveDGML.cs rename to IncludeToolboxShared/GraphWindow/Commands/SaveDGML.cs diff --git a/IncludeToolbox/GraphWindow/PropertyChangedBase.cs b/IncludeToolboxShared/GraphWindow/PropertyChangedBase.cs similarity index 100% rename from IncludeToolbox/GraphWindow/PropertyChangedBase.cs rename to IncludeToolboxShared/GraphWindow/PropertyChangedBase.cs diff --git a/IncludeToolbox/GraphWindow/View/IncludeGraphControl.xaml b/IncludeToolboxShared/GraphWindow/View/IncludeGraphControl.xaml similarity index 100% rename from IncludeToolbox/GraphWindow/View/IncludeGraphControl.xaml rename to IncludeToolboxShared/GraphWindow/View/IncludeGraphControl.xaml diff --git a/IncludeToolbox/GraphWindow/View/IncludeGraphControl.xaml.cs b/IncludeToolboxShared/GraphWindow/View/IncludeGraphControl.xaml.cs similarity index 100% rename from IncludeToolbox/GraphWindow/View/IncludeGraphControl.xaml.cs rename to IncludeToolboxShared/GraphWindow/View/IncludeGraphControl.xaml.cs diff --git a/IncludeToolbox/GraphWindow/View/IncludeGraphToolWindow.cs b/IncludeToolboxShared/GraphWindow/View/IncludeGraphToolWindow.cs similarity index 100% rename from IncludeToolbox/GraphWindow/View/IncludeGraphToolWindow.cs rename to IncludeToolboxShared/GraphWindow/View/IncludeGraphToolWindow.cs diff --git a/IncludeToolbox/GraphWindow/View/ToolWindowStyle.xaml b/IncludeToolboxShared/GraphWindow/View/ToolWindowStyle.xaml similarity index 100% rename from IncludeToolbox/GraphWindow/View/ToolWindowStyle.xaml rename to IncludeToolboxShared/GraphWindow/View/ToolWindowStyle.xaml diff --git a/IncludeToolbox/GraphWindow/ViewModel/FolderIncludeTreeItem.cs b/IncludeToolboxShared/GraphWindow/ViewModel/FolderIncludeTreeItem.cs similarity index 100% rename from IncludeToolbox/GraphWindow/ViewModel/FolderIncludeTreeItem.cs rename to IncludeToolboxShared/GraphWindow/ViewModel/FolderIncludeTreeItem.cs diff --git a/IncludeToolbox/GraphWindow/ViewModel/HierarchyIncludeTreeViewItem.cs b/IncludeToolboxShared/GraphWindow/ViewModel/HierarchyIncludeTreeViewItem.cs similarity index 100% rename from IncludeToolbox/GraphWindow/ViewModel/HierarchyIncludeTreeViewItem.cs rename to IncludeToolboxShared/GraphWindow/ViewModel/HierarchyIncludeTreeViewItem.cs diff --git a/IncludeToolbox/GraphWindow/ViewModel/IncludeGraphViewModel.cs b/IncludeToolboxShared/GraphWindow/ViewModel/IncludeGraphViewModel.cs similarity index 100% rename from IncludeToolbox/GraphWindow/ViewModel/IncludeGraphViewModel.cs rename to IncludeToolboxShared/GraphWindow/ViewModel/IncludeGraphViewModel.cs diff --git a/IncludeToolbox/GraphWindow/ViewModel/IncludeTreeViewItem.cs b/IncludeToolboxShared/GraphWindow/ViewModel/IncludeTreeViewItem.cs similarity index 100% rename from IncludeToolbox/GraphWindow/ViewModel/IncludeTreeViewItem.cs rename to IncludeToolboxShared/GraphWindow/ViewModel/IncludeTreeViewItem.cs diff --git a/IncludeToolboxShared/IWYU/IWYU.cs b/IncludeToolboxShared/IWYU/IWYU.cs new file mode 100644 index 0000000..7b04f6c --- /dev/null +++ b/IncludeToolboxShared/IWYU/IWYU.cs @@ -0,0 +1,160 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox.IncludeWhatYouUse +{ + internal class IWYU + { + readonly Process process = new(); + string output = ""; + string command_line = ""; + readonly string support_path = ""; + readonly string support_cpp_path = ""; + + public string ProcOutput { get => output; } + + public IWYU() + { + process.EnableRaisingEvents = true; + process.StartInfo.UseShellExecute = false; + process.StartInfo.CreateNoWindow = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + + process.OutputDataReceived += (s, args) => + { + output += args.Data + "\n"; + }; + process.ErrorDataReceived += (s, args) => + { + output += args.Data + "\n"; + }; + + // initialize temp files (can be multithreaded, hence instance based) + support_cpp_path = Path.ChangeExtension(Path.GetTempFileName(), ".cpp"); + support_path = Path.GetTempFileName(); + } + + public void BuildCommandLine(IWYUOptions settings) + { + process.StartInfo.FileName = settings.Executable; + + List args = new() + { + string.Format("--verbose={0}", settings.Verbosity) + }; + + if (settings.Precompiled || settings.IgnoreHeader) + args.Add("--pch_in_code"); + if (settings.Transitives) + args.Add("--transitive_includes_only"); + if (settings.NoDefault) + args.Add("--no_default_mappings"); + if (settings.UseProvided) + { + var path = IWYUDownload.GetDefaultMappingChecked(); + if (!string.IsNullOrEmpty(path)) + args.Add(string.Format("--mapping_file=\"{0}\"", path)); + } + if (settings.MappingFile != "") + args.Add(string.Format("--mapping_file=\"{0}\"", settings.MappingFile)); + args.Add("--max_line_length=256"); // output line for commentaries + + command_line = + string.Join(" ", args.Select(x => " -Xiwyu " + x)); + + if (!settings.Warnings) + command_line += " -w"; + + command_line += " -Wno-invalid-token-paste -fms-compatibility -fms-extensions -fdelayed-template-parsing"; + if (settings.ClangOptions != null && settings.ClangOptions?.Count() != 0) + command_line += " " + string.Join(" ", settings.ClangOptions); + if (settings.Options != null && settings.Options.Count() != 0) + command_line += " " + string.Join(" ", settings.Options.Select(x => " -Xiwyu " + x)); + } + + static public void MoveHeader(DocumentView view) + { + var buf = view.TextBuffer; + var str = buf.CurrentSnapshot.GetText(); + var span = Utils.GetIncludeSpan(str); + + Regex regex = new($"#include\\s[<\"]([\\w\\\\\\/\\.]+{Path.GetFileNameWithoutExtension(view.FilePath)}.h(?:pp|xx)?)[>\"]"); + var match = regex.Match(str, span.Start, span.Length); + if (!match.Success) return; + var edit = buf.CreateEdit(); + _ = edit.Delete(new(match.Index, match.Length)); + + edit.Insert(span.Start, match.Value + Utils.GetLineBreak(view.TextView)); + edit.Apply(); + } + + + + public async Task StartAsync(string file, Project proj, bool rebuild) + { + var cmd = await VCUtil.GetCommandLineAsync(rebuild, proj); + if (string.IsNullOrEmpty(cmd)) + { + Output.WriteLineAsync("Failed to gather command line for c++ project").FireAndForget(); + return false; + } + return StartImpl(file, cmd); + } + public async Task StartAsync(string file, bool rebuild) + { + var cmd = await VCUtil.GetCommandLineAsync(rebuild); + if (string.IsNullOrEmpty(cmd)) + { + Output.WriteLineAsync("Failed to gather command line for c++ project").FireAndForget(); + return false; + } + return StartImpl(file, cmd); + } + + bool StartImpl(string file, string cmd) + { + output = ""; + File.WriteAllText(support_path, cmd); + + var ext = Path.GetExtension(file); + if (ext == ".h" || ext == ".hpp" || ext == ".hxx") + { + File.WriteAllText(support_cpp_path, "#include \"" + file + "\""); + file = " -Xiwyu --check_also=" + "\"" + file + "\""; + file += " \"" + support_cpp_path.Replace("\\", "\\\\") + "\""; + } + else + { + file = "\"" + file + "\""; + } + + process.StartInfo.Arguments = $"{command_line} \"@{support_path}\" {file}"; + + Output.WriteLineAsync(string.Format("Running command '{0}' with following arguments:\n{1}\n\n", process.StartInfo.FileName, process.StartInfo.Arguments)).FireAndForget(); + + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + process.CancelOutputRead(); + process.CancelErrorRead(); + + Output.WriteLineAsync(output).FireAndForget(); + return true; + } + public async Task CancelAsync() + { + await Task.Run(delegate { process.Kill(); }); + Output.WriteLineAsync($"IWYU Process {process.ProcessName} was cancelled.").FireAndForget(); + } + } +} diff --git a/IncludeToolboxShared/IWYU/IWYUApply.cs b/IncludeToolboxShared/IWYU/IWYUApply.cs new file mode 100644 index 0000000..5c4577e --- /dev/null +++ b/IncludeToolboxShared/IWYU/IWYUApply.cs @@ -0,0 +1,147 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Text; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace IncludeToolbox +{ + internal static class IWYUApply + { + static readonly string match = "The full include-list for "; + + public static void ClearNamespaces(DocumentView doc) + { + using var edit = doc.TextBuffer.CreateEdit(); + var text = doc.TextBuffer.CurrentSnapshot.GetText(); + var rem = Parser.ParseEmptyNamespaces(text); + foreach (var ns in rem) + edit.Delete(ns); + edit.Apply(); + } + + public static async Task FormatAsync(DocumentView doc) + { + var include_directories = await VCUtil.GetIncludeDirsAsync(); + var settings = await FormatOptions.GetLiveInstanceAsync(); + var text = doc.TextBuffer.CurrentSnapshot.GetText(); + var span = Utils.GetIncludeSpan(text); + var slice = text.Substring(span.Start, span.Length); + + var result = Formatter.IncludeFormatter.FormatIncludes( + slice.AsSpan(), + doc.FilePath, + include_directories, settings + ); + + Formatter.IncludeFormatter.ApplyChanges(result, doc, slice, span.Start); + } + + public static void ApplyCheap(ITextEdit edit, string result, bool commentary, string lb) + { + if (!commentary) + { + result = string.Join(lb, result.Split('\n') + .Select(s => + { + var str = s.Trim(); + var idx = str.IndexOf("//"); + if (idx >= 0) + return str.Substring(0, idx).Trim(); + return str; + }).ToArray()); + } + var span = Utils.GetIncludeSpan(edit.Snapshot.GetText()); + edit.Replace(span, result); + } + + public static async Task ApplyAsync(IWYUOptions settings, string output) + { + if (output == "") return; + + int pos = output.IndexOf(match); + if (pos == -1) return; + + pos += match.Length; + string part = output.Substring(pos); + + int endp = part.IndexOf("---"); + string path = part.Substring(0, part.IndexOf(':', 3)); + var doc = await VS.Documents.OpenAsync(path); + var lb = Utils.GetLineBreak(doc.TextView); + + using var edit = doc.TextBuffer.CreateEdit(); + + int endl = part.IndexOf("\n"); + string result = part.Substring(endl, endp - endl); + ApplyCheap(edit, + result, + settings.Comms != Comment.No, lb); + + edit.Apply(); + } + + public static async Task ApplyPreciseAsync(IWYUOptions settings, Parser.Output parsed, string output, Standard std) + { + if (output == "") return; + + int pos = output.IndexOf(match); + if (pos == -1) return; + + var retasks = Parser.Parse(output.AsSpan().Slice(0, pos), true, true); + int sep_index = output.IndexOf(" should remove these lines:"); //find middle ground + + pos += match.Length; + string part = output.Substring(pos); + + string path = part.Substring(0, part.IndexOf(':', 3)); + var doc = await VS.Documents.OpenAsync(path); + using var edit = doc.TextBuffer.CreateEdit(); + var lb = Utils.GetLineBreak(doc.TextView); + + var add_f = retasks.Declarations.Where(s => s.span.Start < sep_index); + var rem_f = retasks.Declarations.Where(s => s.span.Start > sep_index); + + var add_i = retasks.Includes.Where(s => s.span.Start < sep_index); + var rem_i = retasks.Includes.Where(s => s.span.Start > sep_index); + + foreach (var task in rem_i) + { + var found = parsed.Includes.FindLast(s => s == task); + edit.Delete(found.span); + parsed.Includes.Remove(found); + } + + DeclNode tree = new(Lexer.TType.Namespace) + { + LineBreak = lb + }; + if (settings.MoveDecls) + { + tree.AddChildren(parsed.Declarations.Where(s => !rem_f.Contains(s))); + foreach (var task in parsed.Declarations) + edit.Delete(task.span); + } + else + { + foreach (var task in rem_f) + { + var found = parsed.Declarations.FindLast(s => s == task); + edit.Delete(found.span); + parsed.Declarations.Remove(found); + } + } + tree.AddChildren(add_f); + + string addition = ""; + + foreach (var item in add_i.Select(s => s.Project(output))) + addition+=lb+item; + + addition+= lb + lb + tree.ToString(std >= Standard.cpp17); + + edit.Insert(parsed.InsertionPoint, addition); + edit.Apply(); + } + } +} diff --git a/IncludeToolboxShared/IWYU/IWYUDownload.cs b/IncludeToolboxShared/IWYU/IWYUDownload.cs new file mode 100644 index 0000000..b220f0a --- /dev/null +++ b/IncludeToolboxShared/IWYU/IWYUDownload.cs @@ -0,0 +1,249 @@ +using Community.VisualStudio.Toolkit; +using IncludeToolbox.Commands; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox.IncludeWhatYouUse +{ + /// + /// Functions for downloading and versioning of the iwyu installation. + /// + public class IWYUDownload + { + public static readonly string DisplayRepositorURL = @"https://github.com/Agrael1/BuildIWYU"; + private static readonly string DownloadRepositorURL = @"https://github.com/Agrael1/BuildIWYU/archive/main.zip"; + private static readonly string LatestCommitQuery = @"https://api.github.com/repos/Agrael1/BuildIWYU/git/refs/heads/main"; + + public static string GetDefaultFolder() + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "iwyu"); + } + public static string GetDefaultExecutablePath() + { + return Path.Combine(GetDefaultFolder(), "include-what-you-use.exe"); + } + public static string GetDefaultMappingPath() + { + return Path.Combine(GetDefaultFolder(), "msvc.imp"); + } + public static string GetDefaultMappingChecked() + { + var path = GetDefaultMappingPath(); + return File.Exists(path) ? path : ""; + } + + + static public string GetVersionFilePath() + { + return Path.Combine(GetDefaultFolder(), "version"); + } + + public delegate void OnChangeDelegate(string Section, string Status, float percent); + + + + public event OnChangeDelegate OnProgress; + WebClient client; + + protected void OnProgressEvent(string Section, string Status, float percent) + { + OnProgress?.Invoke(Section, Status, percent); + } + + + + public static async Task DownloadAsync(IVsThreadedWaitDialogFactory dialogFactory, IWYUOptions settings) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (!await VS.MessageBox.ShowConfirmAsync($"Can't locate include-what-you-use. Do you want to download it from '{IWYUDownload.DisplayRepositorURL}'?")) + return false; + + var downloader = new IWYUDownload(); + + dialogFactory.CreateInstance(out IVsThreadedWaitDialog2 xdialog); + IVsThreadedWaitDialog4 dialog = xdialog as IVsThreadedWaitDialog4; + + downloader.OnProgress += (string section, string status, float percentage) => + { + ThreadHelper.ThrowIfNotOnUIThread(); + + dialog.UpdateProgress( + szUpdatedWaitMessage: section, + szProgressText: status, + szStatusBarText: $"Downloading include-what-you-use - {section} - {status}", + iCurrentStep: (int)(percentage * 100), + iTotalSteps: 100, + fDisableCancel: false, + pfCanceled: out bool canceled); + }; + + dialog.StartWaitDialogWithCallback( + szWaitCaption: "Include Toolbox - Downloading include-what-you-use", + szWaitMessage: "", // comes in later. + szProgressText: null, + varStatusBmpAnim: null, + szStatusBarText: "Downloading include-what-you-use", + fIsCancelable: true, + iDelayToShowDialog: 0, + fShowProgress: true, + iTotalSteps: 100, + iCurrentStep: 0, + new CancelCallback(() => { downloader.Cancel(); })); + + await downloader.DownloadIWYUAsync(); + + if (dialog.EndWaitDialog()) return false; + settings.Executable = GetDefaultExecutablePath(); + + settings.Downloaded(); + await settings.SaveAsync(); + + return true; + } + + private static async Task GetVersionOnlineAsync() + { + using (var httpClient = new HttpClient()) + { + // User agent is always required for github api. + // https://developer.github.com/v3/#user-agent-required + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("IncludeToolbox"); + + string latestCommitResponse; + try + { + latestCommitResponse = await httpClient.GetStringAsync(LatestCommitQuery); + } + catch (HttpRequestException e) + { + _ = Output.WriteLineAsync($"Failed to query IWYU version from {DownloadRepositorURL}: {e}"); + return ""; + } + + // Poor man's json parsing in lack of a json parser. + var shaRegex = new Regex(@"\""sha\""\w*:\w*\""([a-z0-9]+)\"""); + return shaRegex.Match(latestCommitResponse).Groups[1].Value; + } + } + + private static string GetCurrentVersionHarddrive() + { + // Read current version. + try + { + return File.ReadAllText(GetVersionFilePath()); + } + catch + { + return ""; + } + } + + public static async Task IsNewerVersionAvailableOnlineAsync() + { + string currentVersion = GetCurrentVersionHarddrive(); + string onlineVersion = await GetVersionOnlineAsync(); + return currentVersion != onlineVersion; + } + + + + + async Task DownloadAsync(string targetZipFile) + { + using (client = new WebClient()) + { + client.DownloadProgressChanged += (object sender, DownloadProgressChangedEventArgs e) => + { + int kbTodo = (int)System.Math.Ceiling((double)e.TotalBytesToReceive / 1024); + int kbDownloaded = (int)System.Math.Ceiling((double)e.BytesReceived / 1024); + OnProgressEvent("Downloading", kbTodo > 0 ? $"{kbTodo} / {kbDownloaded} kB" : $"{kbDownloaded} kB", e.ProgressPercentage * 0.01f); + }; + + await client.DownloadFileTaskAsync(DownloadRepositorURL, targetZipFile); + client = null; + } + } + + /// + /// Downloads iwyu from default download repository. + /// + public async Task DownloadIWYUAsync() + { + string targetDirectory = GetDefaultFolder(); + Directory.CreateDirectory(targetDirectory); + DirectoryInfo di = new DirectoryInfo(targetDirectory); + + foreach (FileInfo file in di.EnumerateFiles()) + { + file.Delete(); + } + foreach (DirectoryInfo dir in di.EnumerateDirectories()) + { + dir.Delete(true); + } + + string targetZipFile = Path.Combine(targetDirectory, "download.zip"); + + // Download. + OnProgressEvent("Connecting...", "", -1.0f); + + try + { + await DownloadAsync(targetZipFile); + } + catch (Exception e) + { + _ = Output.WriteLineAsync("Failed to download IWYU with error:" + e.Message); + return; + } + + // Unpacking. Looks like there is no async api, so we're just moving this to a task. + OnProgressEvent("Unpacking...", "", -1.0f); + + try + { + using (ZipArchive archive = ZipFile.OpenRead(targetZipFile)) + { + foreach (ZipArchiveEntry entry in archive.Entries.Where(e => + { + if (e.Name == "LICENSE") return true; + string a = Path.GetExtension(e.FullName); + return a == ".exe" || a == ".txt" || a == ".imp" || a == ".md"; + })) + { + entry.ExtractToFile(Path.Combine(GetDefaultFolder(), entry.Name)); + } + } + } + catch (Exception e) + { + _ = Output.WriteLineAsync("Failed to unpack IWYU with error:" + e.Message); + File.Delete(targetZipFile); + return; + } + + // Save version. + OnProgressEvent("Saving Version", "", -1.0f); + string version = await GetVersionOnlineAsync(); + File.WriteAllText(GetVersionFilePath(), version); + + OnProgressEvent("Clean Up", "", -1.0f); + File.Delete(targetZipFile); + } + public void Cancel() + { + client?.CancelAsync(); + } + } +} diff --git a/IncludeToolboxShared/Implementation/IncludeFormatter.cs b/IncludeToolboxShared/Implementation/IncludeFormatter.cs new file mode 100644 index 0000000..458fc57 --- /dev/null +++ b/IncludeToolboxShared/Implementation/IncludeFormatter.cs @@ -0,0 +1,247 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Text; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace IncludeToolbox.Formatter +{ + public static class IncludeFormatter + { + public static string FormatPath(string absoluteIncludeFilename, PathMode pathformat, IEnumerable includeDirectories) + { + // todo: Treat std library files special? + + if (absoluteIncludeFilename == null) return null; + + int bestLength = int.MaxValue; + string bestCandidate = null; + + foreach (string includeDirectory in includeDirectories) + { + string proposal = Utils.MakeRelative(includeDirectory, absoluteIncludeFilename); + + if (proposal.Length < bestLength) + { + if (pathformat == PathMode.Shortest || + (proposal.IndexOf("../") < 0 && proposal.IndexOf("..\\") < 0)) + { + bestCandidate = proposal; + bestLength = proposal.Length; + } + } + } + return bestCandidate; + } + private static void FormatPaths(IncludeLine[] lines, PathMode pathformat, IEnumerable includeDirectories) + { + for (int i = 0; i < lines.Length; i++) + { + string absoluteIncludeDir = lines[i].Resolve(includeDirectories); + if (string.IsNullOrEmpty(absoluteIncludeDir)) continue; + var formatted = FormatPath(absoluteIncludeDir, pathformat, includeDirectories); + if (string.IsNullOrEmpty(formatted)) continue; + lines[i].SetFile(formatted); + } + } + + private static void FormatDelimiters(IncludeLine[] lines, DelimiterMode delimiterMode) + { + switch (delimiterMode) + { + case DelimiterMode.AngleBrackets: + for (int i = 0; i < lines.Length; i++) + lines[i].SetDelimiter(DelimiterMode.AngleBrackets); + break; + case DelimiterMode.Quotes: + for (int i = 0; i < lines.Length; i++) + lines[i].SetDelimiter(DelimiterMode.Quotes); + break; + } + } + private static void FormatSlashes(IncludeLine[] lines, SlashMode slashMode) + { + switch (slashMode) + { + case SlashMode.ForwardSlash: + for (int i = 0; i < lines.Length; i++) + lines[i].ToForward(); + break; + case SlashMode.BackSlash: + for (int i = 0; i < lines.Length; i++) + lines[i].ToBackward(); + break; + } + } + + private static IncludeLine[] SortIncludes(IncludeLine[] lines, FormatOptions settings, string documentName) + { + string[] precedenceRegexes = RegexUtils.FixupRegexes(settings.PrecedenceRegexes, documentName); + List outSortedList = new(lines.Length); + + while (lines.Length != 0) + { + int line_n = lines.First().line; + + var pack = lines.TakeWhile(s => + { + bool a = s.line - line_n <= 1; + line_n = s.line; + return a; + }); + var e = pack.ToArray(); + if (e.Count() > 1) + outSortedList.AddRange(SortIncludeBatch(settings, precedenceRegexes, e)); + else + outSortedList.AddRange(e); + lines = lines.Skip(e.Count()).ToArray(); + } + + return outSortedList.ToArray(); + } + + private static void RemoveDuplicates(IncludeLine[] includes) + { + HashSet uniqueIncludes = new(); + uniqueIncludes.UnionWith(includes.Where(s => s.Keep).Select(s => s.FullFile)); + + for (int i = 0; i < includes.Length; i++) + { + ref var r = ref includes[i]; + if (!r.Keep && !uniqueIncludes.Add(r.FullFile)) + r.SetFullContent(""); + } + } + private static IncludeLine[] SortIncludeBatch(FormatOptions settings, + string[] precedenceRegexes, + IncludeLine[] includeBatch) + { + // Fetch settings. + TypeSorting typeSorting = settings.SortByType; + bool regexIncludeDelimiter = settings.RegexIncludeDelimiter; + bool blankAfterRegexGroupMatch = settings.BlankAfterRegexGroupMatch; + + // Select only valid include lines and sort them. They'll stay in this relative sorted + // order when rearranged by regex precedence groups. + var includeLines = includeBatch + .OrderBy(x => { return x.Content; }).ToArray(); + + if (settings.RemoveDuplicates) + { + // store kept headers first, to remove all the duplicates + HashSet uniqueIncludes = new(); + uniqueIncludes.UnionWith(includeLines.Where(s => s.Keep).Select(s => s.FullFile)); + + for (int i = 0; i < includeLines.Length; i++) + { + ref var r = ref includeLines[i]; + if (!r.Keep && !uniqueIncludes.Add(r.FullFile)) + r.SetFullContent(""); + } + } + + // Group the includes by the index of the precedence regex they match, or + // precedenceRegexes.Length for no match, and sort the groups by index. + var includeGroups = includeLines + .GroupBy(x => + { + if (!x.Valid) return precedenceRegexes.Length; + var includeContent = regexIncludeDelimiter ? x.FullFile : x.Content; + for (int precedence = 0; precedence < precedenceRegexes.Count(); ++precedence) + { + if (Regex.Match(includeContent, precedenceRegexes[precedence]).Success) + return precedence; + } + return precedenceRegexes.Length; + }, x => x) + .OrderBy(x => x.Key); + + // Optional newlines between regex match groups + var groupStarts = new HashSet(); + if (blankAfterRegexGroupMatch && precedenceRegexes.Length > 0 && includeLines.Count() > 1) + { + // Set flag to prepend a newline to each group's first include + foreach (var grouping in includeGroups) + groupStarts.Add(grouping.First()); + } + + // Flatten the groups + var sortedIncludes = includeGroups.SelectMany(x => x.Select(y => y)); + + // Sort by angle or quoted delimiters if either of those options were selected + if (typeSorting == TypeSorting.AngleBracketsFirst) + sortedIncludes = sortedIncludes.OrderBy(x => x.delimiter == DelimiterMode.AngleBrackets ? 0 : 1); + else if (typeSorting == TypeSorting.QuotedFirst) + sortedIncludes = sortedIncludes.OrderBy(x => x.delimiter == DelimiterMode.Quotes ? 0 : 1); + + return sortedIncludes.ToArray(); + } + + + + public static IncludeLine[] FormatIncludes(ReadOnlySpan text, string documentPath, IEnumerable includeDirectories, FormatOptions settings) + { + string documentDir = Path.GetDirectoryName(documentPath); + string documentName = Path.GetFileNameWithoutExtension(documentPath); + + includeDirectories = new string[] { Microsoft.VisualStudio.PlatformUI.PathUtil.Normalize(documentDir) + Path.DirectorySeparatorChar }.Concat(includeDirectories); + + var lines = Parser.ParseInclues(text, settings.IgnoreIfdefs); + + // Format. + IEnumerable formatingDirs = includeDirectories; + if (settings.IgnoreFileRelative) + formatingDirs = formatingDirs.Skip(1); + + if (settings.RemoveDuplicates) + RemoveDuplicates(lines); + if (settings.PathFormat != PathMode.Unchanged) + FormatPaths(lines, settings.PathFormat, formatingDirs); + + FormatDelimiters(lines, settings.DelimiterFormatting); + FormatSlashes(lines, settings.SlashFormatting); + + + // Sorting. Ignores non-include lines. + return SortIncludes(lines, settings, documentName); + } + + + private static IEnumerable> RemoveWhitespaces(this IEnumerable> e, string text) + { + int start = 0; + foreach (var a in e) + { + var x = a; + if (start != 0 && a.Key.Start - start > 0) + { + ReadOnlySpan subspan = text.AsSpan(start, a.Key.Start - start); + if (subspan.IsWhiteSpace()) + x = new(new(start, a.Key.Start - start + a.Key.Length), a.Value); + } + start = x.Key.End; + yield return x; + } + } + public static void ApplyChanges(IncludeLine[] includes, DocumentView doc, string text, int relative_pos, bool remove_empty = true) + { + var lb = Utils.GetLineBreak(doc.TextView); + var enumerator = includes + .OrderBy(s => s.line) + .Zip(includes, + (a, b) => { return new KeyValuePair(a.span, b.Project(text)); }); + + + using var edit = doc.TextBuffer.CreateEdit(); + + if (remove_empty) enumerator = enumerator.RemoveWhitespaces(text); + foreach (var line in enumerator) + edit.Replace(line.Key.Move(relative_pos), line.Value); + + edit.Apply(); + } + } +} diff --git a/IncludeToolboxShared/Implementation/TrialAndErrorRemoval.cs b/IncludeToolboxShared/Implementation/TrialAndErrorRemoval.cs new file mode 100644 index 0000000..fd02680 --- /dev/null +++ b/IncludeToolboxShared/Implementation/TrialAndErrorRemoval.cs @@ -0,0 +1,266 @@ +using System; +using System.Linq; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using System.IO; +using System.Threading.Tasks; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.VCProjectEngine; +using Microsoft.VisualStudio.Threading; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox +{ + internal sealed class AsyncDispatcher : IDisposable + { + TaskCompletionSource tcs; + private bool disposedValue; + + private void Dispatch(bool a) + { + tcs.SetResult(a); + } + public AsyncDispatcher() + { + VS.Events.BuildEvents.SolutionBuildDone += Dispatch; + } + ~AsyncDispatcher() + { + if (!disposedValue) + VS.Events.BuildEvents.SolutionBuildDone -= Dispatch; + } + + public async Task CompileAsync(VCFileConfiguration config) + { + tcs = new(); + for (int i = 0; i < 3; i++) + { + try + { + config.Compile(true, false); + return await tcs.Task; + } + catch (Exception) + { + await Task.Delay(100); + } + } + return false; + } + + void IDisposable.Dispose() + { + if (!disposedValue) + { + VS.Events.BuildEvents.SolutionBuildDone -= Dispatch; + disposedValue = true; + } + GC.SuppressFinalize(this); + } + } + internal sealed class DialogGuard : IDisposable + { + private bool disposedValue; + public IVsThreadedWaitDialog4 Dialog { get; private set; } + + public DialogGuard(IVsThreadedWaitDialog4 dialog) + { + this.Dialog = dialog; + } + + private void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects) + } + Dialog.EndWaitDialog(); + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + ~DialogGuard() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + internal sealed class TrialAndErrorRemoval + { + public static bool WorkInProgress { get; private set; } + + public int Removed { get; private set; } = 0; + + + //Makes less variable noise + struct Descriptor + { + public IncludeLine[] lines; + public ITextBuffer buffer; + public string text; + public string filename; + public VCFileConfiguration config; + public int offset; + public TrialAndErrorRemovalOptions settings; + } + + + private async Task TestCompileAsync(VCFileConfiguration config) + { + using AsyncDispatcher dispatcher = new(); + return await dispatcher.CompileAsync(config); + } + + + + public async Task StartGenericAsync(VCFileConfiguration config, DocumentView document, TrialAndErrorRemovalOptions settings) + { + var buffer = document.TextBuffer; + var snap = buffer.CurrentSnapshot; + + var text = snap.GetText(); + var span = Utils.GetIncludeSpan(text); + text = text.Substring(span.Start, span.Length); + + var lines = Parser.ParseInclues(text.AsSpan(), settings.IgnoreIfdefs); + var iterator = lines.Where(s => !s.Keep); + + // Filter regecies + string documentName = Path.GetFileNameWithoutExtension(document.FilePath); + string[] ignoreRegexList = RegexUtils.FixupRegexes(settings.IgnoreList, documentName); + iterator = iterator.Where(line => !ignoreRegexList.Any(regexPattern => + new System.Text.RegularExpressions.Regex(regexPattern).Match(line.Content).Success)); + + iterator = settings.RemovalOrder == IncludeRemovalOrder.TopToBottom ? iterator : iterator.Reverse(); + + var array = iterator.ToArray(); + try + { + Descriptor desc = new() + { + lines = array, + settings = settings, + config = config, + text = text, + filename = document.FilePath, + buffer = buffer, + offset = span.Start + }; + await RemoveAsync(desc); + _ = Output.WriteLineAsync($"Successfully removed {Removed} headers from {desc.filename}"); + } + catch (Exception e) + { + return $"Failed to create a dialog: {e.Message}"; + } + return ""; + } + + private async Task RemoveAsync(Descriptor desc) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + using DialogGuard dialog = new(await StartProgressDialogAsync(desc.filename, desc.lines.Length + 1)); + int delta = 0; + using AsyncDispatcher dispatcher = new(); + int step = 0; + + foreach (var line in desc.lines) + { + string waitMessage = $"Removing #includes from '{desc.filename}'"; + string progressText = $"Trying to remove '{line.Content}' ..."; + dialog.Dialog.UpdateProgress( + szUpdatedWaitMessage: waitMessage, + szProgressText: progressText, + szStatusBarText: "Running Trial & Error Removal - " + waitMessage + " - " + progressText, + iCurrentStep: ++step, + iTotalSteps: desc.lines.Length + 1, + fDisableCancel: false, + pfCanceled: out var canceled); + + if (canceled) + { + _ = Output.WriteLineAsync("Operation was cancelled."); + return; + } + + var rs = desc.settings.KeepLineBreaks ? + line.ReplaceSpanWithoutNewline(desc.offset) : + line.ReplaceSpan(desc.offset); + + desc.buffer.Delete(rs); + + bool b = await dispatcher.CompileAsync(desc.config); + + if (b) + { + if (desc.settings.RemovalOrder == IncludeRemovalOrder.TopToBottom) + { + desc.offset -= rs.Length; + delta += rs.Length; + } + await Output.WriteLineAsync($"{line.FullFile} was successfully removed"); + Removed++; + continue; + } + desc.buffer.Insert(rs.Start, desc.text.Substring(rs.Start - desc.offset + delta, rs.Length)); + await Output.WriteLineAsync($"Unable to remove {line.FullFile}"); + } + } + public async Task StartHeaderAsync(VCFile file, VCFile support, TrialAndErrorRemovalOptions settings) + { + var document = await VS.Documents.GetDocumentViewAsync(file.FullPath); + VCFileConfiguration config = VCUtil.GetVCFileConfig(support); + if (config == null) return $"{support.Name} has failed to yield a config."; + + return !await TestCompileAsync(config) + ? $"{file.FullPath} failed to compile. Include removal stopped." + : await StartGenericAsync(config, document, settings); + } + //Expected: compilable file .cpp or other + public async Task StartAsync(VCFile file, TrialAndErrorRemovalOptions settings) + { + var document = await VS.Documents.GetDocumentViewAsync(file.FullPath); + VCFileConfiguration config = VCUtil.GetVCFileConfig(file); + if (config == null) return $"{file.Name} has failed to yield a config."; + + return !await TestCompileAsync(config) + ? $"{document.FilePath} failed to compile. Include removal stopped." + : await StartGenericAsync(config, document, settings); + } + + private async Task StartProgressDialogAsync(string documentName, int steps) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var dialog_factory = (IVsThreadedWaitDialogFactory)await VS.Services.GetThreadedWaitDialogAsync(); + var dialog = dialog_factory.CreateInstance(); + + + string waitMessage = $"Parsing '{documentName}' ... "; + dialog.StartWaitDialogWithPercentageProgress( + szWaitCaption: "Include Toolbox - Running Trial & Error Include Removal", + szWaitMessage: waitMessage, + szProgressText: null, + varStatusBmpAnim: null, + szStatusBarText: "Running Trial & Error Removal - " + waitMessage, + fIsCancelable: true, + iDelayToShowDialog: 0, + iTotalSteps: steps, + iCurrentStep: 0); + + return dialog; + } + } +} \ No newline at end of file diff --git a/IncludeToolboxShared/IncludeGraphViewModel.cs b/IncludeToolboxShared/IncludeGraphViewModel.cs new file mode 100644 index 0000000..f214e78 --- /dev/null +++ b/IncludeToolboxShared/IncludeGraphViewModel.cs @@ -0,0 +1,231 @@ +using IncludeToolbox.Formatter; +using IncludeToolbox.Graph; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Shell; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox.GraphWindow +{ + public class IncludeGraphViewModel : PropertyChangedBase + { + public HierarchyIncludeTreeViewItem HierarchyIncludeTreeModel { get; set; } = new HierarchyIncludeTreeViewItem(new IncludeGraph.Include(), ""); + public FolderIncludeTreeViewItem_Root FolderGroupedIncludeTreeModel { get; set; } = new FolderIncludeTreeViewItem_Root(null, null); + + public IncludeGraph Graph { get; private set; } + + + public enum RefreshMode + { + DirectParsing, + ShowIncludes, + } + + public static readonly string[] RefreshModeNames = new string[] { "Direct Parsing", "Compile /showIncludes" }; + + public RefreshMode ActiveRefreshMode + { + get => activeRefreshMode; + set + { + if (activeRefreshMode != value) + { + activeRefreshMode = value; + OnNotifyPropertyChanged(); + QueueUpdatingRefreshStatus(); + } + } + } + RefreshMode activeRefreshMode = RefreshMode.DirectParsing; + + public IEnumerable PossibleRefreshModes => Enum.GetValues(typeof(RefreshMode)).Cast(); + + public bool CanRefresh + { + get => canRefresh; + private set { canRefresh = value; OnNotifyPropertyChanged(); } + } + private bool canRefresh = false; + + public string RefreshTooltip + { + get => refreshTooltip; + set { refreshTooltip = value; OnNotifyPropertyChanged(); } + } + private string refreshTooltip = ""; + + public bool RefreshInProgress + { + get => refreshInProgress; + private set + { + refreshInProgress = value; + QueueUpdatingRefreshStatus(); + OnNotifyPropertyChanged(); + OnNotifyPropertyChanged(nameof(CanSave)); + } + } + private bool refreshInProgress = false; + + public string GraphRootFilename + { + get => graphRootFilename; + private set { graphRootFilename = value; OnNotifyPropertyChanged(); } + } + private string graphRootFilename = ""; + + public int NumIncludes + { + get => (Graph?.GraphItems.Count ?? 1) - 1; + } + + public bool CanSave + { + get => !refreshInProgress && Graph != null && Graph.GraphItems.Count > 0; + } + + // Need to keep these guys alive. + private EnvDTE.WindowEvents windowEvents; + + public IncludeGraphViewModel() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + // UI update on dte events. + var dte = VSUtils.GetDTE(); + if (dte != null) + { + windowEvents = dte.Events.WindowEvents; + windowEvents.WindowActivated += (x, y) => QueueUpdatingRefreshStatus(); + } + + QueueUpdatingRefreshStatus(); + } + + private void QueueUpdatingRefreshStatus() + { + _ = Task.Run(async () => + { + var currentDocument = VSUtils.GetDTE()?.ActiveDocument; + + if (RefreshInProgress) + { + CanRefresh = false; + RefreshTooltip = "Refresh in progress"; + } + // 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. + // This is why we just check for "having a document" here for now. + else if (currentDocument == null) + { + CanRefresh = false; + RefreshTooltip = "No open document"; + } + else + { + if (activeRefreshMode == RefreshMode.ShowIncludes) + { + var canPerformShowIncludeCompilation = await CompilationBasedGraphParser.CanPerformShowIncludeCompilation(currentDocument); + CanRefresh = canPerformShowIncludeCompilation.Result; + RefreshTooltip = canPerformShowIncludeCompilation.Reason; + } + else + { + CanRefresh = true; + RefreshTooltip = null; + } + } + }); + } + + public async Task RefreshIncludeGraph() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var currentDocument = VSUtils.GetDTE()?.ActiveDocument; + GraphRootFilename = currentDocument.Name ?? ""; + if (currentDocument == null) + return; + + var newGraph = new IncludeGraph(); + RefreshInProgress = true; + + try + { + switch (activeRefreshMode) + { + case RefreshMode.ShowIncludes: + if (await newGraph.AddIncludesRecursively_ShowIncludesCompilation(currentDocument, OnNewTreeComputed)) + { + ResetIncludeTreeModel(null); + } + break; + + case RefreshMode.DirectParsing: + ResetIncludeTreeModel(null); + var settings = (ViewerOptionsPage)IncludeToolboxPackage.Instance.GetDialogPage(typeof(ViewerOptionsPage)); + var includeDirectories = VSUtils.GetProjectIncludeDirectories(currentDocument.ProjectItem.ContainingProject); + var uiThreadDispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher; + await Task.Run( + async () => + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + newGraph.AddIncludesRecursively_ManualParsing(currentDocument.FullName, includeDirectories, settings.NoParsePaths); + await OnNewTreeComputed(newGraph, currentDocument, true); + }); + break; + + default: + throw new NotImplementedException(); + } + } + catch(Exception e) + { + Output.Instance.WriteLine("Unexpected error when refreshing Include Graph: {0}", e); + await OnNewTreeComputed(newGraph, currentDocument, false); + } + } + + private void ResetIncludeTreeModel(IncludeGraph.Item root) + { + HierarchyIncludeTreeModel.Reset(new IncludeGraph.Include() { IncludedFile = root }, ""); + OnNotifyPropertyChanged(nameof(HierarchyIncludeTreeModel)); + + FolderGroupedIncludeTreeModel.Reset(Graph?.GraphItems, root); + OnNotifyPropertyChanged(nameof(FolderGroupedIncludeTreeModel)); + + OnNotifyPropertyChanged(nameof(CanSave)); + } + + /// + /// Should be called after a tree was computed. Refreshes tree model. + /// + /// The include tree + /// This can be different from the active document at the time the refresh button was clicked. + /// Wheather the tree was created successfully + private async Task OnNewTreeComputed(IncludeGraph graph, EnvDTE.Document documentTreeComputedFor, bool success) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + RefreshInProgress = false; + + if (success) + { + this.Graph = graph; + + var includeDirectories = VSUtils.GetProjectIncludeDirectories(documentTreeComputedFor.ProjectItem.ContainingProject); + includeDirectories.Insert(0, PathUtil.Normalize(documentTreeComputedFor.Path) + Path.DirectorySeparatorChar); + + foreach (var item in Graph.GraphItems) + item.FormattedName = IncludeFormatter.FormatPath(item.AbsoluteFilename, FormatterOptionsPage.PathMode.Shortest_AvoidUpSteps, includeDirectories); + + ResetIncludeTreeModel(Graph.CreateOrGetItem(documentTreeComputedFor.FullName, out _)); + } + + OnNotifyPropertyChanged(nameof(NumIncludes)); + } + } +} diff --git a/IncludeToolboxShared/IncludeToolboxPackage.cs b/IncludeToolboxShared/IncludeToolboxPackage.cs new file mode 100644 index 0000000..ecb2088 --- /dev/null +++ b/IncludeToolboxShared/IncludeToolboxPackage.cs @@ -0,0 +1,42 @@ +using Task = System.Threading.Tasks.Task; +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; + +namespace IncludeToolbox +{ + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [InstalledProductRegistration(Vsix.Name, Vsix.Description, Vsix.Version)] + [ProvideMenuResource("Menus.ctmenu", 1)] + [Guid(PackageGuids.IncludeToolboxString)] + [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionOpening_string, PackageAutoLoadFlags.BackgroundLoad)] + [ProvideToolWindow(typeof(IncludeGraphToolWindow.Pane), Style = VsDockStyle.Float)] + [ProvideToolWindowVisibility(typeof(IncludeGraphToolWindow.Pane), VSConstants.UICONTEXT.SolutionExists_string, Name = "Include Graph")] + [ProvideOptionPage(typeof(OptionsProvider.FormatterOptionsPage), "Include Toolbox", "Include Format", 0, 0, true, SupportsProfiles = true)] + [ProvideOptionPage(typeof(OptionsProvider.TrialAndErrorRemovalOptionsPage), "Include Toolbox", "Trial and Error", 0, 0, true, SupportsProfiles = true)] + [ProvideOptionPage(typeof(OptionsProvider.MapperOptionsPage), "Include Toolbox", "Mapper", 0, 0, true, SupportsProfiles = true)] + [ProvideOptionPage(typeof(OptionsProvider.IWYUOptionsPage), "Include Toolbox", "Include-What-You-Use", 0, 0, true, SupportsProfiles = true)] + [ProvideOptionPage(typeof(OptionsProvider.ViewerOptionsPage), "Include Toolbox", "Include Viewer", 0, 0, true, SupportsProfiles = true)] + [ProvideUIContextRule(PackageGuids.GOnlyVCString, "UIOnlyVC", + expression: "one & two", + termNames: new[] { "one", "two" }, + termValues: new[] { @"ActiveProjectCapability:VisualC", @"HierSingleSelectionName:.(h|hpp|hxx|cpp|c|cxx)$" } +)] + [ProvideUIContextRule(PackageGuids.GHeaderOnlyString, "UIOnlyHead", + expression: "one & two", + termNames: new[] { "one", "two" }, + termValues: new[] { @"ActiveProjectCapability:VisualC", @"HierSingleSelectionName:.(h|hpp|hxx|inl)$" } +)] + public sealed class IncludeToolboxPackage : ToolkitPackage + { + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + { + await this.RegisterCommandsAsync(); + await Output.InitializeAsync(); + this.RegisterToolWindows(); + } + } +} diff --git a/IncludeToolboxShared/IncludeToolboxShared.projitems b/IncludeToolboxShared/IncludeToolboxShared.projitems new file mode 100644 index 0000000..ed53ba3 --- /dev/null +++ b/IncludeToolboxShared/IncludeToolboxShared.projitems @@ -0,0 +1,65 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + c50c4863-6200-4e51-837a-31febc09c8b2 + + + IncludeToolboxShared + + + + + + + + + + + + + + + + + + + + + + + + + + + + + IncludeGraphControl.xaml + + + + + + + + + + + + + + + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/IncludeToolboxShared/IncludeToolboxShared.shproj b/IncludeToolboxShared/IncludeToolboxShared.shproj new file mode 100644 index 0000000..516a1a2 --- /dev/null +++ b/IncludeToolboxShared/IncludeToolboxShared.shproj @@ -0,0 +1,13 @@ + + + + c50c4863-6200-4e51-837a-31febc09c8b2 + 14.0 + + + + + + + + diff --git a/IncludeToolboxShared/Options/FormatterOptions.cs b/IncludeToolboxShared/Options/FormatterOptions.cs new file mode 100644 index 0000000..a26bb93 --- /dev/null +++ b/IncludeToolboxShared/Options/FormatterOptions.cs @@ -0,0 +1,109 @@ +using Community.VisualStudio.Toolkit; +using System.ComponentModel; +using System.Linq; +using System.Runtime.InteropServices; + +namespace IncludeToolbox +{ + internal partial class OptionsProvider + { + [ComVisible(true)] + public class FormatterOptionsPage : BaseOptionPage { } + } + + public class FormatOptions : BaseOptionModel + { + [Category("Path")] + [DisplayName("Mode")] + [Description("Changes the path mode to the given pattern.")] + public PathMode PathFormat { get; set; } = PathMode.Shortest_AvoidUpSteps; + + [Category("Path")] + [DisplayName("Ignore File Relative")] + [Description("If true, include directives will not take the path of the file into account.")] + public bool IgnoreFileRelative { get; set; } = false; + + + + [Category("Formatting")] + [DisplayName("Delimiter Mode")] + [Description("Optionally changes all delimiters to either angle brackets <...> or quotes \"...\".")] + public DelimiterMode DelimiterFormatting { get; set; } = DelimiterMode.Unchanged; + + + [Category("Formatting")] + [DisplayName("Slash Mode")] + [Description("Changes all slashes to the given type.")] + public SlashMode SlashFormatting { get; set; } = SlashMode.ForwardSlash; + + [Category("Formatting")] + [DisplayName("Remove Empty Lines")] + [Description("If true, all empty lines of a include selection will be removed.")] + public bool RemoveEmptyLines { get; set; } = true; + + [Category("Formatting")] + [DisplayName("Ignore #if blocks")] + [Description("If true, all empty lines of an #if block include selection will be ignored.")] + public bool IgnoreIfdefs { get; set; } = true; + + + + [Category("Sorting")] + [DisplayName("Include delimiters in precedence regexes")] + [Description("If true, precedence regexes will consider delimiters (angle brackets or quotes.)")] + public bool RegexIncludeDelimiter { get; set; } = false; + + [Category("Sorting")] + [DisplayName("Insert blank line between precedence regex match groups")] + [Description("If true, a blank line will be inserted after each group matching one of the precedence regexes.")] + public bool BlankAfterRegexGroupMatch { get; set; } = false; + + [Category("Sorting")] + [DisplayName("Precedence Regexes")] + [Description("Earlier match means higher sorting priority.\n\"" + RegexUtils.CurrentFileNameKey + "\" will be replaced with the current file name without extension.")] + public string[] PrecedenceRegexes + { + get { return precedenceRegexes; } + set { precedenceRegexes = value.Where(x => x.Length > 0).ToArray(); } // Remove empty lines. + } + private string[] precedenceRegexes = new string[] { $"(?i){RegexUtils.CurrentFileNameKey}\\.(h|hpp|hxx|inl|c|cpp|cxx)(?-i)" }; + + + [Category("Sorting")] + [DisplayName("Sort by Include Type")] + [Description("Optionally put either includes with angle brackets <...> or quotes \"...\" first.")] + public TypeSorting SortByType { get; set; } = TypeSorting.QuotedFirst; + + [Category("Sorting")] + [DisplayName("Remove duplicates")] + [Description("If true, duplicate includes will be removed.")] + public bool RemoveDuplicates { get; set; } = true; + } + + + public enum PathMode + { + Unchanged, + Shortest, + Shortest_AvoidUpSteps, + Absolute, + } + public enum DelimiterMode + { + Unchanged, + AngleBrackets, + Quotes, + } + public enum SlashMode + { + Unchanged, + ForwardSlash, + BackSlash, + } + public enum TypeSorting + { + None, + AngleBracketsFirst, + QuotedFirst, + } +} diff --git a/IncludeToolboxShared/Options/IWYUOptions.cs b/IncludeToolboxShared/Options/IWYUOptions.cs new file mode 100644 index 0000000..f64d38b --- /dev/null +++ b/IncludeToolboxShared/Options/IWYUOptions.cs @@ -0,0 +1,187 @@ +using Community.VisualStudio.Toolkit; +using IncludeToolbox.Commands; +using IncludeToolbox.IncludeWhatYouUse; +using System; +using System.ComponentModel; +using System.Net; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace IncludeToolbox +{ + internal partial class OptionsProvider + { + [ComVisible(true)] + public class IWYUOptionsPage : BaseOptionPage { } + } + + public class IWYUOptions : BaseOptionModel + { + string exe = ""; + Comment comm = Comment.Yes; + Substitution subs = Substitution.Precise; + uint verbose = 2; + bool? download_required = null; + bool pch = false; + bool nodefault = false; + bool provided = true; + bool transitives = false; + bool warn = false; + bool always = false; + bool header = false; + bool format = false; + string mapping = ""; + string[] clang_options = new string[] { }; + string[] iwyu_options = new string[] { }; + + + + [Category("General")] + [DisplayName("IWYU Executable")] + [Description("A path to IWYU executable.")] + [DefaultValue("")] + public string Executable + { + get { return exe; } + set + { + OnChangeEvent(); + exe = value; + } + } + + [Category("General")] + [DisplayName("Print Commentaries")] + [Description("Tells IWYU to show or hide individual commentaries to headers.")] + [DefaultValue(Comment.Yes)] + [TypeConverter(typeof(EnumConverter))] + public Comment Comms { get { return comm; } set { OnChangeEvent(); comm = value; } } + + [Category("General")] + [DisplayName("Output Verbosity")] + [Description("Determines how much output needs to be printed. May help in case of error. Max value is 7.")] + [DefaultValue(2)] + public uint Verbosity{ get { return verbose; } set { OnChangeEvent(); verbose = Math.Min(value, 7); } } + + [Category("General")] + [DisplayName("Precompiled Header")] + [Description("Sets if first file in .cpp is precompiled header. Blocks first file from being parsed.")] + [DefaultValue(false)] + public bool Precompiled { get { return pch; } set { OnChangeEvent(); pch = value; } } + + [Category("General")] + [DisplayName("No Default Maps")] + [Description("If true, turns default gcc iwyu STL bindings off. Useful for STL map implementation.")] + [DefaultValue(false)] + public bool NoDefault { get { return nodefault; } set { OnChangeEvent(); nodefault = value; } } + + [Category("General")] + [DisplayName("Use Provided Maps")] + [Description("If true, uses provided MSVC mappings from repository. Their location is C:/Users/$(USERNAME)/iwyu/msvc.imp")] + [DefaultValue(true)] + public bool UseProvided { get { return provided; } set { OnChangeEvent(); provided = value; } } + + [Category("General")] + [DisplayName("Only Transitive")] + [Description("Do not suggest that a file add foo.h unless foo.h is already visible in the file's transitive includes.")] + [DefaultValue(false)] + public bool Transitives { get { return transitives; } set { OnChangeEvent(); transitives = value; } } + + [Category("General")] + [DisplayName("Show Warnings")] + [Description("Shows warnings from IWYU compiler.")] + [DefaultValue(false)] + public bool Warnings { get { return warn; } set { OnChangeEvent(); warn = value; } } + + [Category("General")] + [DisplayName("Always Rebuild")] + [Description("Rebuild the project command line on each call. Good for dynamic projects, that may change their options.")] + [DefaultValue(false)] + public bool AlwaysRebuid { get { return always; } set { OnChangeEvent(); always = value; } } + + [Category("General")] + [DisplayName("Substitution Mode")] + [Description("Choose the model of substitution for headers. If includes are scattered across the file, the mode is precise. Cheap is used when includes are a block before any code. If used wrong, Cheap may remove code between first and the last include.")] + [TypeConverter(typeof(EnumConverter))] + public Substitution Sub { get { return subs; } set { subs = value; OnChangeEvent(); } } + + + [Category("Options")] + [DisplayName("IWYU options")] + [Description("IWYU launch options, that determine the flow of include-what-you-use.")] + public string[] Options { get { return iwyu_options; } set { iwyu_options = value; OnChangeEvent(); } } + + [Category("Options")] + [DisplayName("Clang options")] + [Description("Clang launch options, that determine compilation stage flow.")] + public string[] ClangOptions { get { return clang_options; } set { clang_options = value; OnChangeEvent(); } } + + [Category("Options")] + [DisplayName("Mapping File")] + [Description("Specifies the mapping file to use by iwyu.")] + [DefaultValue("")] + public string MappingFile { get { return mapping; } set { mapping = value; OnChangeEvent(); } } + + [Category("Options")] + [DisplayName("Ignore Header")] + [Description("Ignores header of specified .cpp. Tries to find same-named .h in the includes. If it succeeds, the header is moved to the beginning and it is treated as precompiled. Useful when .h file is already refactored.")] + [DefaultValue(false)] + public bool IgnoreHeader { get { return header; } set { header = value; OnChangeEvent(); } } + + [Category("Options")] + [DisplayName("Format Includes")] + [Description("Uses formatting tool after results of IWYU are applied.")] + [DefaultValue(false)] + public bool Format { get => format; set { format = value; OnChangeEvent(); } } + + [Category("Options")] + [DisplayName("Format Document")] + [Description("Uses formatting command (Ctrl+K D) after results of IWYU are applied.")] + [DefaultValue(true)] + public bool FormatDoc { get; set; } = true; + + [Category("Options")] + [DisplayName("Move Forward Declarations")] + [Description("Moves all forward declarations present in the document. !Does not work with Cheap IWYU mode.")] + [DefaultValue(true)] + public bool MoveDecls { get; set; } = true; + + [Category("Options")] + [DisplayName("Remove Empty Namespaces")] + [Description("Removes empty namespaces from the file if finds any.")] + [DefaultValue(true)] + public bool RemoveENS { get; set; } = true; + + public async Task DownloadRequiredAsync() + { + if (download_required != null) return download_required.Value; + if (Executable == IWYUDownload.GetDefaultExecutablePath()) + download_required = await IWYUDownload.IsNewerVersionAvailableOnlineAsync(); + return download_required.Value; + } + public void Downloaded() + { + download_required = false; + } + + + public delegate void OnChangeDelegate(IWYUOptions options); + public event OnChangeDelegate OnChange; + + protected void OnChangeEvent() + { + OnChange?.Invoke(this); + } + } + + public enum Comment + { + Yes, + No + } + public enum Substitution + { + Cheap, + Precise + } +} diff --git a/IncludeToolboxShared/Options/MapperOptions.cs b/IncludeToolboxShared/Options/MapperOptions.cs new file mode 100644 index 0000000..767bb33 --- /dev/null +++ b/IncludeToolboxShared/Options/MapperOptions.cs @@ -0,0 +1,68 @@ +using Community.VisualStudio.Toolkit; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace IncludeToolbox +{ + internal partial class OptionsProvider + { + [ComVisible(true)] + public class MapperOptionsPage : BaseOptionPage { } + } + + public class MapperOptions : BaseOptionModel + { + string map_path = ""; + private MapManager map = new(); + + [Browsable(false)] + public MapManager Map { get { return map; } } + + [Category("General")] + [DisplayName("Mapping file")] + [Description("File to write results to.")] + [DefaultValue("")] + public string MapFile { get => map_path; set + { + if (map_path == value) return; + map_path = value; + map.Load(map_path); + } + } + + + + [Category("General")] + [DisplayName("Relative File Prefix")] + [Description("Prefix for relative file path stored into map. e.g. C:\\users\\map\\a.h with prefix C:\\users will write to the final map.")] + [DefaultValue("")] + public string Prefix { get; set; } = ""; + + [Category("General")] + [DisplayName("Mapping preference")] + [Description("Choose to prefer one option of inclusion over other. In case other than default the other option is marked as private and will be replaced.")] + [DefaultValue(MappingPreference.Default)] + public MappingPreference Preference { get; set; } = MappingPreference.Default; + + [Category("General")] + [DisplayName("Ignore #ifdefs")] + [Description("If true, the headers inside ifdef blocks are treated as unavailable. May be removed with preprocessor introduction.")] + [DefaultValue(false)] + public bool Ignoreifdefs { get; set; } = false; + + public async Task IsInMapAsync() + { + var doc = await VS.Documents.GetActiveDocumentViewAsync(); + var str = Utils.MakeRelative(Prefix, doc.FilePath).Replace('\\', '/'); + return map.Map.ContainsKey(str); + } + } + + public enum MappingPreference + { + Default, + Quotes, + AngleBrackets + } +} diff --git a/IncludeToolboxShared/Options/TrialAndErrorRemovalOptions.cs b/IncludeToolboxShared/Options/TrialAndErrorRemovalOptions.cs new file mode 100644 index 0000000..1ea5cae --- /dev/null +++ b/IncludeToolboxShared/Options/TrialAndErrorRemovalOptions.cs @@ -0,0 +1,46 @@ +using Community.VisualStudio.Toolkit; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace IncludeToolbox +{ + internal partial class OptionsProvider + { + [ComVisible(true)] + public class TrialAndErrorRemovalOptionsPage : BaseOptionPage { } + } + + public class TrialAndErrorRemovalOptions : BaseOptionModel + { + [Category("General")] + [DisplayName("Removal Order")] + [Description("Gives the order which #includes are removed.")] + public IncludeRemovalOrder RemovalOrder { get; set; } = IncludeRemovalOrder.BottomToTop; + + [Category("General")] + [DisplayName("Ignore First Include")] + [Description("If true, the first include of a file will never be removed (useful for ignoring PCH).")] + public bool IgnoreFirstInclude { get; set; } = true; + + [Category("General")] + [DisplayName("Ignore Preprocessor Blocks")] + [Description("If true, the includes situated in preprocessor #if blocks are ignored.")] + public bool IgnoreIfdefs { get; set; } = true; + + [Category("General")] + [DisplayName("Ignore List")] + [Description("List of regexes. If the content of a #include directive match with any of these, it will be ignored." + + "\n\"" + RegexUtils.CurrentFileNameKey + "\" will be replaced with the current file name without extension.")] + public string[] IgnoreList { get; set; } = new string[] { $"(\\/|\\\\|^){RegexUtils.CurrentFileNameKey}\\.(h|hpp|hxx|inl|c|cpp|cxx)$", ".inl", "_inl.h" }; + + [Category("General")] + [DisplayName("Keep Line Breaks")] + [Description("If true, removed includes will leave an empty line.")] + public bool KeepLineBreaks { get; set; } = false; + } + public enum IncludeRemovalOrder + { + BottomToTop, + TopToBottom, + } +} diff --git a/IncludeToolboxShared/Options/ViewerOptionsPage.cs b/IncludeToolboxShared/Options/ViewerOptionsPage.cs new file mode 100644 index 0000000..55d791b --- /dev/null +++ b/IncludeToolboxShared/Options/ViewerOptionsPage.cs @@ -0,0 +1,56 @@ +using Community.VisualStudio.Toolkit; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace IncludeToolbox +{ + internal partial class OptionsProvider + { + [ComVisible(true)] + public class ViewerOptionsPage : BaseOptionPage { } + } + + public class ViewerOptions : BaseOptionModel + { + [Category("Include Graph Parsing")] + [DisplayName("Graph Endpoint Directories")] + [Description("List of absolute directory paths. For any include below these paths, the graph parsing will stop.")] + public string[] NoParsePaths + { + get { return noParsePaths; } + set + { + // It is critical that the paths are "exact" since we want to use them as with string comparison. + noParsePaths = value; + for (int i = 0; i < noParsePaths.Length; ++i) + noParsePaths[i] = Utils.GetExactPathName(noParsePaths[i]); + } + } + private string[] noParsePaths = new string[0]; + + [Category("Include Graph DGML")] + [DisplayName("Create Group Nodes by Folders")] + [Description("Creates folders like in the folder hierarchy view of Include Graph.")] + public bool CreateGroupNodesForFolders { get; set; } = true; + + [Category("Include Graph DGML")] + [DisplayName("Expand Folder Group Nodes")] + [Description("If true all folder nodes start out expanded, otherwise they are collapsed.")] + public bool ExpandFolderGroupNodes { get; set; } = false; + + [Category("Include Graph DGML")] + [DisplayName("Colorize by Number of Includes")] + [Description("If true each node gets color coded according to its number of unique transitive includes.")] + public bool ColorCodeNumTransitiveIncludes { get; set; } = true; + + [Category("Include Graph DGML")] + [DisplayName("No Children Color")] + [Description("See \"Colorize by Number of Includes\". Color for no children at all.")] + public System.Drawing.Color NoChildrenColor { get; set; } = System.Drawing.Color.White; + + [Category("Include Graph DGML")] + [DisplayName("Max Children Color")] + [Description("See \"Colorize by Number of Includes\". Color for highest number of children.")] + public System.Drawing.Color MaxChildrenColor { get; set; } = System.Drawing.Color.Red; + } +} diff --git a/IncludeToolboxShared/ToolWindows/View/IncludeGraphControl.xaml b/IncludeToolboxShared/ToolWindows/View/IncludeGraphControl.xaml new file mode 100644 index 0000000..d75a434 --- /dev/null +++ b/IncludeToolboxShared/ToolWindows/View/IncludeGraphControl.xaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IncludeToolboxShared/ToolWindows/View/IncludeGraphControl.xaml.cs b/IncludeToolboxShared/ToolWindows/View/IncludeGraphControl.xaml.cs new file mode 100644 index 0000000..d9fc0c5 --- /dev/null +++ b/IncludeToolboxShared/ToolWindows/View/IncludeGraphControl.xaml.cs @@ -0,0 +1,38 @@ +using Community.VisualStudio.Toolkit; +using IncludeToolbox.Commands; +using Microsoft.VisualStudio.Shell; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace IncludeToolbox +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class IncludeGraphControl : UserControl + { + public IncludeGraphControl() + { + InitializeComponent(); + DataContext = new IncludeGraphViewModel(); + } + + private void OnIncludeTreeItemMouseDown(object sender, MouseButtonEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed && e.ClickCount == 2) + { + if (sender is FrameworkElement frameworkElement) + { + if (frameworkElement.DataContext is IncludeTreeViewItem treeItem) // Arguably a bit hacky to go over the DataContext, but it seems to be a good direct route. + { + treeItem.NavigateToIncludeAsync().FireAndForget(); + } + } + } + } + } +} diff --git a/IncludeToolboxShared/ToolWindows/View/IncludeGraphToolWindow.cs b/IncludeToolboxShared/ToolWindows/View/IncludeGraphToolWindow.cs new file mode 100644 index 0000000..98c7df6 --- /dev/null +++ b/IncludeToolboxShared/ToolWindows/View/IncludeGraphToolWindow.cs @@ -0,0 +1,33 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Imaging; +using Microsoft.VisualStudio.Shell; +using System; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox +{ + public class IncludeGraphToolWindow : BaseToolWindow + { + public override string GetTitle(int toolWindowId) => "Include Graph"; + + public override Type PaneType => typeof(Pane); + + public override Task CreateAsync(int toolWindowId, CancellationToken cancellationToken) + { + return Task.FromResult(new IncludeGraphControl()); + } + + [Guid("dc2e50f8-d627-4f55-9095-5d783ad9d475")] + internal class Pane : ToolWindowPane + { + public Pane() + { + BitmapImageMoniker = KnownMonikers.ToolWindow; + } + } + } +} \ No newline at end of file diff --git a/IncludeToolboxShared/ToolWindows/View/ToolWindowStyle.xaml b/IncludeToolboxShared/ToolWindows/View/ToolWindowStyle.xaml new file mode 100644 index 0000000..aa02c18 --- /dev/null +++ b/IncludeToolboxShared/ToolWindows/View/ToolWindowStyle.xaml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/IncludeToolboxShared/ToolWindows/ViewModel/HierarchyIncludeTreeViewItem.cs b/IncludeToolboxShared/ToolWindows/ViewModel/HierarchyIncludeTreeViewItem.cs new file mode 100644 index 0000000..9c38125 --- /dev/null +++ b/IncludeToolboxShared/ToolWindows/ViewModel/HierarchyIncludeTreeViewItem.cs @@ -0,0 +1,109 @@ +using Community.VisualStudio.Toolkit; +using System.Collections.Generic; +using Path = System.IO.Path; +using Task = System.Threading.Tasks.Task; + +namespace IncludeToolbox +{ + public class HierarchyIncludeTree + { + protected IReadOnlyList cachedItems; + public HierarchyIncludeTree() + { + cachedItems = empty; + } + public HierarchyIncludeTree(Include[] root_inc, IGraphModel model_ref, string root_file) + { + var cachedItemsList = new List(); + + foreach (var include in root_inc) + cachedItemsList.Add(new HierarchyIncludeTreeViewItem(include, root_file, model_ref)); + cachedItems = cachedItemsList; + } + + public IReadOnlyList Children => cachedItems; + private static readonly List empty = new(); + } + + + public class HierarchyIncludeTreeViewItem : IncludeTreeViewItem + { + private string ParentFilename = null; + private IGraphModel model_ref; + private int line = 0; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits", Justification = "Synchronous property")] + public override IReadOnlyList Children + { + get + { + if (cachedItems == null) + GenerateChildItemsAsync().Wait(); + return cachedItems; + } + } + protected IReadOnlyList cachedItems; + + public HierarchyIncludeTreeViewItem(Include include, string ParentFilename, IGraphModel model_ref) + { + Reset(include, ParentFilename); + this.model_ref = model_ref; + } + private async Task GenerateChildItemsAsync() + { + if (AbsoluteFilename == Name) + return; + + var item = await model_ref.TryEmplaceAsync(AbsoluteFilename, Name); + var cachedItemsList = new List(); + foreach (var include in item.Includes) + cachedItemsList.Add(new HierarchyIncludeTreeViewItem(include, AbsoluteFilename, model_ref)); + cachedItems = cachedItemsList; + } + + public void Reset(Include include, string includingFileAbsoluteFilename) + { + line = include.Line; + cachedItems = null; + Name = include.File; + AbsoluteFilename = include.AbsolutePath; + ParentFilename = includingFileAbsoluteFilename; + } + + public override async Task NavigateToIncludeAsync() + { + // Want to navigate to origin of this include, not target if possible + if (ParentFilename == null && !Path.IsPathRooted(ParentFilename)) return; + var doc = await VS.Documents.OpenAsync(ParentFilename); + if (doc == null) return; + + var lines = doc.TextView.TextViewLines; + if(line >= lines.Count) return; + + var sel_line = lines[line]; + doc.TextView.ViewScroller.EnsureSpanVisible(sel_line.Extent); + doc.TextView.Caret.MoveTo(sel_line); + + + //{ + + // var fileWindow = VSUtils.OpenFileAndShowDocument(ParentFilename); + + // // Try to move to carret if possible. + // if (include.IncludeLine != null) + // { + // var textDocument = fileWindow.Document.Object() as EnvDTE.TextDocument; + + // if (textDocument != null) + // { + // var includeLinePoint = textDocument.StartPoint.CreateEditPoint(); + // includeLinePoint.MoveToLineAndOffset(include.IncludeLine.LineNumber+1, 1); + // includeLinePoint.TryToShow(); + + // textDocument.Selection.MoveToPoint(includeLinePoint); + // } + // } + //} + } + } +} diff --git a/IncludeToolboxShared/ToolWindows/ViewModel/IncludeGraphViewModel.cs b/IncludeToolboxShared/ToolWindows/ViewModel/IncludeGraphViewModel.cs new file mode 100644 index 0000000..ce5d3a3 --- /dev/null +++ b/IncludeToolboxShared/ToolWindows/ViewModel/IncludeGraphViewModel.cs @@ -0,0 +1,34 @@ +using Community.VisualStudio.Toolkit; +using System.Threading.Tasks; + +namespace IncludeToolbox +{ + public class IncludeGraphViewModel : PropertyNotify + { + public IncludeGraphViewModel() { } + + + private IncludeGraph model = new(); + private HierarchyIncludeTree view = new(); + private string root_file = ""; + private int incs = 0; + + public HierarchyIncludeTree HierarchyIncludeTreeModel { get => view; private set => SetProperty(ref view, value); } + + + public string RootFilename { get => root_file; set { SetProperty(ref root_file, value); } } + public int NumIncludes + { + get => incs; + set => SetProperty(ref incs, value); + } + + public async Task RecalculateForAsync(DocumentView document) + { + var root = await model.InitializeAsync(document); + HierarchyIncludeTreeModel = new(root.Includes, model, root.AbsoluteFilename); + RootFilename = root.FormattedName; + NumIncludes = root.Includes.Length; + } + } +} \ No newline at end of file diff --git a/IncludeToolboxShared/ToolWindows/ViewModel/IncludeTreeViewItem.cs b/IncludeToolboxShared/ToolWindows/ViewModel/IncludeTreeViewItem.cs new file mode 100644 index 0000000..ef6afed --- /dev/null +++ b/IncludeToolboxShared/ToolWindows/ViewModel/IncludeTreeViewItem.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace IncludeToolbox +{ + public abstract class IncludeTreeViewItem : PropertyNotify + { + static protected IReadOnlyList emptyList = new IncludeTreeViewItem[0]; + + string name = ""; + string abs_name = ""; + public string Name { get => name; protected set => SetProperty(ref name, value); } + public string AbsoluteFilename { get => abs_name; protected set => SetProperty(ref abs_name, value); } + public abstract IReadOnlyList Children { get; } + + abstract public Task NavigateToIncludeAsync(); + } +} diff --git a/IncludeToolboxShared/Util/EmptyNS.cs b/IncludeToolboxShared/Util/EmptyNS.cs new file mode 100644 index 0000000..8c256ac --- /dev/null +++ b/IncludeToolboxShared/Util/EmptyNS.cs @@ -0,0 +1,156 @@ +using Microsoft.VisualStudio.Text; +using System; +using System.Collections.Generic; +using static IncludeToolbox.Lexer; + +namespace IncludeToolbox +{ + internal struct NSTracker + { + Stack> nsscan = new(); + public int Start { get; set ; } = 0; + public bool Empty { get; set; } = true; + + public NSTracker() + { + } + + public void Push() + { + nsscan.Push(new(Start, Empty)); + Empty = true; + } + public Span Pop(int end) + { + var v = nsscan.Pop(); + Empty = v.Value && Empty; + return new(v.Key, end - v.Key); + } + public void Drop() + { + if (nsscan.Count > 0) + nsscan.Pop(); + Empty = false; + } + } + + public static partial class Parser + { + static bool LLTableEN(ref Stack context, TType input, TType current) + { + switch (input) + { + case TType.Namespace: + switch (current) + { + case TType.T0: + context.Push(TType.T0); + context.Push(TType.T1); + return true; + case TType.T1: + case TType.T5: + context.Push(TType.CloseBr); + context.Push(TType.T3); + context.Push(TType.OpenBr); + context.Push(TType.T2); + context.Push(TType.Namespace); + return true; + case TType.T3: + context.Push(TType.T3); + context.Push(TType.T5); + return true; + } + break; + case TType.OpenBr: + return current == TType.T2 || current == TType.T4; + case TType.ID: + if (current == TType.T2) + { + context.Push(TType.T4); + context.Push(TType.ID); + return true; + } + break; + case TType.Colon: + if (current == TType.T4) + { + context.Push(TType.T4); + context.Push(TType.ID); + context.Push(TType.Colon); + return true; + } + break; + case TType.CloseBr: + return current == TType.T3; + default: + break; + } + return false; + } + public static Span[] ParseEmptyNamespaces(string text) + { + List namespaces = new(); + Parser.Context pctx = new(); + Lexer.Context lctx = new(text.AsSpan()); + Token tok = lctx.GetToken(false); + + NSTracker tracker = new(); + + bool accept = false; + + while (pctx.expected_tokens.Count != 0 && tok.valid()) + { + TType expect = pctx.expected_tokens.Peek(); + + if (expect >= TType.T0) //LL rules + { + pctx.expected_tokens.Pop(); + accept = LLTableEN(ref pctx.expected_tokens, tok.Type, expect); + continue; + } + if (!accept && expect != tok.Type) + { + tracker.Empty = false; + pctx.Clear(); // unexpected token, start anew + if (tok.Type == TType.OpenBr) // if scope, probably function or class + FFWD(ref lctx, (int)pctx.Scope, (int)pctx.Scope + 1); + if (tok.Type == TType.CloseBr) + { + pctx--; + tracker.Drop(); + } + tok = lctx.GetToken(accept); + continue; + } + + pctx.expected_tokens.Pop(); + + switch (expect) + { + case TType.Namespace: + tracker.Start = tok.Position; + tracker.Push(); + break; + case TType.CloseBr: + if (tracker.Empty) + { + var c = tracker.Pop(tok.End); + namespaces.RemoveAll(x => c.Contains(x)); + namespaces.Add(c); + } + break; + default: + break; + } + + if (tok.Type == TType.OpenBr) + pctx++; + if (tok.Type == TType.CloseBr) + pctx--; + tok = lctx.GetToken(accept); + } + + return namespaces.ToArray(); + } + } +} diff --git a/IncludeToolboxShared/Util/Lexer.cs b/IncludeToolboxShared/Util/Lexer.cs new file mode 100644 index 0000000..65c9cf6 --- /dev/null +++ b/IncludeToolboxShared/Util/Lexer.cs @@ -0,0 +1,390 @@ +using System; + +namespace IncludeToolbox +{ + public class Lexer + { + public struct Desc + { + public bool ignore_ifdefs = true; + public bool newlines = false; + public bool commentaries = false; + + public Desc() + { + } + } + public enum TType + { + Null, + Terminal, + Newline, + Commentary, + MLCommentary, + Namespace, + Class, + Struct, + Enum, + EnumClass, + ID, + AngleID, + QuoteID, + Colon, + Include, + Semicolon, + OpenBr, + CloseBr, + + Pragma, + + If, + Ifdef, + Ifndef, + + Elif, + Else, + Elifdef, + + Endif, + + T0, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + + } + public ref struct Token + { + private readonly TType type; + private readonly ReadOnlySpan value; + private readonly int pos = 0; + + public int Position { get { return pos; } } + public int End { get { return pos + Value.Length; } } + public ReadOnlySpan Value => value; + public TType Type => type; + public bool IsPreprocStart => type >= TType.If && type <= TType.Ifndef; + public bool IsPreprocEnd => type == TType.Endif; + + public Token(TType type, int pos, ReadOnlySpan value) + { + this.type = type; + this.value = value; + this.pos = pos; + } + public Token(TType type = TType.Null, int pos = 0) + { + this.type = type; + this.value = null; + this.pos = pos; + } + public bool valid() + { + return Type != TType.Null; + } + } + + public ref struct Context + { + private readonly ReadOnlySpan original; + private ReadOnlySpan code; + private int current_pos = 0; + + public int Position { get => current_pos; } + + public Context(ReadOnlySpan code) + { + this.code = code; + original = code; + } + + public char Fetch() + { + char c = Prefetch(); + if (c == 0) return c; + current_pos++; + code = code.Slice(1); + return c; + } + public char Prefetch() + { + return Empty() ? (char)0 : code[0]; + } + + public readonly bool Empty() + { + return code.Length == 0; + } + internal void SkipComment() + { + int rem = code.IndexOfAny('\r', '\n'); + if (rem == -1) + { + code = "".AsSpan(); + return; + } + RemovePrefix(rem); + } + internal Token TakeComment() + { + int rem = code.IndexOfAny('\r', '\n'); + if (rem == -1) + { + code = "".AsSpan(); + return new(TType.Commentary, current_pos - 1, original.Slice(current_pos - 1)); + } + Token tk = MakeValueToken(TType.Commentary, rem + 1); + RemovePrefix(rem); + return tk; + } + + internal void SkipCommentML() + { + _ = Fetch(); //remove first * + while (true) + { + int rem = code.IndexOf('*'); + RemovePrefix(rem + 1); + if (rem == -1) return; + if (Prefetch() == '/') + { + RemovePrefix(1); + return; + } + } + } + internal Token TakeCommentML() + { + int start = current_pos - 1; + + _ = Fetch(); //remove first * + while (true) + { + int rem = code.IndexOf('*'); + RemovePrefix(rem + 1); + if (rem == -1) return new(); + if (Prefetch() == '/') + { + RemovePrefix(1); + return MakeValueToken(TType.MLCommentary, start, current_pos - start); + } + } + } + + private void RemovePrefix(int n) + { + if (n < 0) { current_pos = code.Length - 1; return; } + code = code.Slice(n); + current_pos += n; + } + + private int FindDelim() + { + int i = 0; + while (i < code.Length && (char.IsLetterOrDigit(code[i]) || code[i] == '_')) i++; + return i; + } + private int FindBrace(char brace) + { + int i = 0; + + while (i < code.Length && code[i] != (brace == '<' ? '>' : brace)) + { if (code[i] == '\n') return -1; i++; } + return i + 1; + } + + private bool IsDelim(char c) + { + return !char.IsLetterOrDigit(c) && c != '_'; + } + + internal Token TryAssociateWith(ReadOnlySpan tk, TType type) + { + int pos = FindDelim(); + var sl = code.Slice(0, pos); + bool a = sl.SequenceEqual(tk); + + if (!a) return new(); + Token t = new(type, current_pos - 1); + RemovePrefix(pos); + return t; + } + + internal Token GetID() + { + var len = FindDelim(); + Token o = new(TType.ID, current_pos - 1, original.Slice(current_pos - 1, len + 1)); + RemovePrefix(len); + return o; + } + internal Token GetHeader(char delim) + { + var len = FindBrace(delim); + if (len == -1) return new(); + Token o = new(delim == '<' ? TType.AngleID : TType.QuoteID, current_pos - 1, original.Slice(current_pos - 1, len + 1)); + RemovePrefix(len); + return o; + } + internal void Skip() + { + int pos = FindDelim(); + RemovePrefix(pos); + } + internal void SkipSpace() + { + int i = 0; + while (i < code.Length && char.IsWhiteSpace(code[i])) i++; + RemovePrefix(i); + } + + private Token MakeValueToken(TType type, int pos, int length) + { + return new(type, pos, original.Slice(pos, length)); + } + private Token MakeValueToken(TType type, int length) + { + return MakeValueToken(type, current_pos - 1, length); + } + + private Token GetToken(bool expect_id, Desc desc = default) + { + Token tk = new(); + while (!Empty()) + { + char c = Fetch(); + + if (desc.newlines) + { + switch (c) + { + case '\n': return MakeValueToken(TType.Newline, 1); + case '\r': + if (Prefetch() == '\n') + { + tk = MakeValueToken(TType.Newline, 2); + Fetch(); + return tk; + } + return MakeValueToken(TType.Newline, 1); + } + } + + switch (c) + { + case '/': + switch (Prefetch()) + { + case '/': + if (desc.commentaries) + return TakeComment(); + SkipComment(); + if (desc.newlines) + return new Token(TType.Newline); + break; + case '*': + if (desc.commentaries) + return TakeCommentML(); + SkipCommentML(); + break; + default: break; + } + continue; + case 'n': + tk = TryAssociateWith("amespace".AsSpan(), TType.Namespace); + break; + case 'c': + tk = TryAssociateWith("lass".AsSpan(), TType.Class); + break; + case 's': + tk = TryAssociateWith("truct".AsSpan(), TType.Struct); + break; + case '#': + //stupid, but rarely occurring + if (desc.ignore_ifdefs) + { + c = Prefetch(); + switch (c) + { + case 'i': tk = TryAssociateWith("include".AsSpan(), TType.Include); break; + case 'p': tk = TryAssociateWith("pragma".AsSpan(), TType.Pragma); break; + } + } + else + { + c = Prefetch(); + switch (c) + { + case 'i': + tk = TryAssociateWith("include".AsSpan(), TType.Include); + if (tk.valid()) return tk; + tk = TryAssociateWith("if".AsSpan(), TType.If); + if (tk.valid()) return tk; + tk = TryAssociateWith("ifdef".AsSpan(), TType.Ifdef); + if (tk.valid()) return tk; + tk = TryAssociateWith("ifndef".AsSpan(), TType.Ifndef); + if (tk.valid()) return tk; + break; + case 'p': + tk = TryAssociateWith("pragma".AsSpan(), TType.Pragma); break; + case 'e': + tk = TryAssociateWith("elif".AsSpan(), TType.Elif); + if (tk.valid()) return tk; + tk = TryAssociateWith("else".AsSpan(), TType.Else); + if (tk.valid()) return tk; + tk = TryAssociateWith("elifdef".AsSpan(), TType.Elifdef); + if (tk.valid()) return tk; + tk = TryAssociateWith("endif".AsSpan(), TType.Endif); + if (tk.valid()) return tk; + break; + } + } + break; + case 'e': + tk = TryAssociateWith("num".AsSpan(), TType.Enum); + break; + case ':': + if (Prefetch() == ':') + { + tk = new Token(TType.Colon, Position); + _ = Fetch(); //skip the second + } + break; + case ';': + tk = MakeValueToken(TType.Semicolon, 1); + break; + case '<': + case '"': + if (expect_id) + tk = GetHeader(c); + break; + case '{': + tk = MakeValueToken(TType.OpenBr, 1); + break; + case '}': + tk = MakeValueToken(TType.CloseBr, 1); + break; + default: + break; + } + + if (tk.valid()) return tk; + if (expect_id && !char.IsWhiteSpace(c)) + return GetID(); + else if (!char.IsWhiteSpace(c)) + Skip(); + } + return tk; + } + public Token GetToken(bool expect_id, bool ignore_ifdefs = true, bool newlines = false, bool commentaries = false) + { + return GetToken(expect_id, new() { ignore_ifdefs = ignore_ifdefs, newlines = newlines, commentaries = commentaries }); + } + } + } +} diff --git a/IncludeToolboxShared/Util/MapManager.cs b/IncludeToolboxShared/Util/MapManager.cs new file mode 100644 index 0000000..851ee41 --- /dev/null +++ b/IncludeToolboxShared/Util/MapManager.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace IncludeToolbox +{ + public class MapManager + { + readonly Regex regExp = new("^\\s*{\\s*include:\\s*\\[\\s*\\\"(?:(?:\\\\\")|<)([\\w\\/\\.]*)(?:(?:\\\\\")|>)\\\"\\s*,\\s*(?:public|private),\\s\\\"(?:(?:\\\\\")|<)([\\w\\/\\.]*)(?:(?:\\\\\")|>)\\\"\\s*,\\s*(?:public|private)"); + Dictionary map = new(); + + public Dictionary Map { get { return map; } } + + public void Load(string map_path) + { + map.Clear(); + if(string.IsNullOrEmpty(map_path)||!File.Exists(map_path))return; + + var strings = File.ReadAllLines(map_path); + + foreach (string s in strings) + { + var m = regExp.Match(s); + if (!m.Success) continue; + if (!map.ContainsKey(m.Groups[2].Value)) + map.Add(m.Groups[2].Value, ""); + map[m.Groups[2].Value] += s + "\n"; + } + } + public async Task WriteMapAsync(MapperOptions settings) + { + FileStream file = File.OpenWrite(settings.MapFile); + List array = new(); + + await file.WriteAsync(Encoding.ASCII.GetBytes("[\n"), 0, 2); + + foreach (var f in map) + { + var c = "#" + f.Key + "\n"; + array.Append(file.WriteAsync(Encoding.ASCII.GetBytes(c), 0, c.Length)); + array.Append(file.WriteAsync(Encoding.ASCII.GetBytes(f.Value), 0, f.Value.Length)); + } + + foreach (var f in array) + await f; + + await file.WriteAsync(Encoding.ASCII.GetBytes("]\n"), 0, 2); + + file.Close(); + } + public void TryRemoveValue(string key) + { + map.Remove(key); + } + } +} diff --git a/IncludeToolboxShared/Util/NamespaceTree.cs b/IncludeToolboxShared/Util/NamespaceTree.cs new file mode 100644 index 0000000..76da6db --- /dev/null +++ b/IncludeToolboxShared/Util/NamespaceTree.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using static IncludeToolbox.Lexer; + +namespace IncludeToolbox +{ + public class DeclNode + { + private readonly TType _type; + private readonly Dictionary _children = new(); + + public string LineBreak { get; set; } = "\n"; + + public DeclNode(TType ty = TType.Null) + { + _type = ty; + } + + public void AddChild(IEnumerable namespaces, FWDDecl decl) + { + if (namespaces.Count() == 0) + { + _children.Add(decl.ID, new(decl.type)); + return; + } + if (!_children.ContainsKey(namespaces.First())) + _children.Add(namespaces.First(), new DeclNode(TType.Namespace)); + _children[namespaces.First()].AddChild(namespaces.Skip(1), decl); + } + public void AddChild(FWDDecl value) + { + var x = value.namespaces; + AddChild(x.Reverse().Skip(1), value); + } + public void AddChildren(IEnumerable decls) + { + foreach (var declsItem in decls) + AddChild(declsItem); + } + + private string Type() + { + return _type switch + { + TType.Namespace => "namespace", + TType.Class => "class", + TType.Struct => "struct", + TType.Enum => "enum", + TType.EnumClass => "enum class", + _ => "", + }; + } + + + private string ToString(bool c17, string s) + { + var o = ""; + if (_type != TType.Namespace) + return $"{Type()} {s};" + LineBreak; + + if (c17 && _children.Count == 1 && _children.First().Value._type == TType.Namespace) + return $"namespace {s}::{ new string(_children.First().Value.ToString(c17, _children.First().Key).Skip(10).ToArray())}" + LineBreak; + + foreach (var item in _children) + o += '\t' + item.Value.ToString(c17, item.Key); + return $"namespace {s}{LineBreak}{{{LineBreak}{o}}}{LineBreak}"; + } + public string ToString(bool c17) + { + var o = ""; + foreach (var item in _children) + o += item.Value.ToString(c17, item.Key); + return o; + } + } +} diff --git a/IncludeToolboxShared/Util/Output.cs b/IncludeToolboxShared/Util/Output.cs new file mode 100644 index 0000000..433561e --- /dev/null +++ b/IncludeToolboxShared/Util/Output.cs @@ -0,0 +1,30 @@ +using Community.VisualStudio.Toolkit; +using System.Threading.Tasks; + +namespace IncludeToolbox +{ + internal static class Output + { + static private OutputWindowPane pane; + static public async Task InitializeAsync() + { + pane = await VS.Windows.CreateOutputWindowPaneAsync("Include Toolbox"); + } + static public async Task WriteLineAsync(string str) + { + await pane.WriteLineAsync(str); + } + static public void WriteLine(string str) + { + pane.WriteLine(str); + } + static public async Task BringForwardAsync() + { + await pane.ActivateAsync(); + } + static public async Task ClearAsync() + { + await pane.ClearAsync(); + } + } +} \ No newline at end of file diff --git a/IncludeToolboxShared/Util/ParseIncludes.cs b/IncludeToolboxShared/Util/ParseIncludes.cs new file mode 100644 index 0000000..724bedc --- /dev/null +++ b/IncludeToolboxShared/Util/ParseIncludes.cs @@ -0,0 +1,106 @@ +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using static IncludeToolbox.Lexer; +using static Microsoft.VisualStudio.VSConstants; + +namespace IncludeToolbox +{ + public static partial class Parser + { + static readonly Regex pragma = new("(?:\\/\\*|\\/\\/)(?:\\s*IWYU\\s+pragma:\\s+keep)");// IWYU pragma: keep + + + + public static IncludeLine[] ParseInclues(ReadOnlySpan text, bool ignore_ifdefs = true) + { + if (text.IsEmpty) return new IncludeLine[0]; + List lines = new(); + Lexer.Context lctx = new(text); + + + IncludeLine xline = new(); + bool skip = false; + bool accept = true; + bool comments = false; + + int line = 0; + int start_pos = 0; + int end_pos = 0; + + + while (!lctx.Empty()) + { + Token tok = lctx.GetToken(accept, ignore_ifdefs, true, comments); + switch (tok.Type) + { + case TType.Newline: + comments = accept = false; + line++; + if (xline.Valid) + { + xline.newlineChar = tok.Value.ToString() switch + { + "\n" => NewlineChar.LF, + "\r" => NewlineChar.CR, + "\r\n" => NewlineChar.CRLF, + _ => NewlineChar.N + }; + xline.span = new(start_pos, tok.End - start_pos); + lines.Add(xline); + xline = new IncludeLine(); + } + break; + case TType.Include: + accept = !skip; + start_pos = tok.Position; + break; + case TType.AngleID: + case TType.QuoteID: + if (!skip && accept) + { + var begin = tok.Position - start_pos; + xline.FullFile = tok.Value.ToString(); + xline.delimiter = tok.Type == TType.AngleID ? DelimiterMode.AngleBrackets : DelimiterMode.Quotes; + end_pos = tok.End; + xline.line = line; + xline.file_subspan = new(begin, tok.Value.Length); // subspan of file for replacement + + accept = false; + comments = true; + } + break; + case TType.Ifdef: + case TType.Ifndef: + case TType.Elif: + case TType.Else: + case TType.Elifdef: + skip = true; + break; + case TType.Endif: + skip = false; + break; + case TType.Commentary: + case TType.MLCommentary: + end_pos = tok.End; + xline.keep = pragma.IsMatch(tok.Value.ToString()); + break; + default: + accept = false; + break; + } + } + + if (xline.Valid) + { + xline.span = new(start_pos, end_pos - start_pos); + lines.Add(xline); + } + + return lines.ToArray(); + } + } +} diff --git a/IncludeToolboxShared/Util/Parser.cs b/IncludeToolboxShared/Util/Parser.cs new file mode 100644 index 0000000..5a62d47 --- /dev/null +++ b/IncludeToolboxShared/Util/Parser.cs @@ -0,0 +1,397 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using static IncludeToolbox.Lexer; + +namespace IncludeToolbox +{ + public static partial class Parser + { + struct Context + { + public Stack ns_tree = new(); //where are we + public Stack depth = new(); //wie tief + public Stack expected_tokens = new(); //what are we waiting for + bool ex = false; + uint curr = 0; + uint scope = 0; + + public static Context operator ++(Context a) + { + a.scope++; + return a; + } + public static Context operator --(Context a) + { + a.scope--; + return a; + } + + public bool Namespace + { + get => ex; set + { + ex = value; + if (!ex) + { + depth.Push(curr); + curr = 0; + } + } + } + public uint Scope { get => scope; } + + public Context() + { + expected_tokens.Push(TType.Terminal); //terminal + expected_tokens.Push(TType.T0); //first rule + + depth.Push(0); //global namespace + ns_tree.Push(""); + } + public void PushNamespace(string ns) + { + ns_tree.Push(ns); + curr++; + } + public void PopNamespace() + { + curr = depth.Pop(); + while (curr-- != 0) + ns_tree.Pop(); + curr = 0; + } + public string[] GetNamespace() + { + return ns_tree.ToArray(); + } + public void Clear() + { + expected_tokens.Clear(); + expected_tokens.Push(TType.Terminal); //terminal + expected_tokens.Push(TType.T0); //first rule + } + } + public struct Output + { + private readonly List namespaces; + private readonly List decls; + private readonly List includes; + private readonly int insertion_point = 0; + + public List Namespaces { get => namespaces; } + public List Declarations { get => decls; } + public List Includes { get => includes; } + public int InsertionPoint { get => insertion_point; } + + public Output(List namespaces, List decls, List includes, int insertion_point = 0) + { + this.namespaces = namespaces; + this.decls = decls; + this.includes = includes; + this.insertion_point = insertion_point; + } + } + + static void FFWD(ref Lexer.Context ctx, int to_scope, int from_scope) + { + while (from_scope != to_scope && !ctx.Empty()) + { + var tok = ctx.GetToken(false); + if (tok.Type == TType.OpenBr) + from_scope++; + if (tok.Type == TType.CloseBr) + from_scope--; + } + } + + static bool LL_ID(ref Stack stack, TType current) + { + switch (current) + { + case TType.T4: + return true; + default: + break; + } + return false; + } + static bool LL_Class(ref Stack stack, TType current) + { + switch (current) + { + case TType.T0: + case TType.T3: + stack.Push(current); + stack.Push(TType.Semicolon); + stack.Push(TType.ID); + stack.Push(TType.T1); + return true; + case TType.T1: + case TType.T4: + stack.Push(TType.Class); + return true; + default: + break; + } + return false; + } + static bool LL_Struct(ref Stack stack, TType current) + { + switch (current) + { + case TType.T0: + case TType.T3: + stack.Push(current); + stack.Push(TType.Semicolon); + stack.Push(TType.ID); + stack.Push(TType.T1); + return true; + case TType.T1: + stack.Push(TType.Struct); + return true; + default: + break; + } + return false; + } + static bool LL_Namespace(ref Stack stack, TType current) + { + switch (current) + { + case TType.T0: + case TType.T3: + stack.Push(current); + stack.Push(TType.CloseBr); + stack.Push(TType.T3); + stack.Push(TType.OpenBr); + stack.Push(TType.T2); + stack.Push(TType.ID); + stack.Push(TType.Namespace); + return true; + default: + break; + } + return false; + } + static bool LL_Enum(ref Stack stack, TType current) + { + switch (current) + { + case TType.T0: + case TType.T3: + stack.Push(current); + stack.Push(TType.Semicolon); + stack.Push(TType.ID); + stack.Push(TType.T1); + return true; + case TType.T1: + stack.Push(TType.T4); + stack.Push(TType.Enum); + return true; + default: + break; + } + return false; + } + + static bool LLTable(ref Stack lctx, TType input, TType current) + { + switch (input) + { + case TType.Namespace: return LL_Namespace(ref lctx, current); + case TType.Class: return LL_Class(ref lctx, current); + case TType.Struct: return LL_Struct(ref lctx, current); + case TType.Enum: return LL_Enum(ref lctx, current); + case TType.ID: return LL_ID(ref lctx, current); + case TType.Colon: + if (current == TType.T2) + { + lctx.Push(TType.T2); + lctx.Push(TType.ID); + lctx.Push(TType.Colon); + return true; + } + return false; + case TType.OpenBr: return current == TType.T2; + case TType.CloseBr: return current == TType.T3; + case TType.Include: + if (current == TType.T0) + { + lctx.Push(TType.T5); + lctx.Push(TType.Include); + return true; + } + break; + case TType.AngleID: + case TType.QuoteID: + if (current == TType.T5) + { + lctx.Push(TType.T0); + lctx.Push(input); + return true; + } + break; + default: + break; + } + return false; + } + + + + public static Output Parse(ReadOnlySpan text, bool disable_ns = false, bool disable_count = false) + { + List namespaces = new(); + List fwd = new(); + List includes = new(); + + Namespace ns = new(); + FWDDecl decl = new(); + IncludeLine inc = new(); + + Lexer.Context lctx = new(text); + Parser.Context pctx = new(); + bool accept = false; + bool pragma = false; + bool include_end = false; + + int insertion_point = 0; + int preproc = 0; + int start = 0; + + Token tok = lctx.GetToken(accept, false); + + + while (pctx.expected_tokens.Count != 0 && tok.valid()) + { + TType expect = pctx.expected_tokens.Peek(); + + if (expect >= TType.T0) //LL rules + { + pctx.expected_tokens.Pop(); + accept = LLTable(ref pctx.expected_tokens, tok.Type, expect); + continue; + } + if (!accept || expect != tok.Type) + { + pctx.Clear(); // unexpected token, start anew + + switch (tok.Type) + { + case TType.OpenBr: // if scope, probably function or class + FFWD(ref lctx, (int)pctx.Scope, (int)pctx.Scope + 1); break; + case TType.CloseBr: + pctx--; break; + case TType.Pragma: + pragma = true; tok = lctx.GetToken(true, false); continue; + case TType.ID: + if (pragma && tok.Value.SequenceEqual("once".AsSpan())) + insertion_point = tok.End; break; + } + + preproc += tok.IsPreprocStart && includes.Any() ? 1 : 0; + preproc -= preproc>0?tok.IsPreprocEnd ? 1 : 0:0; + + tok = lctx.GetToken(accept, false); + decl.type = TType.Null; //interference with enum{} class; + continue; + } + + pctx.expected_tokens.Pop(); + + + if (!disable_count && !include_end + && expect != TType.Include + && expect != TType.AngleID + && expect != TType.QuoteID + && includes.Count > 0) + { include_end = true; } + + switch (expect) + { + case TType.Namespace: + pctx.Namespace = true; + if (!disable_ns) + { + start = tok.Position; + ns.scope = pctx.Scope; + } + break; + case TType.Class: + if (decl.type == TType.Enum) + decl.type = TType.EnumClass; //special case + else goto case TType.Struct; + break; + case TType.Struct: + case TType.Enum: + decl.type = tok.Type; + start = tok.Position; + break; + case TType.ID: + if (pctx.Namespace) + pctx.PushNamespace(tok.Value.ToString()); + else + decl.ID = tok.Value.ToString(); + break; + case TType.OpenBr: + if (pctx.Namespace) + { + if (!disable_ns) + { + ns.namespaces = pctx.GetNamespace(); + ns.span = new(start, tok.Position - start); + namespaces.Add(ns); + ns = new(); + } + pctx.Namespace = false; + } + break; + case TType.CloseBr: + pctx.PopNamespace(); break; + case TType.Include: + start = tok.Position; + break; + case TType.QuoteID: + case TType.AngleID: + var begin = tok.Position - start; + inc.FullFile = tok.Value.ToString(); + inc.delimiter = tok.Type == TType.AngleID ? DelimiterMode.AngleBrackets : DelimiterMode.Quotes; + inc.span = new(start, tok.End - start); + inc.file_subspan = new(begin, tok.Value.Length); // subspan of file for replacement + + if (!include_end && !disable_count && preproc == 0) insertion_point = tok.End; + + includes.Add(inc); + inc = new(); + break; + case TType.Semicolon: + decl.span = new(start, tok.End - start); + decl.namespaces = pctx.ns_tree.ToArray(); + fwd.Add(decl); + decl = new(); + break; + default: + break; + } + + if (tok.Type == TType.OpenBr) + pctx++; + if (tok.Type == TType.CloseBr) + pctx--; + + tok = lctx.GetToken(accept, false); + } + return new Output(namespaces, fwd, includes, insertion_point); + } + public static Output Parse(string text) + { + return Parse(text.AsSpan()); + } + public static async Task ParseAsync(string text) + { + return await Task.Run(() => Parse(text)); + } + } +} diff --git a/IncludeToolboxShared/Util/ParserStructures.cs b/IncludeToolboxShared/Util/ParserStructures.cs new file mode 100644 index 0000000..6d6c7d7 --- /dev/null +++ b/IncludeToolboxShared/Util/ParserStructures.cs @@ -0,0 +1,202 @@ +using Microsoft.VisualStudio.Text; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using static IncludeToolbox.Lexer; + +namespace IncludeToolbox +{ + public struct Namespace + { + public Span span = new(); + public string[] namespaces = null; + public uint scope = 0; + + public Namespace() { } + } + + public struct FWDDecl + { + public Span span = new(); + public string[] namespaces = null; + public TType type = TType.Null; + public string ID = ""; + + public FWDDecl() { } + + public bool Valid => !string.IsNullOrEmpty(ID); + + public override bool Equals(object obj) + { + return obj is FWDDecl decl && + namespaces.SequenceEqual(decl.namespaces) && + type == decl.type && + ID == decl.ID; + } + + public override int GetHashCode() + { + int hashCode = -1447791890; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(namespaces); + hashCode = hashCode * -1521134295 + type.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ID); + return hashCode; + } + + static public bool operator ==(FWDDecl decl1, FWDDecl decl2) + { + bool a = decl2.namespaces.SequenceEqual(decl1.namespaces); + return decl1.ID == decl2.ID && decl1.type == decl2.type && a; + } + static public bool operator !=(FWDDecl decl1, FWDDecl decl2) + { + return !(decl1 == decl2); + } + } + + public struct IncludeLine : IEquatable + { + private string file = ""; + public DelimiterMode delimiter = DelimiterMode.Unchanged; + public Span span = new(); + public Span file_subspan = new(); + public int line = 0; + public bool keep = false; + public NewlineChar newlineChar = NewlineChar.N; + + public IncludeLine() + { } + + public string Content => Valid ? file.Substring(1, file.Length - 2) : ""; + public string FullFile { get => file; set => file = value; } + public bool Keep => keep; + public bool Valid => !string.IsNullOrEmpty(file); + public int NewlineLength => newlineChar switch { NewlineChar.N => 0, NewlineChar.CR => 2, _ => 1 }; + public int End => span.End; + + + + public Span ReplaceSpan(int relative_pos) => new(relative_pos + span.Start, span.Length); + public Span ReplaceSpan(int relative_pos, int offset_end) => + offset_end >= span.Length ? new() : new(relative_pos + span.Start, span.Length - offset_end); + public Span ReplaceSpanWithoutNewline(int relative_pos) => + ReplaceSpan(relative_pos, NewlineLength); + + public string Project(string over) + { + if (!Valid) return ""; + var x = over.Substring(span.Start, span.Length); + return x.Remove(file_subspan.Start, file_subspan.Length).Insert(file_subspan.Start, FullFile); + } + public void SetFullContent(string content) { FullFile = content; } + + public void SetFile(string val) + { + switch (delimiter) + { + case DelimiterMode.AngleBrackets: + FullFile = '<' + val + '>'; + break; + case DelimiterMode.Quotes: + FullFile = '"' + val + '"'; + break; + } + } + public void SetDelimiter(DelimiterMode delimiter) + { + if (this.delimiter == delimiter) return; + this.delimiter = delimiter; + SetFile(Content); + } + public void ToForward() + { + FullFile.Replace('\\', '/'); + } + public void ToBackward() + { + FullFile.Replace('/', '\\'); + } + + public string Resolve(IEnumerable includeDirectories, bool suppress = false) + { + foreach (string dir in includeDirectories) + { + string candidate = Path.Combine(dir, Content); + if (System.IO.File.Exists(candidate)) + return Utils.GetExactPathName(candidate); + } + + if (!suppress) + Output.WriteLine($"Unable to resolve include: '{Content}'"); + return ""; + } + + public override bool Equals(object obj) + { + return obj is IncludeLine line && Equals(line); + } + + public bool Equals(IncludeLine other) + { + return FullFile == other.FullFile && keep == other.keep; + } + + public override int GetHashCode() + { + int hashCode = -1366893598; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(FullFile); + hashCode = hashCode * -1521134295 + Keep.GetHashCode(); + return hashCode; + } + + public static bool operator ==(IncludeLine left, IncludeLine right) + { + return left.Equals(right); + } + public static bool operator !=(IncludeLine left, IncludeLine right) + { + return !(left == right); + } + } + + public struct Include + { + public Include(IncludeLine line, string[] includeDirectories) + { + this.line = line.line; + file = line.Content; + absolute_path = Resolve(includeDirectories); + } + + readonly string file = ""; + readonly string absolute_path = ""; + readonly int line = 0; + + public string File => file; + public string AbsolutePath => absolute_path; + public int Line => line; + + public string Resolve(string[] includeDirectories) + { + if (!string.IsNullOrEmpty(absolute_path)) + return absolute_path; + + foreach (string dir in includeDirectories) + { + string candidate = Path.Combine(dir, file); + if (System.IO.File.Exists(candidate)) + return candidate; + } + return file; + } + } + + public enum NewlineChar + { + N, + CR, + LF, + CRLF + } +} diff --git a/IncludeToolboxShared/Util/PropertyNotify.cs b/IncludeToolboxShared/Util/PropertyNotify.cs new file mode 100644 index 0000000..a788027 --- /dev/null +++ b/IncludeToolboxShared/Util/PropertyNotify.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text; + +namespace IncludeToolbox +{ + public class PropertyNotify : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + protected bool SetProperty(ref T field, T newValue, [CallerMemberName] string propertyName = null) + { + if (!(Equals(field, newValue))) + { + field = (newValue); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + return true; + } + + return false; + } + } +} diff --git a/IncludeToolbox/RegexUtils.cs b/IncludeToolboxShared/Util/RegexUtils.cs similarity index 100% rename from IncludeToolbox/RegexUtils.cs rename to IncludeToolboxShared/Util/RegexUtils.cs diff --git a/IncludeToolboxShared/Util/Standard.cs b/IncludeToolboxShared/Util/Standard.cs new file mode 100644 index 0000000..aba705f --- /dev/null +++ b/IncludeToolboxShared/Util/Standard.cs @@ -0,0 +1,45 @@ +namespace IncludeToolbox +{ + public enum Standard + { + None, + cpp11, + cpp14, + cpp17, + cpp20, + cpp23, + cpp26, + cpp29, + } + + public static class ExtensionMethods + { + public static string ToStdFlag(this Standard e) + { + return e switch + { + Standard.cpp11 => "-std=c++11", + Standard.cpp14 => "-std=c++14", + Standard.cpp17 => "-std=c++17", + Standard.cpp20 => "-std=c++20", + Standard.cpp23 => "-std=c++2b", + Standard.cpp26 => "-std=c++2b", + Standard.cpp29 => "-std=c++2b", + _ => "-std=c++2b", + }; + } + public static Standard FromMSVCFlag(string s) + { + var e = s switch + { + "stdcpp20" => Standard.cpp20, + "stdcpplatest" => Standard.cpp23, + "Default" => Standard.cpp14, + "stdcpp17" => Standard.cpp17, + "stdcpp14" => Standard.cpp14, + _ => Standard.cpp23, + }; + return e; + } + } +} diff --git a/IncludeToolbox/Utils.cs b/IncludeToolboxShared/Util/Utils.cs similarity index 57% rename from IncludeToolbox/Utils.cs rename to IncludeToolboxShared/Util/Utils.cs index b9b81e5..6d931f5 100644 --- a/IncludeToolbox/Utils.cs +++ b/IncludeToolboxShared/Util/Utils.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; namespace IncludeToolbox { @@ -17,16 +15,42 @@ public struct BoolWithReason public static class Utils { + public static string GetLineBreak(IWpfTextView view) + { + return view.Options.GetNewLineCharacter(); + } + + public static Span GetIncludeSpan(string text) + { + int[] line = new int[2]; + line[0] = text.IndexOf("#include"); //first + line[1] = text.IndexOf("\n", text.LastIndexOf("#include")) - line[0] + 1; //last + return new Span(line[0], line[1]); + } + + public static ReadOnlySpan GetIncludeSpanRO(string text) + { + int[] line = new int[2]; + line[0] = text.IndexOf("#include"); //first + if (line[0] == -1) return new ReadOnlySpan(); + line[1] = text.IndexOf("\n", text.LastIndexOf("#include")) - line[0] + 1; //last + return text.AsSpan(line[0], line[1]); + } + + public static string MakeRelative(string absoluteRoot, string absoluteTarget) { Uri rootUri, targetUri; + if (!absoluteRoot.EndsWith(Path.DirectorySeparatorChar.ToString())) + absoluteRoot += Path.DirectorySeparatorChar; + try { rootUri = new Uri(absoluteRoot); targetUri = new Uri(absoluteTarget); } - catch(UriFormatException) + catch (UriFormatException) { return absoluteTarget; } @@ -58,33 +82,5 @@ public static string GetExactPathName(string pathName) return di.Name.ToUpper(); } } - - /// - /// Retrieves the dominant newline for a given piece of text. - /// - public static string GetDominantNewLineSeparator(string text) - { - string lineEndingToBeUsed = "\n"; - - // For simplicity we're just assuming that every \r has a \n - int numLineEndingCLRF = text.Count(x => x == '\r'); - int numLineEndingLF = text.Count(x => x == '\n') - numLineEndingCLRF; - if (numLineEndingLF < numLineEndingCLRF) - lineEndingToBeUsed = "\r\n"; - - return lineEndingToBeUsed; - } - - /// - /// Prepending a single Item to an to an IEnumerable. - /// - public static IEnumerable Prepend(this IEnumerable seq, T val) - { - yield return val; - foreach (T t in seq) - { - yield return t; - } - } } } diff --git a/IncludeToolboxShared/Util/VCUtil.cs b/IncludeToolboxShared/Util/VCUtil.cs new file mode 100644 index 0000000..a49706f --- /dev/null +++ b/IncludeToolboxShared/Util/VCUtil.cs @@ -0,0 +1,140 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.VCProjectEngine; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using static Microsoft.VisualStudio.VSConstants; +using Project = Community.VisualStudio.Toolkit.Project; + +namespace IncludeToolbox +{ + public static class VSToolkitExtension + { + public static Span Move(this Span span, int relative_pos) + { + return new(span.Start + relative_pos, span.Length); + } + public static async Task ToVCProjectAsync(this Project project) + { + project.GetItemInfo(out var hierarchy, out _, out _); + return await VCUtil.GetVCProjectAsync(hierarchy); + } + public static async Task ToVCProjectItemAsync(this SolutionItem project) + { + project.GetItemInfo(out var hierarchy, out var n, out _); + return await VCUtil.GetVCProjectItemAsync(hierarchy, n); + } + } + + + + public class VCUtil + { + static VCProject cached_project; + static string command_line = ""; + static Standard std = Standard.cpp23; + + public static Standard Std { get => std; } + + internal static async Task GetVCProjectAsync(IVsHierarchy hierarchy) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + _ = hierarchy.GetProperty(VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out var objProj); + return (objProj as EnvDTE.Project)?.Object as VCProject; + } + internal static async Task GetVCProjectItemAsync(IVsHierarchy item, uint n) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + _ = item.GetProperty(n, (int)__VSHPROPID.VSHPROPID_ExtObject, out var objProj); + return (objProj as EnvDTE.ProjectItem)?.Object as VCProjectItem; + } + internal static VCFileConfiguration GetVCFileConfig(VCProjectItem item) + { + var project = item.project as VCProject; + if (project == null) return null; + + VCFile file = (VCFile)item; + var configs = (IVCCollection)file.FileConfigurations; + + if (configs?.Item(project.ActiveConfiguration.Name) is not VCFileConfiguration fileConfig) return null; + return fileConfig; + } + + public static async Task> GetIncludeDirsAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var actproj = await VS.Solutions.GetActiveProjectAsync(); + var proj = await actproj.ToVCProjectAsync(); + if (proj == null) { VS.MessageBox.ShowErrorAsync("IWYU Error:", "The project is not a Visual Studio C/C++ type.").FireAndForget(); return null; } + + var dir = Path.GetDirectoryName(actproj.FullPath).Replace('\\', '/'); + + var cfg = proj.ActiveConfiguration; + var cl = cfg?.Rules; + if (cl == null) { VS.MessageBox.ShowErrorAsync("IWYU Error:", "Failed to gather Compiler info.").FireAndForget(); return null; } + + + var com = (IVCRulePropertyStorage2)cl.Item("CL"); + + string property = com!=null? "AdditionalIncludeDirectories": "IncludePath"; + com ??= (IVCRulePropertyStorage2)cl.Item("ConfigurationDirectories"); + + var dirs = com.GetEvaluatedPropertyValue(property).Replace('\\','/') + .Split(';').Where(s => !string.IsNullOrWhiteSpace(s)).Select(s=>Path.IsPathRooted(s)?s:Path.Combine(dir, s)); + return dirs; + } + + public static async Task GetCommandLineAsync(bool rebuild) + { + return await GetCommandLineAsync(rebuild, await VS.Solutions.GetActiveProjectAsync()); + } + + public static string GetPCH(VCProject proj) + { + var cfg = proj.ActiveConfiguration; + var cl = cfg?.Rules; + var com = (IVCRulePropertyStorage2)cl.Item("CL"); + var xstandard = com.GetEvaluatedPropertyValue("PrecompiledHeader"); + return xstandard == "Use" ? com.GetEvaluatedPropertyValue("PrecompiledHeaderFile") : ""; + } + + public static async Task GetCommandLineAsync(bool rebuild, Project xproj) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var proj = await xproj.ToVCProjectAsync(); + + if (cached_project == proj && !rebuild) + return command_line; + + cached_project = proj; + + if (proj == null) { VS.MessageBox.ShowErrorAsync("IWYU Error:", "The project is not a Visual Studio C/C++ type.").FireAndForget(); return null; } + + var cfg = proj.ActiveConfiguration; + var cl = cfg?.Rules; + if (cl == null) { VS.MessageBox.ShowErrorAsync("IWYU Error:", "Failed to gather Compiler info.").FireAndForget(); return null; } + + var com = (IVCRulePropertyStorage2)cl.Item("CL"); + var xstandard = com.GetEvaluatedPropertyValue("LanguageStandard"); + var includes = com.GetEvaluatedPropertyValue("AdditionalIncludeDirectories").Replace('\\', '/') + .Split(';').Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(x => "-I\"" + x + '\"'); + var defs = com.GetEvaluatedPropertyValue("PreprocessorDefinitions") + .Split(';').Where(s => !string.IsNullOrWhiteSpace(s)) + .Select(x => "-D" + x); + + std = ExtensionMethods.FromMSVCFlag(xstandard); + string standard = std.ToStdFlag(); + + var inc_string = string.Join(" ", includes); + var def_string = string.Join(" ", defs); + + + return command_line = inc_string + ' ' + def_string + ' ' + standard; + } + } +} diff --git a/README.md b/README.md index e07fb76..2439eb4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,159 @@ -**Project inactive, looking for maintainer!** - -# IncludeToolbox -Visual Studio extension for managing C/C++ includes: -* **Format & Sort:** Format include commands to cleanup large lists of includes according to your preferences -* **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 -* **Explore:** _Include Graph_ tool window shows the tree of includes for a given file - -More information and download on the [Visual Studio Extension Gallery page](https://visualstudiogallery.msdn.microsoft.com/28c36d4f-425a-4bfe-9449-03f07b35f7b0) +# Include Toolbox +[![Build](https://github.com/Agrael1/IncludeToolbox/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/Agrael1/IncludeToolbox/actions/workflows/main.yml) + +**_Tools for managing C/C++ #includes: Formatting, sorting, exploring, pruning._** + +Include Toolbox consists of 4 different tools. All of them are only applicable to VC++ projects. + +![](/art/iformat.png) **[Command]** Include Formatter +![](/art/itrial.png) **[Command]** Trial and Error Include Removal +![](/art/iwyu.png) **[Command]** [Include-What-You-Use](https://include-what-you-use.org/) Integration +![](/art/AddPageGuides.png) **[Command]** Mapper module for IWYU +**[Command]** Compile Header +![](/art/igraph.png)**[Tool Window]** Include Graph + +## Links + +[Open VSIX Gallery 2019](https://www.vsixgallery.com/extension/IncludeToolbox2019.1431faa5-aa04-47af-8289-9d887e0696a4) + +[Open VSIX Gallery 2022](https://www.vsixgallery.com/extension/IncludeToolbox2022.d3cba4fe-8d65-479b-8436-18d743ee7b53) + +[Marketplace 2019](https://marketplace.visualstudio.com/items?itemName=Wumpf.IncludeToolbox) + +[Marketplace 2022](https://marketplace.visualstudio.com/items?itemName=Agrael.IncludeToolbox2022) + +[Version History](/doc/changelog.md) + +# Tools in Detail + +## Include Formatter + +![Include Format](/art/includeformatter.gif) + +Select a group of includes, right click, select "Format Selected Includes" + +The behavior of this command is controlled by various options which can be configured in _Tools>Options>Include Toolbox>Include Formatter_: + +* Formatting + * Delimiter Mode + Optionally change "" to <> or vice versa + * Slash Mode + Optionally changes / to \ or vice versa + * Remove Empty Lines + Optionally removes empty lines within the selection + +* Path Reformatting + * Ignore File Relative + If true, the local file path will not be considered for reformatting the path + * Mode + Configures the strategy that should be used to determine new include paths +* Sorting + The tool will always sort all selected includes alphabeticaly, unless.. + * Precedence Regex + Every line gives a regex - if an include matches a regex, it has precedence over all other includes that do not match any, or a later regex. Multiple includes that match the same regex are still alphabetically sorted. + * Sort by Include Type + Optionally puts all inclues with either quotes or angle brackets first. + * Remove duplicates + Removes duplicate headers. May be suppressed using `//IWYU pragma: keep` e.g. for maintaining strong ordering dependency + +All operations are performed in the order in which they occur on the option page. + +## Trial and Error Include Removal + +The name says it all: This tool will try to remove an include, recompile, see if it works and proceed to the next one accordingly. +The tool can be started an all compilable files in a VC++ by right clicking on the code window. There is also a special version in the Project context menu which will run over every single compilable file in the project (takes very long). + +Obviously the results of this tool are far from optimal and the process can take a while. + +The exact behavior of this command can be controlled in *Tools>Options>Include Toolbox>Trial and Error Include Removal*: + +* Ignore List + A list of regexes. If the content of an include matches any of these, it will never be removed. +* Ignore First Include + If true the top most include will always be ignored, does not work in headers +* Removal Order + Wheater the tool should run from top to bottom or bottom to top (this can make a difference on the end result) + +To suppress removal of a single include, add a comment to its line containing `//IWYU pragma: keep` + +Since 3.2.47 works for header files as well. + +## Include-What-You-Use Integration + + +![Include What You Use](/art/iwyu.gif) + +Include Toolbox with an integration of the free [Include-What-You-Use](https://github.com/include-what-you-use/include-what-you-use). By default (see _Tools>Options>Include Toolbox>Include-What-You-Use_) it is downloaded together with a VC++ specific mapping file from [this github repository](https://github.com/Agrael1/BuildIWYU) upon first use (and whenever there is a newer version available in this repository). New version is automatically built and shipped every month. + + +Again, it can be activated by right clicking on a C++ Code file in a VC++ document. The Option page exposes most of IWYU's command line options and provides the option to directly apply the results. The complete output will be displayed in the Include Toolbox output window. + +IWYU often does not work as expected - for more information look at the [official docs](https://github.com/include-what-you-use/include-what-you-use/tree/master/docs). + +IWYU has several pragmas, described at [Pragmas](https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUPragmas.md), e.g. `//IWYU pragma: keep` works as include removal suppresor. + +Since 3.0.0: +Added mapper support. Maps produced with it are used to make results better, as it describes all include files within mapped file. + +Added cheap and precise modes: cheap mode copies contents of IWYU output, may be undesirable, as it does not account forward declarations, but it is fast. Presice mode uses custom LL1 partial parser, which reads all the information from file and output, combining all the possibilities it allows for additional steps: + - Format all includes + - Extract all forward declarations and place them before code + - Empty namespaces removal, useful combining with previous option + +There is a BETA feature of IWYU usage with several files: + - Select several files in project menu. + - *Right click>Run Include-What-You-Use* + +It is useful for example with several .cpp files, when you are sure, that headers included are fully correct. + +## Map Generator for Include-What-You-Use [beta] + +The feature is tested, but it is useful even within large projects. It makes results of IWYU better. Works only on header files. + +It gets all the #include declarations and writes them as they are to the specified mapping file. Combining several of those files are done using `{ref: }` in the final file. To find more visit [official mappings guide](https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/IWYUMappings.md) + +Configuration is on *Tools>Options>Include Toolbox>Include Mapper* page. + +Mapper has one option, that specifies separator you would like to use, quotes or angle brackets. This option maps opposite choice as a private header, ultimately forsing IWYU to choose your vision of the file. + +To specify relative index use *Relative File Prefix* option. e.g. C:\\users\\map\\a.h with prefix C:\\users will write to the final map. + +## Include Graph [beta] + +![Include What You Use](/art/IncludeGraph.png) + +Partially restored functionality of include graph. The Graph window is controlled by the `Open in Include Graph` command. + +The command opens a window with lazy parsed #includes. The file names are expandable only if the file described is present in project directory or in any of the additional directories. Graph includes can be navigated by double clicking the item in a graph. This will open the file which has that include line in it. + +Options are not working right now. DGML save command will be added in future releases. + +The graph is not stable, searching for bugs or inconsistensies. Issues pointing inconsistensies are welcome. + +## Compile Header [new] + +Have you ever felt the situation: you have a clean API header, and you wonder if it is working standalone without any dependencies? + +Well, now there is a button for that. +It creates an empty file in you project, includes the header and compiles it. Also it automatically adds a pch if it exists. + +# FAQ: + +* Why don't you apply the formatting to all includes within a file? + This may sound desirable, but is very messy if there are optional includes (preprocessor) or specific exceptions where not all includes should be in the same place or in the default order. +* XY didn't work, what is going on? + Look in the output window for Include Toolbox to get more information. + +# Optimal Usage Pattern + +1. Start from the header file in your project, that includes only standard library. +2. Use IWYU and/or TAEIR tool +3. Add file to mapper with pre-specified .imp file at *Tools>Options>Include Toolbox>Include Mapper* +4. Add this map file to the IWYU preset at *Tools>Options>Include Toolbox>Include-What-You-Use>Mapping file* +5. Go through all the headers in your project, including them in mapper file +6. After that go through all .cpp files with the same tools. IWYU has mass processing for several selected files. IThe best way of using this tool is in batches of \4-5 files. +7. Compile files to asses the result. + +# Final Words + +The IWYU itself is far from perfect, TAEIR also, but combinig those tools and Mapping capabilities with other maps, provided by IWYU repo and defaults the results will be just good enough. \ No newline at end of file diff --git a/Tests/IncludeFormatingTest.cs b/Tests/IncludeFormatingTest.cs deleted file mode 100644 index c6953ae..0000000 --- a/Tests/IncludeFormatingTest.cs +++ /dev/null @@ -1,285 +0,0 @@ -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using IncludeToolbox.Formatter; - -namespace Tests -{ - [TestClass] - public class IncludeFormatingTest - { - private static string sourceCode_NoBlanks = -@"#include ""a.h"" -#include -#include ""a.h"" -#include ""filename.h"" -#include -#include -#include -#include -#include -#include "; - - private static string sourceCode_WithBlanks = -@"#include ""c_third"" - -#include ""filename.h"" - -#include ""z_first"" - -#include -#include -// A comment -#include ""z_first"" - -#include -#include ""filename.h"""; - - [TestMethod] - public void Sorting_BlanksAfterRegexGroup() - { - // Blanks after groups. - string expectedFormatedCode_NoBlanks = -@"#include ""filename.h"" - -#include -#include - -#include ""a.h"" -#include -#include - - - -"; - - string expectedFormatedCode_WithBlanks = -@"#include ""filename.h"" - -#include - -#include ""c_third"" - -#include ""z_first"" - -// A comment - - - -"; - - - var settings = new IncludeToolbox.FormatterOptionsPage(); - settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.None; - settings.PrecedenceRegexes = new string[] - { - IncludeToolbox.RegexUtils.CurrentFileNameKey, - ".+_.+" - }; - settings.BlankAfterRegexGroupMatch = true; - settings.RemoveEmptyLines = false; - - - string formatedCode = IncludeFormatter.FormatIncludes(sourceCode_NoBlanks, "filename.cpp", new string[] { }, settings); - Assert.AreEqual(expectedFormatedCode_NoBlanks, formatedCode); - formatedCode = IncludeFormatter.FormatIncludes(sourceCode_WithBlanks, "filename.cpp", new string[] { }, settings); - Assert.AreEqual(expectedFormatedCode_WithBlanks, formatedCode); - } - - [TestMethod] - public void Sorting_AngleBracketsFirst() - { - // With sort by type. - string expectedFormatedCode_NoBlanks = -@"#include -#include -#include -#include -#include ""filename.h"" -#include ""a.h"" - - - -"; - - - string expectedFormatedCode_WithBlanks = -@"#include - -#include ""filename.h"" - -#include ""c_third"" - -#include ""z_first"" - -// A comment - - - -"; - - - var settings = new IncludeToolbox.FormatterOptionsPage(); - settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.AngleBracketsFirst; - settings.PrecedenceRegexes = new string[] - { - IncludeToolbox.RegexUtils.CurrentFileNameKey, - ".+_.+" - }; - settings.BlankAfterRegexGroupMatch = false; - settings.RemoveEmptyLines = false; - - string formatedCode = IncludeFormatter.FormatIncludes(sourceCode_NoBlanks, "filename.cpp", new string[] { }, settings); - Assert.AreEqual(expectedFormatedCode_NoBlanks, formatedCode); - formatedCode = IncludeFormatter.FormatIncludes(sourceCode_WithBlanks, "filename.cpp", new string[] { }, settings); - Assert.AreEqual(expectedFormatedCode_WithBlanks, formatedCode); - } - - [TestMethod] - public void Sorting_DontRemoveDuplicates() - { - // With sort by type. - string expectedFormatedCode_NoBlanks = -@"#include ""filename.h"" - -#include -#include -#include -#include - -#include ""a.h"" -#include ""a.h"" -#include -#include -#include "; - - - string expectedFormatedCode_WithBlanks = -@"#include ""filename.h"" -#include ""filename.h"" - -#include -#include -#include -// A comment -#include ""c_third"" -#include ""z_first"" -#include ""z_first"""; - - - var settings = new IncludeToolbox.FormatterOptionsPage(); - settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.None; - settings.PrecedenceRegexes = new string[] - { - IncludeToolbox.RegexUtils.CurrentFileNameKey, - ".+_.+" - }; - settings.BlankAfterRegexGroupMatch = true; - settings.RemoveEmptyLines = true; - settings.RemoveDuplicates = false; - - string formatedCode = IncludeFormatter.FormatIncludes(sourceCode_NoBlanks, "filename.cpp", new string[] { }, settings); - Assert.AreEqual(expectedFormatedCode_NoBlanks, formatedCode); - formatedCode = IncludeFormatter.FormatIncludes(sourceCode_WithBlanks, "filename.cpp", new string[] { }, settings); - Assert.AreEqual(expectedFormatedCode_WithBlanks, formatedCode); - } - - [TestMethod] - public void RemoveEmptyLines() - { - string expectedFormatedCode_WithBlanks = -@"#include ""filename.h"" -#include -#include ""c_third"" -#include ""z_first"" -// A comment"; - - var settings = new IncludeToolbox.FormatterOptionsPage(); - settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.None; - settings.PrecedenceRegexes = new string[] { IncludeToolbox.RegexUtils.CurrentFileNameKey }; - settings.BlankAfterRegexGroupMatch = false; - settings.RemoveEmptyLines = true; - - string formatedCode = IncludeFormatter.FormatIncludes(sourceCode_WithBlanks, "filename.cpp", new string[] { }, settings); - Assert.AreEqual(expectedFormatedCode_WithBlanks, formatedCode); - } - - [TestMethod] - public void EmptySelection() - { - // Activate all features - var settings = new IncludeToolbox.FormatterOptionsPage(); - settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.AngleBracketsFirst; - settings.PrecedenceRegexes = new string[] { IncludeToolbox.RegexUtils.CurrentFileNameKey }; - settings.BlankAfterRegexGroupMatch = true; - settings.RemoveEmptyLines = true; - settings.DelimiterFormatting = IncludeToolbox.FormatterOptionsPage.DelimiterMode.AngleBrackets; - settings.SlashFormatting = IncludeToolbox.FormatterOptionsPage.SlashMode.BackSlash; - - string formatedCode = IncludeFormatter.FormatIncludes("", "filename.cpp", new string[] { }, settings); - Assert.AreEqual("", formatedCode); - } - - [TestMethod] - public void OtherPreprocessorDirectives() - { - string source = -@"#pragma once -// SomeComment -#include ""z"" -#include - -#include ""filename.h"" - -#if test -#include -// A comment -#include ""a9"" -#include -#include -#else -#include - -#include // comment -//#endif - -#include -#endif -#include -#include "; - - string expectedFormatedCode = -@"#pragma once -// SomeComment -#include -#include ""filename.h"" - -#include ""z"" - -#if test -#include -// A comment -#include -#include ""a9"" - -#else -#include - -#include // comment -//#endif - -#include -#endif -#include -#include "; - - var settings = new IncludeToolbox.FormatterOptionsPage(); - settings.SortByType = IncludeToolbox.FormatterOptionsPage.TypeSorting.AngleBracketsFirst; - settings.PrecedenceRegexes = new string[] { IncludeToolbox.RegexUtils.CurrentFileNameKey }; - settings.BlankAfterRegexGroupMatch = false; - settings.RemoveEmptyLines = false; - - string formatedCode = IncludeFormatter.FormatIncludes(source, "filename.cpp", new string[] { }, settings); - Assert.AreEqual(expectedFormatedCode, formatedCode); - } -} -} diff --git a/Tests/IncludeGraphTest.cs b/Tests/IncludeGraphTest.cs deleted file mode 100644 index 0a88042..0000000 --- a/Tests/IncludeGraphTest.cs +++ /dev/null @@ -1,227 +0,0 @@ -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using IncludeToolbox; -using System.IO; -using IncludeToolbox.Graph; -using IncludeToolbox.Formatter; -using System.Collections.Generic; - -namespace Tests -{ - [TestClass] - public class IncludeGraphTest - { - [TestMethod] - public void WriteSimpleDGML() - { - string filenameTestOutput = "testdata/output.dgml"; - string filenameComparision= "testdata/simplegraph.dgml"; - try - { - File.Delete(filenameTestOutput); - } - catch { } - - var writer = new DGMLGraph(); - writer.Nodes.Add(new DGMLGraph.Node { Id = "0", Label = "0" }); - writer.Nodes.Add(new DGMLGraph.Node { Id = "1", Label = "1label" }); - writer.Nodes.Add(new DGMLGraph.Node { Id = "2", Label = "2" }); - writer.Nodes.Add(new DGMLGraph.Node { Id = "3", Label = "3" }); - writer.Links.Add(new DGMLGraph.Link { Source = "0", Target = "1" }); - writer.Links.Add(new DGMLGraph.Link { Source = "1", Target = "0", Label = "backlink"}); - writer.Links.Add(new DGMLGraph.Link { Source = "1", Target = "1", Label = "loop" }); - writer.Links.Add(new DGMLGraph.Link { Source = "2", Target = "3", }); - writer.Links.Add(new DGMLGraph.Link { Source = "4", Target = "3", }); - writer.Links.Add(new DGMLGraph.Link { Source = "2", Target = "0", }); - writer.Serialize(filenameTestOutput); - - string expectedFile = File.ReadAllText(filenameComparision); - string writtenFile = File.ReadAllText(filenameTestOutput); - - Assert.AreEqual(expectedFile, writtenFile); - - // For a clean environment! - File.Delete(filenameTestOutput); - } - - [TestMethod] - public void CustomGraphParse() - { - string[] noParseDirectories = new[] { Utils.GetExactPathName("testdata/subdir/subdir") }; - - IncludeGraph graph = new IncludeGraph(); - graph.AddIncludesRecursively_ManualParsing(Utils.GetExactPathName("testdata/source0.cpp"), Enumerable.Empty(), noParseDirectories); - graph.AddIncludesRecursively_ManualParsing(Utils.GetExactPathName("testdata/source1.cpp"), Enumerable.Empty(), noParseDirectories); - graph.AddIncludesRecursively_ManualParsing(Utils.GetExactPathName("testdata/testinclude.h"), Enumerable.Empty(), noParseDirectories); // Redundancy shouldn't matter. - - // Check items. - Assert.AreEqual(7, graph.GraphItems.Count); - bool newItem = false; - var source0 = graph.CreateOrGetItem("testdata/source0.cpp", out newItem); - Assert.AreEqual(false, newItem); - var source1 = graph.CreateOrGetItem("testdata/source1.cpp", out newItem); - Assert.AreEqual(false, newItem); - var testinclude = graph.CreateOrGetItem("testdata/testinclude.h", out newItem); - Assert.AreEqual(false, newItem); - var subdir_testinclude = graph.CreateOrGetItem("testdata/subdir/teStinclude.h", out newItem); - Assert.AreEqual(false, newItem); - var subdir_inline = graph.CreateOrGetItem("testdata/subdir/inline.inl", out newItem); - Assert.AreEqual(false, newItem); - var subdirsubdir_subsub = graph.CreateOrGetItem("testdata/subdir/subdir/subsub.h", out newItem); - Assert.AreEqual(false, newItem); - var broken = graph.CreateOrGetItem("broken!", out newItem); - Assert.AreEqual(false, newItem); - - // Check includes in source0. - Assert.AreEqual(2, source0.Includes.Count); - Assert.AreEqual(0, source0.Includes[0].IncludeLine.LineNumber); // Different line numbers. - Assert.AreEqual(5, source0.Includes[1].IncludeLine.LineNumber); - Assert.AreEqual(subdir_testinclude, source0.Includes[0].IncludedFile); // But point to the same include. - Assert.AreEqual(subdir_testinclude, source0.Includes[1].IncludedFile); - - // Check includes in source1. - Assert.AreEqual(1, source1.Includes.Count); - Assert.AreEqual(testinclude, source1.Includes[0].IncludedFile); - - // Check includes in testinclude. - Assert.AreEqual(1, testinclude.Includes.Count); - Assert.AreEqual(subdir_testinclude, testinclude.Includes[0].IncludedFile); - - // Check includes in subdir_testinclude. - Assert.AreEqual(2, subdir_testinclude.Includes.Count); - Assert.AreEqual(subdir_inline, subdir_testinclude.Includes[0].IncludedFile); - Assert.AreEqual(subdirsubdir_subsub, subdir_testinclude.Includes[1].IncludedFile); - - // Check includes in subdir_inline. - Assert.AreEqual(1, subdir_inline.Includes.Count); - Assert.AreEqual(broken, subdir_inline.Includes[0].IncludedFile); - Assert.AreEqual(true, subdir_inline.Includes[0].IncludeLine.ContainsActiveInclude); - - // Check includes in subdirsubdir_subsub - should be empty since we have this dir on the ignore list. - Assert.AreEqual(0, subdirsubdir_subsub.Includes.Count); - - // Check item representing a unresolved include. - Assert.AreEqual(0, broken.Includes.Count); - } - - private void RemoveAbsolutePathsFromDGML(DGMLGraph dgml, IEnumerable includeDirectories) - { - foreach (var item in dgml.Nodes) - { - item.Id = IncludeFormatter.FormatPath(item.Id, FormatterOptionsPage.PathMode.Shortest, includeDirectories); - item.Label = IncludeFormatter.FormatPath(item.Label, FormatterOptionsPage.PathMode.Shortest, includeDirectories); - } - foreach (var link in dgml.Links) - { - link.Source = IncludeFormatter.FormatPath(link.Source, FormatterOptionsPage.PathMode.Shortest, includeDirectories); - link.Target = IncludeFormatter.FormatPath(link.Target, FormatterOptionsPage.PathMode.Shortest, includeDirectories); - } - } - - [TestMethod] - public void CustomGraphParseToDGML_NoGrouping() - { - CustomGraphParseToDGML(false); - } - - [TestMethod] - public void CustomGraphParseToDGML_WithGrouping() - { - CustomGraphParseToDGML(true); - } - - public void CustomGraphParseToDGML(bool grouping) - { - string filenameTestOutput = "testdata/output.dgml"; - string filenameComparision = grouping ? "testdata/includegraph_grouped.dgml" : "testdata/includegraph.dgml"; - string filenameComparision_color = grouping ? "testdata/includegraph_withcolors_grouped.dgml" : "testdata/includegraph_withcolors.dgml"; - - string[] noParseDirectories = new[] { Utils.GetExactPathName("testdata/subdir/subdir") }; - - IncludeGraph graph = new IncludeGraph(); - graph.AddIncludesRecursively_ManualParsing(Utils.GetExactPathName("testdata/source0.cpp"), Enumerable.Empty(), noParseDirectories); - graph.AddIncludesRecursively_ManualParsing(Utils.GetExactPathName("testdata/source1.cpp"), Enumerable.Empty(), noParseDirectories); - - // Formatting... - var includeDirectories = new[] { Path.Combine(System.Environment.CurrentDirectory, "testdata") }; - foreach (var item in graph.GraphItems) - item.FormattedName = IncludeFormatter.FormatPath(item.AbsoluteFilename, FormatterOptionsPage.PathMode.Shortest, includeDirectories); - - // To DGML and save. - // Since we don't want to have absolute paths in our compare/output dgml we hack the graph before writing it out. - var dgml = graph.ToDGMLGraph(grouping, true); - RemoveAbsolutePathsFromDGML(dgml, new[] { System.Environment.CurrentDirectory }); - - // Without colors. - { - dgml.Serialize(filenameTestOutput); - string expectedFile = File.ReadAllText(filenameComparision); - string writtenFile = File.ReadAllText(filenameTestOutput); - Assert.AreEqual(expectedFile, writtenFile); - } - - // With colors. - { - dgml.ColorizeByTransitiveChildCount(System.Drawing.Color.White, System.Drawing.Color.Black); - dgml.Serialize(filenameTestOutput); - string expectedFile = File.ReadAllText(filenameComparision_color); - string writtenFile = File.ReadAllText(filenameTestOutput); - Assert.AreEqual(expectedFile, writtenFile); - } - - // For a clean environment! - File.Delete(filenameTestOutput); - } - - [TestMethod] - public void IncludeFolderGrouping() - { - IncludeGraph graph = new IncludeGraph(); - string sourceFile = Utils.GetExactPathName("testdata/source0.cpp"); - graph.AddIncludesRecursively_ManualParsing(sourceFile, Enumerable.Empty(), new string[] { }); - - var root = new IncludeToolbox.GraphWindow.FolderIncludeTreeViewItem_Root(graph.GraphItems, graph.CreateOrGetItem(sourceFile, out bool isNew)); - var children = root.Children; - - // Check if tree is as expected. - { - Assert.AreEqual(2, children.Count); - - var unresolvedFolder = children.First(x => x.Name == ""); - Assert.IsNotNull(unresolvedFolder); - Assert.IsInstanceOfType(unresolvedFolder, typeof(IncludeToolbox.GraphWindow.FolderIncludeTreeViewItem_Folder)); - - var subdirFolder = children.First(x => x.Name.EndsWith("testdata\\subdir\\")); - Assert.IsTrue(Path.IsPathRooted(subdirFolder.Name)); - Assert.IsNotNull(unresolvedFolder); - Assert.IsInstanceOfType(unresolvedFolder, typeof(IncludeToolbox.GraphWindow.FolderIncludeTreeViewItem_Folder)); - - // subdir folder - { - Assert.AreEqual(3, subdirFolder.Children.Count); - - var testinclude = subdirFolder.Children.First(x => x.Name == "testinclude.h"); - Assert.IsNotNull(testinclude); - Assert.IsInstanceOfType(testinclude, typeof(IncludeToolbox.GraphWindow.FolderIncludeTreeViewItem_Leaf)); - - var inline = subdirFolder.Children.First(x => x.Name == "inline.inl"); - Assert.IsNotNull(inline); - Assert.IsInstanceOfType(inline, typeof(IncludeToolbox.GraphWindow.FolderIncludeTreeViewItem_Leaf)); - - var subdirsubdirFolder = subdirFolder.Children.First(x => x.Name == "subdir\\"); - Assert.IsNotNull(subdirsubdirFolder); - Assert.IsInstanceOfType(subdirsubdirFolder, typeof(IncludeToolbox.GraphWindow.FolderIncludeTreeViewItem_Folder)); - - // subdir\subdir folder - { - Assert.AreEqual(1, subdirsubdirFolder.Children.Count); - - var subsubh = subdirsubdirFolder.Children.First(x => x.Name == "subsub.h"); - Assert.IsNotNull(subsubh); - Assert.IsInstanceOfType(subsubh, typeof(IncludeToolbox.GraphWindow.FolderIncludeTreeViewItem_Leaf)); - } - } - } - } - } -} diff --git a/Tests/IncludeLineInfoTests.cs b/Tests/IncludeLineInfoTests.cs deleted file mode 100644 index 0786891..0000000 --- a/Tests/IncludeLineInfoTests.cs +++ /dev/null @@ -1,210 +0,0 @@ -using System.Linq; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using IncludeToolbox.Formatter; - -namespace Tests -{ - [TestClass] - public class IncludeLineInfoTests - { - [TestMethod] - public void SimpleParsing() - { - string sourceCode = -@"#include ""test.h"" - #include - - #pragma once - #if -#endif -int main () {}"; - - var parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.None); - Assert.AreEqual(parse.Count, 7); - - Assert.AreEqual("test.h", parse[0].IncludeContent); - Assert.AreEqual("tüst.hpp", parse[1].IncludeContent); - Assert.AreEqual("", parse[2].IncludeContent); - Assert.AreEqual("", parse[3].IncludeContent); - Assert.AreEqual("", parse[4].IncludeContent); - Assert.AreEqual("", parse[5].IncludeContent); - Assert.AreEqual("", parse[6].IncludeContent); - - Assert.AreEqual("#include \"test.h\"", parse[0].RawLine); - Assert.AreEqual(" #include ", parse[1].RawLine); - Assert.AreEqual(" ", parse[2].RawLine); - Assert.AreEqual(" #pragma once", parse[3].RawLine); - Assert.AreEqual(" #if", parse[4].RawLine); - Assert.AreEqual("#endif", parse[5].RawLine); - Assert.AreEqual("int main () {}", parse[6].RawLine); - - Assert.AreEqual(IncludeLineInfo.DelimiterType.Quotes, parse[0].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[1].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.None, parse[2].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.None, parse[3].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.None, parse[4].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.None, parse[5].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.None, parse[6].LineDelimiterType); - - Assert.AreEqual(true, parse[0].ContainsPreProcessorDirective); - Assert.AreEqual(true, parse[1].ContainsPreProcessorDirective); - Assert.AreEqual(false, parse[2].ContainsPreProcessorDirective); - Assert.AreEqual(true, parse[3].ContainsPreProcessorDirective); - Assert.AreEqual(true, parse[4].ContainsPreProcessorDirective); - Assert.AreEqual(true, parse[5].ContainsPreProcessorDirective); - Assert.AreEqual(false, parse[6].ContainsPreProcessorDirective); - - - parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.RemoveEmptyLines); - Assert.AreEqual(parse.Count, 6); - Assert.AreEqual(0, parse[0].LineNumber); - Assert.AreEqual(1, parse[1].LineNumber); - Assert.AreEqual(3, parse[2].LineNumber); - Assert.AreEqual(4, parse[3].LineNumber); - Assert.AreEqual(5, parse[4].LineNumber); - Assert.AreEqual(6, parse[5].LineNumber); - } - - [TestMethod] - public void SingleLineComments() - { - string sourceCode = -@"// #include -#include "; - - var parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.RemoveEmptyLines); - Assert.AreEqual(IncludeLineInfo.DelimiterType.None, parse[0].LineDelimiterType); - Assert.AreEqual("", parse[0].IncludeContent); - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[1].LineDelimiterType); - Assert.AreEqual("include", parse[1].IncludeContent); - } - - [TestMethod] - public void MultiLineComments() - { - // Technically some of the code here has C++ compile errors since preprocessor must always start before any whitespace. - // But we want to handle this gracefully! - - string sourceCode = "/* test // */ #include "; - var parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.RemoveEmptyLines); - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[0].LineDelimiterType); - - parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.RemoveEmptyLines); - sourceCode = "#include /* test */ "; - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[0].LineDelimiterType); - - - sourceCode = -@"#include /* -/* #include -sdfsdf // #include -dfdf // */ #include "; - - parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.RemoveEmptyLines); - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[0].LineDelimiterType); - Assert.AreEqual("there0", parse[0].IncludeContent); - Assert.AreEqual(IncludeLineInfo.DelimiterType.None, parse[1].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.None, parse[2].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[3].LineDelimiterType); - Assert.AreEqual("there1", parse[3].IncludeContent); - } - - [TestMethod] - public void PreprocessorConditionals() - { - string sourceCode = -@"/* #if */ #include -#if SomeCondition -#include -//#include -#else -#include -//#endif -/* -#endif -*/ -#include -#endif -#include "; - - var parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.RemoveEmptyLines | ParseOptions.IgnoreIncludesInPreprocessorConditionals); - Assert.AreEqual(2, parse.Count(x => x.LineDelimiterType != IncludeLineInfo.DelimiterType.None)); - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[0].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[parse.Count - 1].LineDelimiterType); - Assert.AreEqual(0, parse[0].LineNumber); - Assert.AreEqual(1, parse[1].LineNumber); - - parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.KeepOnlyValidIncludes | ParseOptions.IgnoreIncludesInPreprocessorConditionals); - Assert.AreEqual(2, parse.Count); - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[0].LineDelimiterType); - Assert.AreEqual(IncludeLineInfo.DelimiterType.AngleBrackets, parse[parse.Count - 1].LineDelimiterType); - Assert.AreEqual(0, parse[0].LineNumber); - Assert.AreEqual(12, parse[1].LineNumber); - - parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.RemoveEmptyLines); - Assert.AreEqual(5, parse.Count(x => x.LineDelimiterType != IncludeLineInfo.DelimiterType.None)); - } - - [TestMethod] - public void ResolveIncludes() - { - string sourceCode = -@"#include ""testinclude.h"" -#include <../testinclude.h> -#include -#adsfsdf not a include"; - - string[] includeDirs = - { - "C:/hopefullyyoudonthavethisdir/", - "garbage", - System.Environment.CurrentDirectory, - System.IO.Path.Combine(System.Environment.CurrentDirectory, "testdata/subdir"), - System.IO.Path.Combine(System.Environment.CurrentDirectory, "testdata"), - }; - - var parse = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.RemoveEmptyLines); - - bool successfullyResolved = false; - - string resolvedPath = parse[0].TryResolveInclude(includeDirs, out successfullyResolved); - StringAssert.EndsWith(resolvedPath, "testdata\\subdir\\testinclude.h"); - Assert.AreEqual(true, successfullyResolved); - - resolvedPath = parse[1].TryResolveInclude(includeDirs, out successfullyResolved); - StringAssert.EndsWith(resolvedPath, "testdata\\testinclude.h"); - Assert.AreEqual(true, successfullyResolved); - - resolvedPath = parse[2].TryResolveInclude(includeDirs, out successfullyResolved); - Assert.AreEqual("unresolvable", resolvedPath); - Assert.AreEqual(false, successfullyResolved); - - resolvedPath = parse[3].TryResolveInclude(includeDirs, out successfullyResolved); - Assert.AreEqual("", resolvedPath); - Assert.AreEqual(false, successfullyResolved); - } - - [TestMethod] - public void MixedLineEndings() - { - // The end of this string is tricky as it adds a 3 newlines: \r\n (win), \n (unix), \r (mac old) - string sourceCode = "#include \n#include \r\n#include \r#include \r\n\n\r"; - var parseWithoutEmpty = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.RemoveEmptyLines); - Assert.AreEqual(4, parseWithoutEmpty.Count); - var parseWithEmpty = IncludeLineInfo.ParseIncludes(sourceCode, ParseOptions.None); - Assert.AreEqual(6, parseWithEmpty.Count); - } - - [TestMethod] - public void PreserveFlag() - { - string testCode = @"#include // $include-toolbox-preserve$ -#include ""test"""; - - var parsedLines = IncludeLineInfo.ParseIncludes(testCode, ParseOptions.RemoveEmptyLines); - Assert.AreEqual(2, parsedLines.Count); - Assert.IsTrue(parsedLines[0].ShouldBePreserved); - Assert.IsFalse(parsedLines[1].ShouldBePreserved); - } - } -} diff --git a/Tests/IncludeWhatYouUseTests.cs b/Tests/IncludeWhatYouUseTests.cs deleted file mode 100644 index 12e8e8e..0000000 --- a/Tests/IncludeWhatYouUseTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -using IncludeToolbox.IncludeWhatYouUse; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System; -using System.Linq; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Tests -{ - [TestClass] - public class IncludeWhatYouUseTests - { - private void ReportProgress(string section, string status, float percentage) - { - System.Diagnostics.Debug.WriteLine($"{section} - {status} - {percentage}"); - } - - private string GetCleanExecutableDir() - { - var executableDir = Path.Combine(Environment.CurrentDirectory, "testdata", "iwyu", "include-what-you-use.exe"); - try - { - Directory.Delete(Path.GetDirectoryName(executableDir), true); - } - catch { } - return executableDir; - } - - /// - /// Tests the automatic include what you use download and updating function. - /// - /// - /// This indirectly also tests whether our iwyu repository is healthy! - /// - [TestMethod] - public async Task DownloadAsync() - { - var executableDir = GetCleanExecutableDir(); - - Assert.AreEqual(false, File.Exists(executableDir)); - Assert.AreEqual(true, await IWYUDownload.IsNewerVersionAvailableOnline(executableDir)); // Nothing here practically means that there is a new version. - - await IWYUDownload.DownloadIWYU(executableDir, ReportProgress, new CancellationToken()); - - Assert.AreEqual(false, await IWYUDownload.IsNewerVersionAvailableOnline(executableDir)); - Assert.AreEqual(true, File.Exists(executableDir)); - } - - [TestMethod] - public void AddMappingFilesFromDownloadDir() - { - var executableDir = GetCleanExecutableDir(); - var folder = Path.GetDirectoryName(executableDir); - Directory.CreateDirectory(folder); - - string test0Path = Path.Combine(folder, "test0.imp"); - File.Create(test0Path); - string test1Path = Path.Combine(folder, "test1.imp"); - File.Create(test1Path); - File.Create(Path.Combine(folder, "test2.mip")); - - var optionPage = new IncludeToolbox.IncludeWhatYouUseOptionsPage(); - optionPage.MappingFiles = new string[] { "doesn't exist.imp", test1Path }; - - var newMappingFiles = IWYUDownload.GetMappingFilesNextToIwyuPath(executableDir); - - { - var newMappingFilesArray = newMappingFiles.ToArray(); - Assert.AreEqual(2, newMappingFilesArray.Length); - Assert.AreEqual(test0Path, newMappingFilesArray[0]); - Assert.AreEqual(test1Path, newMappingFilesArray[1]); - } - - optionPage.AddMappingFiles(newMappingFiles); - { - Assert.AreEqual(3, optionPage.MappingFiles.Length); - Assert.AreEqual(true, optionPage.MappingFiles.Contains(test0Path)); - Assert.AreEqual(true, optionPage.MappingFiles.Contains(test1Path)); - Assert.AreEqual(true, optionPage.MappingFiles.Contains("doesn't exist.imp")); - } - } - } -} diff --git a/Tests/Properties/AssemblyInfo.cs b/Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 26c284a..0000000 --- a/Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Tests")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: ComVisible(false)] - -[assembly: Guid("b31c729a-9cf7-4dde-8c93-6a671d21807a")] - -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj deleted file mode 100644 index 8a741ed..0000000 --- a/Tests/Tests.csproj +++ /dev/null @@ -1,114 +0,0 @@ - - - - - Debug - AnyCPU - {F577F5D2-5E3C-43BE-9030-AF2609A0917A} - Library - Properties - Tests - Tests - v4.8 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - 16.10.31321.278 - - - 17.3.2088 - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - 1.3.2 - - - 1.3.2 - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - {f9e250c6-a7ad-4888-8f17-6876736b8dcf} - IncludeToolbox - - - - - \ No newline at end of file diff --git a/Tests/Tests/EmptyNamespacesTests.cs b/Tests/Tests/EmptyNamespacesTests.cs new file mode 100644 index 0000000..5b69af9 --- /dev/null +++ b/Tests/Tests/EmptyNamespacesTests.cs @@ -0,0 +1,25 @@ +using IncludeToolbox; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tests +{ + internal class EmptyNamespacesTests + { + static readonly string V = "namespace Core\n{\nclass CdwkAttributeData;\nclass Command;\n}\n\n#ifdef CACHE_OBJECTS\n#include \n#endif\n\n\nnamespace Core\n{\n\n}\n"; + + [Test] + public void TestRealistic() + { + var text = V; + var lines = Parser.ParseEmptyNamespaces(text); + var lines2 = Parser.Parse(text); + Assert.IsTrue(lines.Any()); + Assert.That(lines.Length, Is.EqualTo(1)); + Assert.That(lines[0].Start, Is.EqualTo(lines2.Namespaces[1].span.Start)); + } + } +} diff --git a/Tests/Tests/FullParserTests.cs b/Tests/Tests/FullParserTests.cs new file mode 100644 index 0000000..6f3f15f --- /dev/null +++ b/Tests/Tests/FullParserTests.cs @@ -0,0 +1,117 @@ +using IncludeToolbox; + +namespace Tests +{ + internal class FullParserTests + { + private const string V = "#pragma once\n\n#include \n#include \n#include \n#include \n#include \n#include \n\n#include \n\nnamespace App\n{\nclass Resource;\nclass Document;\nclass ObjectFactory;\n\n// TaskDefinitionStructure used for conversion from older version\nstruct TaskDefinition\n{\n Base::String oldPropHide;\n Base::String oldPropShow;\n};\n\nclass LX_APP_EXPORT Task : public App::Process\n{\npublic:\n enum TaskTypeEnum // Only helper enum, is not used for property\n {\n DAY,\n WEEK,\n WEEK5,\n WEEK2DR,\n HOUR,\n };\n enum MyVisibilityState\n {\n Invisible,\n Mixed,\n Visible,\n Undefined\n };\n\n typedef App::Process inherited;\n friend class Task_Factory;\n friend struct TaskSort;\n\n TYPESYSTEM_HEADER();\n LX_NODE_HEADER();\n\npublic:\n // CmdCreateTaskItems(TargetTask, Operation - add/Insert)\t// Create Task object + ScheduleTimeControl object + all relations objects\n // CmdAttach2Task(App::Task * task, App::Element * elem);\t// Create/Remove relations between Object(Elements) and Task\n\n#pragma region IFC Definition\n Core::PropertyText TaskID;\n Core::PropertyText Status;\n Core::PropertyText WorkMethod;\n Core::PropertyBoolean IsMilestone;\n Core::PropertyInteger Priority;\n\n // App::Task * getNestsObject();\t\t\t\t\t\t\t\t\t\t\t// \tdecomposes_ SET[0:1]\t// Restrict the relationship \'Nests\' (decomposes_) inherited from Object\n // to Task. std::unordered_set getIsNestedByObjects();\t\t\t\t//\t \tisDecomposedBy_\t \t\t// Restrict the relationship\n // \'IsNestedBy\' (isDecomposedBy_) inherited from Object to Task. Base::String getName() { return userName.getValue(); }\t\t\t\t//\n // userName\t\t\t\t// The Name attribute should be inserted to describe the task name.\n#pragma endregion IFC Definition\n\n // App::Task is attached to App::ScheduleTimeControl\n // Create App::RelAssignsTasks if does not exist and set links and back links\n App::RelAssignsTasks* AttachScheduleTimeControl(App::ScheduleTimeControl* schedule, App::RelAssignsTasks* rel = NULL);\n // Clear links and backlinks and return relation for cmd or dispose purposes\n App::RelAssignsTasks* DetachScheduleTimeControl();\n // ScheduleTimeControl is stored in ObjectDefinition.hasAssignments_ inside App::RelAssignsTasks as RelatingControl, but I use Internal pointer to\n // save finding\n App::ScheduleTimeControl* GetScheduleTimeControl()\n {\n return _attachedScheduleTimeControl;\n }\n // Create App::RelNests and set links and back links\n App::RelNests* AttachParentTask(App::Task* parent, App::RelNests* rel = NULL);\n // Clear links and backlinks return App::RelNests for Cmd or dispose purposes\n App::RelNests* DetachParentTask();\n\n bool ContainParent(App::Task* parent);\n App::Task* GetParentTask();\n int GetLevelOfNested();\n // App::Task * GetTopParentTask();\n bool HasChildren();\n bool ContainsChildTaskInTree(App::Task* child);\n App::Task* GetFirstChildTask();\n App::Task* GetLastChildTask();\n App::Task* GetLastLeafChildTask();\n int GetCountOfAllChildren();\n // Children are sorted according App::RelSequence\n inline std::vector GetChildTasks()\n {\n return _soretedChildren;\n }\n inline std::set GetMultigeoGuId()\n {\n return _multigeoGuId;\n }\n inline void SetMultigeoGuId(std::set setVal)\n {\n _multigeoGuId = setVal;\n }\n\n App::RelSequence* AttachPredecesorTask(App::Task* task, App::RelSequence* rel = NULL); // Create App::RelSequence and set links and back links\n App::RelSequence* DetachPredecesorTask(); // unset links and back links return App::RelSequence\t// for Cmd or dispose\n App::RelSequence* AttachSucessorTask(App::Task* task, App::RelSequence* rel = NULL); // Create App::RelSequence and set links and back links\n App::RelSequence* DetachSucessorTask(); // unset links and back links return App::RelSequence\t// for Cmd or dispose\n\n App::Task* GetPredecessorTask();\n App::Task* GetSuccessorTask();\n // Return FirstChild Task or Succesor Task or Succesor of Parent if this child does not have succesor\t(Return \"next line\" in tree)\n App::Task* GetNextLineTaskInTree();\n App::Task* GetPrevLineTaskInTree();\n\n bool IsConnectedTo(App::Task* task);\n\n std::set GetRelatedElements(); // only App::Elements from App::Process.OperatesOn // No childern\n std::set GetRelatedResources(); // only App::Resource from App::Process.OperatesOn // No children\n\n std::set GetAllRelatedElements(); // my RelatedElements + RelatedElements of my children recursively\n\n App::RelAssignsToProcess* AttachResource(App::Resource* resTempl, int qua, App::RelAssignsToProcess* rel = NULL);\n App::RelAssignsToProcess* DetachResource(App::Resource* resTempl)\n {\n return AttachResource(resTempl, 0);\n }\n\n#pragma region Lexocad properties\n App::Task::MyVisibilityState GetVisibility();\n void SetExpanded(bool onoff)\n {\n expanded.setValue(onoff);\n }\n bool IsExpanded()\n {\n return expanded.getValue();\n }\n bool IsParentExpanded();\n void SetTaskNumberWithoutTouch(int num);\n void SetTaskNumber(float num, bool extended = false);\n float GetTaskNumber(bool extended = false) const;\n\n Core::PropertyInteger elementCount;\n Core::PropertyBoolean demolition;\n Core::PropertyBoolean subComponent;\n Core::PropertyBoolean moveElement;\n App::PropertyPlacementList originalElementsPlacement;\n App::PropertyPlacementList newElementsPlacement;\n App::PropertyPlacementMap originalElementsPlacementMap;\n App::PropertyPlacementMap newElementsPlacementMap;\n\n\n Core::PropertyLinkSetBase hideTasks;\n Core::PropertyBoolean hideSelf;\n Core::PropertyLinkSetBase showTasks;\n Core::PropertyLink taskColorType; // SharedObject\n\n Core::PropertyLink material;\n\n Core::PropertyText UserProperty1; // Km\n Core::PropertyText UserProperty2; // Scene\n Core::PropertyText UserProperty3; // eBKP\n\n Core::PropertyText MSProjectID;\n Core::PropertyIndex number2dr;\n#pragma endregion Lexocad properties\n\n\n // void addAssignmentObject(App::ObjectDefinition * object, App::ObjectTypeEnum objectType = NOTDEFINED) override;\n\n virtual Core::ExecuteStatus execute(Core::ExecuteContext* /*context*/) override\n {\n return Core::EXECUTE_OK;\n }\n void restore(Base::AbstractXMLReader& reader, Base::PersistenceVersion& version) override;\n\n bool mustbeSaved() const override\n {\n return true;\n }\n\n App::Task* FindTaskNumber(int taskNum, App::Task* task = NULL);\n App::Task* FindTaskNumberOrBigger(int taskNum, int originalNum, App::Task* task = NULL);\n App::Task* FindTaskNumberOrSmaller(int taskNum, int originalNum, App::Task* task = NULL);\n App::Task* FindTaskOnPosition(int offsetLine);\n\n // static\t\tApp::Task * FindTaskNumberOrSmaller(int taskNum, App::Task * firstRootTask);\n\n /// Returns task of the given number if exists, nullptr otherwise\n static App::Task* getTaskByNumber(Core::CoreDocument* cdoc, int number);\n\n void InitPrivateProperties();\n\n // Not Saved temporary data, used for building Tree and GUI stuff\n void* TreeItemPointer; // for storing QStandardItem during buildng QStandardItemModel in TaskTreeView plugin\n void* TreeSimpleItemPointer; // for storing QStandardItem during buildng QTreeWidget in TaskTreeViewSimple plugin\n QDateTime TmpStart; // Tmp date for updating formula\n QDateTime TmpEnd; // Tmp date for updating formula\n int MyCheckState; // For MyCheckState used insted of QtCheckState, because Andreas wants different behavior/order of states\n\nprotected:\n Task(void);\n virtual ~Task(void);\n\n Core::PropertyBoolean expanded;\n Core::PropertyInteger taskNumber;\n\n void restoreProperty(Core::Property* property,\n const Base::String& name,\n Base::AbstractXMLReader& reader,\n Base::PersistenceVersion& version) override;\n void sortChildren();\n\nprivate:\n TaskDefinition _newDefRestore; // for converting from older version\n\n // Store pointer to ScheduleTimeControl to save time by finding type in ObjectDefinition.hasAssignments_\n App::ScheduleTimeControl* _attachedScheduleTimeControl;\n // Store sorted child tasks vector to save time\n std::vector _soretedChildren;\n // multigeo children list, needed for week2dr mode (hacks)\n std::set _multigeoGuId;\n\n App::Task* findTaskOnPosition(int offsetLine, int& counter);\n};\n\nDECLARE_OBJECT_FACTORY(Task_Factory, App::Task, IFCTASK)\n\n\n\nstruct TaskSort\n{\n inline bool operator()(const App::Task* t1, const App::Task* t2)\n {\n if (t1->taskNumber.getValue() < t2->taskNumber.getValue())\n return true;\n else if (t1->taskNumber.getValue() > t2->taskNumber.getValue())\n return false;\n else\n return t1 < t2;\n }\n};\n} // namespace App\n\n#ifndef SWIG\nQ_DECLARE_METATYPE(App::Task*)\n#endif"; + + [Test] + public void TestBasic() + { + var text = "#include "; + var lines = Parser.Parse(text, true); + Assert.IsTrue(lines.Includes.Any()); + Assert.IsFalse(lines.Declarations.Any()); + Assert.IsFalse(lines.Namespaces.Any()); + + var line = lines.Includes[0]; + Assert.That(text, Is.EqualTo(line.Project(text))); + } + [Test] + public void TestBasic2() + { + var text = "#include \n"; + var lines = Parser.Parse(text, true); + Assert.IsTrue(lines.Includes.Any()); + Assert.IsFalse(lines.Declarations.Any()); + Assert.IsFalse(lines.Namespaces.Any()); + + var line = lines.Includes[0]; + Assert.That(line.Project(text), Is.EqualTo("#include ")); + } + [Test] + public void TestBasicNamespace() + { + var text = "namespace a{}"; + var lines = Parser.Parse(text); + Assert.IsFalse(lines.Includes.Any()); + Assert.IsFalse(lines.Declarations.Any()); + Assert.IsTrue(lines.Namespaces.Any()); + + var line = lines.Namespaces[0]; + Assert.That(line.namespaces[0], Is.EqualTo("a")); + + + var text2 = "namespace {}"; + var lines2 = Parser.Parse(text2); + Assert.IsFalse(lines2.Includes.Any()); + Assert.IsFalse(lines2.Declarations.Any()); + Assert.IsFalse(lines2.Namespaces.Any()); + } + [Test] + public void TestBasicFWDDecl() + { + var text = "namespace a{class b;\r\n}"; + var lines = Parser.Parse(text); + Assert.IsFalse(lines.Includes.Any()); + Assert.IsTrue(lines.Declarations.Any()); + Assert.IsTrue(lines.Namespaces.Any()); + + var line = lines.Namespaces[0]; + Assert.That(line.namespaces[0], Is.EqualTo("a")); + + var decl = lines.Declarations[0]; + Assert.That(decl.namespaces[0], Is.EqualTo("a")); + Assert.That(decl.ID, Is.EqualTo("b")); + Assert.That(text.Substring(decl.span.Start, decl.span.Length), Is.EqualTo("class b;")); + } + [Test] + public void TestRealistic() + { + var text = V; + var lines = Parser.Parse(text); + Assert.IsTrue(lines.Includes.Any()); + Assert.IsTrue(lines.Includes.Count == 7); + Assert.IsTrue(lines.Declarations.Any()); + Assert.IsTrue(lines.Declarations.Count == 3); + Assert.IsTrue(lines.Namespaces.Any()); + } + + [Test] + public void TestPreprocessor() + { + var text = "#include \r\n#if 0\r\n#include \r\n#endif"; + var lines = Parser.Parse(text); + Assert.IsTrue(lines.Includes.Any()); + Assert.IsTrue(lines.Includes.Count == 2); + + Assert.That(lines.InsertionPoint, Is.EqualTo(lines.Includes[0].End)); + + var text2 = "#if 0\r\n#include \r\n #include \r\n#endif"; + var lines2 = Parser.Parse(text2); + Assert.IsTrue(lines2.Includes.Any()); + Assert.IsTrue(lines2.Includes.Count == 2); + + Assert.That(lines2.InsertionPoint, Is.EqualTo(lines2.Includes[1].End)); + + var text3 = "#if 0\r\n#include \r\n#endif #include \r\n"; + var lines3 = Parser.Parse(text3); + Assert.IsTrue(lines3.Includes.Any()); + Assert.IsTrue(lines3.Includes.Count == 2); + + Assert.That(lines3.InsertionPoint, Is.EqualTo(lines3.Includes[1].End)); + } + + [Test] + public void TestNonConventional() + { + var text = "#include \r\n namespace a{} \r\n#include "; + var lines = Parser.Parse(text); + Assert.IsTrue(lines.Includes.Any()); + Assert.IsTrue(lines.Includes.Count == 2); + + Assert.That(lines.InsertionPoint, Is.EqualTo(lines.Includes[0].End)); + } + } +} diff --git a/Tests/Tests/IncludeParseTests.cs b/Tests/Tests/IncludeParseTests.cs new file mode 100644 index 0000000..a3ed8ff --- /dev/null +++ b/Tests/Tests/IncludeParseTests.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using IncludeToolbox; + +namespace Tests +{ + internal class IncludeParseTests + { + [Test] + public void TestBasic() + { + var text = "#include "; + var lines = Parser.ParseInclues(text, true); + Assert.IsTrue(lines.Any()); + var line = lines[0]; + Assert.IsTrue(line.keep == false); + Assert.IsTrue(line.FullFile.SequenceEqual("")); + Assert.IsTrue(line.Project(text).SequenceEqual(text)); + } + + [Test] + public void TestBasicNewline() + { + var text = "#include \r\n //gigachad"; + var lines = Parser.ParseInclues(text, true); + Assert.IsTrue(lines.Any()); + var line = lines[0]; + Assert.IsTrue(line.keep == false); + Assert.IsTrue(line.FullFile.SequenceEqual("")); + Assert.IsTrue(line.Project(text).SequenceEqual("#include \r\n")); + + var text2 = "#include //gigachad"; + var lines2 = Parser.ParseInclues(text2, true); + Assert.IsTrue(lines.Any()); + var line2 = lines2[0]; + Assert.IsTrue(line2.keep == false); + Assert.IsTrue(line2.FullFile.SequenceEqual("")); + Assert.IsTrue(line2.Project(text2).SequenceEqual(text2)); + + var text3 = "#include //gigachad\n"; + var lines3 = Parser.ParseInclues(text3, true); + Assert.IsTrue(lines.Any()); + var line3 = lines3[0]; + Assert.IsTrue(line3.keep == false); + Assert.IsTrue(line3.FullFile.SequenceEqual("")); + Assert.IsTrue(line3.Project(text3).SequenceEqual(text3)); + } + + [Test] + public void TestSpan() + { + var text = " #include \r\n#include "; + var lines = Parser.ParseInclues(text[3..], true); + Assert.IsTrue(lines.Length == 2); + var line = lines[1]; + Assert.IsTrue(line.keep == false); + Assert.IsTrue(line.FullFile.SequenceEqual("")); + var offs = line.ReplaceSpan(3); + Assert.IsTrue(text.Substring(offs.Start, offs.Length).SequenceEqual("#include ")); + + var text2 = " #include \r\n#include //Giga \n"; + var lines2 = Parser.ParseInclues(text2[3..], true); + Assert.IsTrue(lines2.Length == 2); + var line2 = lines2[1]; + Assert.IsTrue(line2.keep == false); + Assert.IsTrue(line2.FullFile.SequenceEqual("")); + var offs2 = line2.ReplaceSpan(3); + Assert.IsTrue(text2.Substring(offs2.Start, offs2.Length).SequenceEqual("#include //Giga \n")); + } + } +} diff --git a/Tests/Tests/LexTests.cs b/Tests/Tests/LexTests.cs new file mode 100644 index 0000000..8840061 --- /dev/null +++ b/Tests/Tests/LexTests.cs @@ -0,0 +1,72 @@ +using IncludeToolbox; +using static IncludeToolbox.Lexer; + +namespace Tests +{ + public class LexTests + { + [SetUp] + public void Setup() + { + + } + + [Test] + public void TestBasicToken() + { + Context lexer = new("hello\n"); + + var token = lexer.GetToken(true); + Assert.IsTrue(token.Value.SequenceEqual("hello")); + token = lexer.GetToken(true, true, true); + Assert.IsTrue(token.Type == TType.Newline && token.Value.SequenceEqual("\n")); + } + + [Test] + public void TestToken() + { + Context lexer = new("class B;\r\n // assault rifle"); + + var token = lexer.GetToken(true); + Assert.IsTrue(token.Type == TType.Class); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.ID && token.Value.SequenceEqual("B")); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Semicolon); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Newline && token.Value.SequenceEqual("\r\n")); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Commentary && token.Value.SequenceEqual("// assault rifle")); + } + + [Test] + public void TestCommentaries() + { + Context lexer = new("/*class \n\n B;\r\n*/\r\n// assault rifle\r\n"); + var token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.MLCommentary && token.Value.SequenceEqual("/*class \n\n B;\r\n*/")); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Newline && token.Value.SequenceEqual("\r\n")); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Commentary && token.Value.SequenceEqual("// assault rifle")); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Newline && token.Value.SequenceEqual("\r\n")); + } + + [Test] + public void TestNewlines() + { + Context lexer = new("\n \n \r\n \r \n"); + var token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Newline && token.Value.SequenceEqual("\n") && token.Position == 0); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Newline && token.Value.SequenceEqual("\n")); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Newline && token.Value.SequenceEqual("\r\n")); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Newline && token.Value.SequenceEqual("\r")); + token = lexer.GetToken(true, true, true, true); + Assert.IsTrue(token.Type == TType.Newline && token.Value.SequenceEqual("\n")); + } + } +} \ No newline at end of file diff --git a/Tests/Tests/Tests.csproj b/Tests/Tests/Tests.csproj new file mode 100644 index 0000000..3b3c7b6 --- /dev/null +++ b/Tests/Tests/Tests.csproj @@ -0,0 +1,24 @@ + + + + net6.0-windows8.0 + enable + enable + + false + + + + + + + + + + + + + + + + diff --git a/Tests/Tests/Usings.cs b/Tests/Tests/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/Tests/Tests/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/Tests/testdata/includegraph.dgml b/Tests/testdata/includegraph.dgml deleted file mode 100644 index c15641c..0000000 --- a/Tests/testdata/includegraph.dgml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tests/testdata/includegraph_grouped.dgml b/Tests/testdata/includegraph_grouped.dgml deleted file mode 100644 index 3c5c294..0000000 --- a/Tests/testdata/includegraph_grouped.dgml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tests/testdata/includegraph_withcolors.dgml b/Tests/testdata/includegraph_withcolors.dgml deleted file mode 100644 index b266934..0000000 --- a/Tests/testdata/includegraph_withcolors.dgml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tests/testdata/includegraph_withcolors_grouped.dgml b/Tests/testdata/includegraph_withcolors_grouped.dgml deleted file mode 100644 index 7f6f9a2..0000000 --- a/Tests/testdata/includegraph_withcolors_grouped.dgml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tests/testdata/simplegraph.dgml b/Tests/testdata/simplegraph.dgml deleted file mode 100644 index 086d2bf..0000000 --- a/Tests/testdata/simplegraph.dgml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Tests/testdata/source0.cpp b/Tests/testdata/source0.cpp deleted file mode 100644 index a5e6898..0000000 --- a/Tests/testdata/source0.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "subdir/testinclude.h" -/* -// commented includes shouldn't throw off the system of course -#include "testinclude.h" -*/ -#include // Same again! Weird, but happens. \ No newline at end of file diff --git a/Tests/testdata/source1.cpp b/Tests/testdata/source1.cpp deleted file mode 100644 index cf190fc..0000000 --- a/Tests/testdata/source1.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "testinclude.h" \ No newline at end of file diff --git a/Tests/testdata/subdir/inline.inl b/Tests/testdata/subdir/inline.inl deleted file mode 100644 index e869fc1..0000000 --- a/Tests/testdata/subdir/inline.inl +++ /dev/null @@ -1 +0,0 @@ -#include "broken!" \ No newline at end of file diff --git a/Tests/testdata/subdir/subdir/subsub.h b/Tests/testdata/subdir/subdir/subsub.h deleted file mode 100644 index a541129..0000000 --- a/Tests/testdata/subdir/subdir/subsub.h +++ /dev/null @@ -1 +0,0 @@ -#include "../inline.inl" \ No newline at end of file diff --git a/Tests/testdata/subdir/testinclude.h b/Tests/testdata/subdir/testinclude.h deleted file mode 100644 index a07ab36..0000000 --- a/Tests/testdata/subdir/testinclude.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "inline.inl" -#include "subdir/subsub.h" \ No newline at end of file diff --git a/Tests/testdata/testinclude.h b/Tests/testdata/testinclude.h deleted file mode 100644 index e54b610..0000000 --- a/Tests/testdata/testinclude.h +++ /dev/null @@ -1 +0,0 @@ -#include \ No newline at end of file diff --git a/art/AddPageGuides.png b/art/AddPageGuides.png new file mode 100644 index 0000000..5d04f59 Binary files /dev/null and b/art/AddPageGuides.png differ diff --git a/art/IncludeGraph.png b/art/IncludeGraph.png new file mode 100644 index 0000000..9515b75 Binary files /dev/null and b/art/IncludeGraph.png differ diff --git a/art/iformat.png b/art/iformat.png new file mode 100644 index 0000000..1620c38 Binary files /dev/null and b/art/iformat.png differ diff --git a/art/igraph.png b/art/igraph.png new file mode 100644 index 0000000..18c76d8 Binary files /dev/null and b/art/igraph.png differ diff --git a/art/includeformatter.gif b/art/includeformatter.gif new file mode 100644 index 0000000..c5002ff Binary files /dev/null and b/art/includeformatter.gif differ diff --git a/art/itrial.png b/art/itrial.png new file mode 100644 index 0000000..9cc4986 Binary files /dev/null and b/art/itrial.png differ diff --git a/art/iwyu.gif b/art/iwyu.gif new file mode 100644 index 0000000..35482d8 Binary files /dev/null and b/art/iwyu.gif differ diff --git a/art/iwyu.png b/art/iwyu.png new file mode 100644 index 0000000..33218b4 Binary files /dev/null and b/art/iwyu.png differ diff --git a/doc/changelog.md b/doc/changelog.md new file mode 100644 index 0000000..08d05f1 --- /dev/null +++ b/doc/changelog.md @@ -0,0 +1,141 @@ +# Version History +* 3.2.69 + * Added Compile Header button +* 3.2.66 + * Fixed an Unreal Engine Compatibility +* 3.2.62 + * Added automatic CI/CD with publishing to Marketplace. +* 3.2.59 + * Added a relative path merger for additional include directories. +* 3.2.58 + * Added an include graph. +* 3.2.47 + * Enabled Trial and Error Include Removal for header files. +* 3.2.43 + * Added more tests for parser + * Fixed Include formatter not deleting empty lines + * Fixed Empty namespace removal + * Added brains to Precise mode of IWYU, now it ignores #ifdef preprocessor blocks for insertion (no more insertion in #if block only because it is the last include) + * Fixed general newline parser bugs (when it failed to parse include only because there was no newline in the selection) + * Newline char is picked from the editor options rather than from string (O(n)->O(1)) +* 3.2.36 + * Added IWYU default MSVC mappings with selectable option +* 3.2.32 + * Refactored Trial And Error + * DTE support removed + * Cleaned up utils + * Added tests for Lex and Parser and test steps in building pipeline + * \*BREAKING CHANGES\* Unified pragmas with IWYU +* 3.1.22 + * New Include Format parsing, performed using project Lexer + * Small fixes and DTE reduction + * Unified formatting pragma for duplicate removal +* 3.0.0 + * Versions have new pattern (enforced by github pipelines) Major.Minor.Build, the build number does not decrease. + * New SDK and Tools. General renewal. Visual Studio 2022 support, dropped support for 2015 and 2017. + * Refactoring of IWYU, new code and new feature set. + * Some features are dropped for now, until fixed. + * Build pipeline for IWYU, which builds every month at [Build Pipeline](https://github.com/Agrael1/BuildIWYU)! + * CI/CD for this whole project! + * Added Include mapper[beta] for IWYU, works as public-public include mapping. + * Include What You Use additions: + * Added LL1 partial parser for includes and forward declarations. + * Added forward declaration moving to the beginning of the file, after all the includes. + * Empty namespace removal tool. +* 2.4.1 + * Fixed crash when opening context menu on some non-project files +* 2.4.0 + * Added support for Visual Studio 2019 + * Dropped support for Visual Studio 2015 + * Made some operations asynchronous under the hood, related bugfixing/checks driven by VS2019's static analysis warnings +* 2.3.0 + * Include Formatter contributions by _[Dakota Hawkins](https://github.com/dakotahawkins)_ + * has now a remove duplicates option which is enabled by default + * Fixed not adding newlines before the last line of a batch + * Fixed TrialAndErrorRemoval stopping when encountering an unsupported document, changed operation timeout to a couple of minutes ([PR by _bytefactory73_](https://github.com/Wumpf/IncludeToolbox/pull/58)) + * Fixed IWYU failing for long command line argument ([PR by _codingdave_](https://github.com/Wumpf/IncludeToolbox/pull/60)) + * Trying now to query NMake settings for include paths if there is no VCCLCompilerTool present (happens if vcxproj is not a standard C++ project) +* 2.2.0 + * IWYU Integration/Trial and Error Include Removal + * Introduced comment-tag to avoid removing include (thx to [_ergins23_ for suggesting](https://github.com/Wumpf/IncludeToolbox/issues/38)) + * IWYU Integration + * Passes now arch parameter for x64 projects on (thx to [_Fei_ for reporting](https://github.com/Wumpf/IncludeToolbox/issues/43)) + * Added option for custom parameters (thx to [_Fei_ for suggesting](https://github.com/Wumpf/IncludeToolbox/issues/44)) +* 2.1.5 + * [Fixed](https://github.com/Wumpf/IncludeToolbox/issues/41) random timeouts in Trial and Error Include Removal + * Updated internal library references & used VS Extension toolkit +* 2.1 + * DGML graph saving feature improvements + + * Each nodes has information about child count and unique transitive child counts + * Option to color elements by transitive child count + * Option to group by folders, expanded or collapsed + * Messageprompt after graph is saved, allows to open in VS directly + * Other fixes and small improvements + + * Renamed "Try and Error Include Removal" to "_Trial_ and Error Include Removal" (thx to [_steronydh_ for reporting](https://github.com/Wumpf/IncludeToolbox/issues/35)) + * Include sorting treats other preprocessor directives as barrier over which includes can't be moved (thx to [_etiennehebert_ for reporting](https://github.com/Wumpf/IncludeToolbox/issues/34)) + * Pressing enter on item in Include Graph jumps to include (previously only double click) + * Fixed Include Graph not displaying graph when switching active file while graph is computed +* 2.0.1 + * Fixed bug that BlankAfterRegexGroupMatch option would only work if RemoveEmptyLines was active as well. + * Fixed crash in formatter if delimiter mode not "Unchanged" + "Remove Empty Lines" was false. (thx to [_etiennehebert_ for reporting](https://github.com/Wumpf/IncludeToolbox/issues/33)) + * Include Graph folder items end now in slashes. +* 2.0 + * Rewrote Include Graph ("Include Viewer" previously) + * New, improved UI + * Allows to display includes grouped by folder + * Much faster graph bulid up using by direct parsing (as alternative to compile with /showIncludes) + * Double click can navigate to include site + * Graph can be saved as DGML file + * Trial-and-Error-Include-Removal "Ignore List" option does now support "$(currentFilename)" macro + * Default setting include "(\/|\\\\|^)$(currentFilename)\.(h|hpp|hxx|inl|c|cpp|cxx)$" to ignore corresponding header file in removal +* 1.8 + * Include-what-you-use (iwyu): + * Iwyu.exe is no longer part of the package. Instead there is a automatic download and update from a [different repository](https://github.com/Wumpf/iwyu_for_vs_includetoolbox) on first use. + * iwyu.exe path can be configured by user + * In case of automatic download, mapping files in iwyu path will be added to configuration + * Fixed hardcoded defines being passed to iwyu + * MSVC version is correctly passed to iwyu + * Fixed issues with applying removal/addition of declarations + * Changes can now optionally run through IncludeFormatter (on by default) + * Formatter: + * Include parser recognizes all whitespace-only lines as empty + * No longer resolves includes via file local path if "Ignore File Relative" option is active + * Formatting applied to includes inside preprocessor conditionals again. (Still ignored for include removal though) + * Fixed incorrect include parse behavior for preceding /* */ comment. + * Fixed potential crashes in internal path resolve + * Other: + * New Icons! + * Safer against crashes in commands + * Codebase has now a handful of unit tests +* 1.7 + * .inl and _inl.h are by default ignored for trial-and-error-include-removal (configurable) + * New option for trial-and-error-include-removal to keep line breaks (off by default) + * _Contributed_ by [Adam Skoglund](https://github.com/gulgi): Another fix for folder handling in trial-and-error-include-removal +* 1.6 _- _Contributed_ by [Adam Skoglund](https://github.com/gulgi)_ + + * Basic support for #if/#endif - any include within an #if/#endif block will be ignored. + * Better support for subdirectories in trial-and-error-include-removal on projects. +* 1.5 + * Fixed problems with VCProject runtimes in VS2015 introduced in previous version. + Required suprisingly large internal restructuring to support both VS2015 and VS2017 equally. +* 1.4 + * Support for VS2017 + * "Format Selected Includes" action is now only visible if includes were actually selected. + * "Format Selected Includes" works partially now also on files that are not in the currently loaded project + * Fixed an error in IWYU include removal parsing +* 1.3 - __Contributed_ by [Dakota Hawkins](https://github.com/dakotahawkins)_ + + * Added option to put spaces between precedence regex matches. + * Improved regex sorting via "Schwartzian transform" (= grouping by regex order number before sorting). +* 1.2 _- Contributed by [Dakota Hawkins](https://github.com/dakotahawkins)_ + * Added option to include delimiters in precedence regex to allow more advanced sorting (for a sample see [original pull request](https://github.com/Wumpf/IncludeToolbox/pull/4)). +* 1.1 + * Remove dependency to ezEngine. + * IncludeViewer visualizes now the output of the /showIncludes command instead of trying to run the preprocessor manually. +* 1.01 + * Have includes with quotes or angle brackets first +* 1.0 + * First release. + * Merged two old projects "Include Viewer" and "Include Formatter" to new "Include Toolbox" bundle diff --git a/vs-publish2019.json b/vs-publish2019.json new file mode 100644 index 0000000..5fa0cd5 --- /dev/null +++ b/vs-publish2019.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json.schemastore.org/vsix-publish", + "categories": [ "other", "coding" ], + "identity": { + "internalName": "IncludeToolbox", + "tags": [ "C", "C++", "include", "iwyu", "formatting", "code", "include what you use"] + }, + "assetFiles": [ + { + "pathOnDisk": "art/AddPageGuides.png", + "targetPath": "art/AddPageGuides.png" + }, + { + "pathOnDisk": "art/iformat.png", + "targetPath": "art/iformat.png" + }, + { + "pathOnDisk": "art/igraph.png", + "targetPath": "art/igraph.png" + }, + { + "pathOnDisk": "art/includeformatter.gif", + "targetPath": "art/includeformatter.gif" + }, + { + "pathOnDisk": "art/itrial.png", + "targetPath": "art/itrial.png" + }, + { + "pathOnDisk": "art/iwyu.gif", + "targetPath": "art/iwyu.gif" + }, + { + "pathOnDisk": "art/iwyu.png", + "targetPath": "art/iwyu.png" + }, + { + "pathOnDisk": "art/IncludeGraph.png", + "targetPath": "art/IncludeGraph.png" + } + ], + "overview": "README.md", + "publisher": "Wumpf", + "repo": "https://github.com/Agrael1/IncludeToolbox" +} diff --git a/vs-publish2022.json b/vs-publish2022.json new file mode 100644 index 0000000..5cf30cb --- /dev/null +++ b/vs-publish2022.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json.schemastore.org/vsix-publish", + "categories": [ "other", "coding" ], + "identity": { + "internalName": "IncludeToolbox2022", + "tags": [ "C", "C++", "include", "iwyu", "formatting", "code", "include what you use"] + }, + "assetFiles": [ + { + "pathOnDisk": "art/AddPageGuides.png", + "targetPath": "art/AddPageGuides.png" + }, + { + "pathOnDisk": "art/iformat.png", + "targetPath": "art/iformat.png" + }, + { + "pathOnDisk": "art/igraph.png", + "targetPath": "art/igraph.png" + }, + { + "pathOnDisk": "art/includeformatter.gif", + "targetPath": "art/includeformatter.gif" + }, + { + "pathOnDisk": "art/itrial.png", + "targetPath": "art/itrial.png" + }, + { + "pathOnDisk": "art/iwyu.gif", + "targetPath": "art/iwyu.gif" + }, + { + "pathOnDisk": "art/iwyu.png", + "targetPath": "art/iwyu.png" + }, + { + "pathOnDisk": "art/IncludeGraph.png", + "targetPath": "art/IncludeGraph.png" + } + ], + "overview": "README.md", + "publisher": "Agrael", + "repo": "https://github.com/Agrael1/IncludeToolbox" +}