diff --git a/Vsix/CodeConversion.cs b/Vsix/CodeConversion.cs index 20f6d78a8..fe92e646d 100644 --- a/Vsix/CodeConversion.cs +++ b/Vsix/CodeConversion.cs @@ -51,9 +51,6 @@ public CodeConversion(IAsyncServiceProvider serviceProvider, public async Task PerformProjectConversionAsync(IReadOnlyCollection selectedProjects) where TLanguageConversion : ILanguageConversion, new() { - await _outputWindow.ClearAsync(); - await _outputWindow.ForceShowOutputPaneAsync(); - await _joinableTaskFactory.RunAsync(async () => { var convertedFiles = ConvertProjectUnhandledAsync(selectedProjects); await WriteConvertedFilesAndShowSummaryAsync(await convertedFiles); @@ -62,9 +59,6 @@ await _joinableTaskFactory.RunAsync(async () => { public async Task PerformDocumentConversionAsync(string documentFilePath, Span selected) where TLanguageConversion : ILanguageConversion, new() { - await _outputWindow.ClearAsync(); - await _outputWindow.ForceShowOutputPaneAsync(); - var conversionResult = await _joinableTaskFactory.RunAsync(async () => { var result = await ConvertDocumentUnhandledAsync(documentFilePath, selected); await WriteConvertedFilesAndShowSummaryAsync(new[] { result }); @@ -81,9 +75,7 @@ await _joinableTaskFactory.RunAsync(async () => { private async Task WriteConvertedFilesAndShowSummaryAsync(IEnumerable convertedFiles) { - await _outputWindow.ClearAsync(); - await _outputWindow.WriteToOutputWindowAsync(Intro); - await _outputWindow.ForceShowOutputPaneAsync(); + await _outputWindow.WriteToOutputWindowAsync(Intro, true, true); var files = new List(); var filesToOverwrite = new List(); @@ -141,8 +133,7 @@ private async Task FinalizeConversionAsync(List files, List erro } var conversionSummary = await GetConversionSummaryAsync(files, errors); - await _outputWindow.WriteToOutputWindowAsync(conversionSummary); - await _outputWindow.ForceShowOutputPaneAsync(); + await _outputWindow.WriteToOutputWindowAsync(conversionSummary, false, true); } @@ -221,7 +212,9 @@ private async Task GetConversionSummaryAsync(IReadOnlyCollection } private async Task ConvertDocumentUnhandledAsync(string documentPath, Span selected) where TLanguageConversion : ILanguageConversion, new() - { + { + await _outputWindow.WriteToOutputWindowAsync($"Converting {documentPath}...", true, true); + //TODO Figure out when there are multiple document ids for a single file path var documentId = _visualStudioWorkspace.CurrentSolution.GetDocumentIdsWithFilePath(documentPath).SingleOrDefault(); if (documentId == null) { @@ -251,12 +244,18 @@ private async Task> ConvertProjectUnhandledAsync p.FilePath, p => p); #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread - ToList ensures this happens within the same thread just switched to above var projects = selectedProjects.Select(p => projectsByPath[p.FullName].First()).ToList(); #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread await TaskScheduler.Default; + var solutionConverter = SolutionConverter.CreateFor(projects, new Progress(s => { _outputWindow.WriteToOutputWindowAsync(FormatForOutputWindow(s)).ForgetNoThrow(); diff --git a/Vsix/ConvertCSToVBCommand.cs b/Vsix/ConvertCSToVBCommand.cs index a8f9acb2a..5ff4cf2a7 100644 --- a/Vsix/ConvertCSToVBCommand.cs +++ b/Vsix/ConvertCSToVBCommand.cs @@ -85,7 +85,7 @@ public static void Initialize(REConverterPackage package, OleMenuCommandService // Command in main menu var menuCommandId = new CommandID(CommandSet, MainMenuCommandId); var menuItem = package.CreateCommand(CodeEditorMenuItemCallbackAsync, menuCommandId); - menuItem.BeforeQueryStatus += CodeEditorMenuItem_BeforeQueryStatusAsync; + menuItem.BeforeQueryStatus += MainEditMenuItem_BeforeQueryStatusAsync; commandService.AddCommand(menuItem); // Command in code editor's context menu @@ -114,11 +114,19 @@ public static void Initialize(REConverterPackage package, OleMenuCommandService } } + private async Task MainEditMenuItem_BeforeQueryStatusAsync(object sender, EventArgs e) + { + if (sender is OleMenuCommand menuItem) { + var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsCSFileName, false); + menuItem.Visible = selectionInCurrentViewAsync != null; + } + } + private async Task CodeEditorMenuItem_BeforeQueryStatusAsync(object sender, EventArgs e) { if (sender is OleMenuCommand menuItem) { - var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsCSFileName); - menuItem.Visible = selectionInCurrentViewAsync?.IsEmpty == false; + var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsCSFileName, true); + menuItem.Visible = selectionInCurrentViewAsync != null; } } @@ -147,7 +155,7 @@ private async Task SolutionOrProjectMenuItem_BeforeQueryStatusAsync(object sende private async Task CodeEditorMenuItemCallbackAsync(object sender, EventArgs e) { - var (filePath, selection) = await VisualStudioInteraction.GetCurrentFilenameAndSelectionAsync(ServiceProvider, CodeConversion.IsCSFileName); + var (filePath, selection) = await VisualStudioInteraction.GetCurrentFilenameAndSelectionAsync(ServiceProvider, CodeConversion.IsCSFileName, false); if (filePath != null && selection != null) { await ConvertDocumentAsync(filePath, selection.Value); } diff --git a/Vsix/ConvertVBToCSCommand.cs b/Vsix/ConvertVBToCSCommand.cs index f1ce55a5f..af20cbbd4 100644 --- a/Vsix/ConvertVBToCSCommand.cs +++ b/Vsix/ConvertVBToCSCommand.cs @@ -84,7 +84,7 @@ public static void Initialize(REConverterPackage package, OleMenuCommandService // Command in main menu var menuCommandId = new CommandID(CommandSet, MainMenuCommandId); var menuItem = package.CreateCommand(CodeEditorMenuItemCallbackAsync, menuCommandId); - menuItem.BeforeQueryStatus += CodeEditorMenuItem_BeforeQueryStatusAsync; + menuItem.BeforeQueryStatus += MainEditMenuItem_BeforeQueryStatusAsync; commandService.AddCommand(menuItem); // Command in code editor's context menu @@ -113,11 +113,19 @@ public static void Initialize(REConverterPackage package, OleMenuCommandService } } + private async Task MainEditMenuItem_BeforeQueryStatusAsync(object sender, EventArgs e) + { + if (sender is OleMenuCommand menuItem) { + var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsVBFileName, true); + menuItem.Visible = selectionInCurrentViewAsync != null; + } + } + private async Task CodeEditorMenuItem_BeforeQueryStatusAsync(object sender, EventArgs e) { if (sender is OleMenuCommand menuItem) { - var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsVBFileName); - menuItem.Visible = selectionInCurrentViewAsync?.IsEmpty == false; + var selectionInCurrentViewAsync = await VisualStudioInteraction.GetFirstSelectedSpanInCurrentViewAsync(ServiceProvider, CodeConversion.IsVBFileName, false); + menuItem.Visible = selectionInCurrentViewAsync != null; } } @@ -146,7 +154,7 @@ private async Task SolutionOrProjectMenuItem_BeforeQueryStatusAsync(object sende private async Task CodeEditorMenuItemCallbackAsync(object sender, EventArgs e) { - var (filePath, selection) = await VisualStudioInteraction.GetCurrentFilenameAndSelectionAsync(ServiceProvider, CodeConversion.IsVBFileName); + var (filePath, selection) = await VisualStudioInteraction.GetCurrentFilenameAndSelectionAsync(ServiceProvider, CodeConversion.IsVBFileName, false); if (filePath != null && selection != null) { await ConvertDocumentAsync(filePath, selection.Value); } diff --git a/Vsix/OutputWindow.cs b/Vsix/OutputWindow.cs index 40fe38fb5..c2c1aa0af 100644 --- a/Vsix/OutputWindow.cs +++ b/Vsix/OutputWindow.cs @@ -64,21 +64,27 @@ public async Task ClearAsync() public async Task ForceShowOutputPaneAsync() { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - VisualStudioInteraction.Dte.Windows.Item(Constants.vsWindowKindOutput).Visible = true; - _outputPane.Activate(); + ForceShowInner(); await TaskScheduler.Default; } - public async Task WriteToOutputWindowAsync(string message) + public async Task WriteToOutputWindowAsync(string message, bool clearFirst = false, bool forceShow = false) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - lock (_outputPane) { - _hasOutputSinceSolutionOpened = true;; - _outputPane.OutputStringThreadSafe(message); - } + _hasOutputSinceSolutionOpened = true; + if (clearFirst) _outputPane.Clear(); + _outputPane.OutputStringThreadSafe(message); + if (forceShow) ForceShowInner(); await TaskScheduler.Default; } + private void ForceShowInner() + { + ThreadHelper.ThrowIfNotOnUIThread(); + VisualStudioInteraction.Dte.Windows.Item(Constants.vsWindowKindOutput).Visible = true; + _outputPane.Activate(); + } + #pragma warning disable VSTHRD100 // Avoid async void methods - fire and forget event handler private async void OnSolutionOpened() #pragma warning restore VSTHRD100 // Avoid async void methods diff --git a/Vsix/REConverterPackage.cs b/Vsix/REConverterPackage.cs index 0ad437b7c..c3168e0de 100644 --- a/Vsix/REConverterPackage.cs +++ b/Vsix/REConverterPackage.cs @@ -20,21 +20,15 @@ namespace CodeConverter.VsExtension { /// - /// This is the class that implements the package exposed by this assembly. + /// Implements the VS package exposed by this assembly. + /// + /// This package will load when: + /// * Visual Studio has been configured not to support UIContextRules and has a solution with a csproj or vbproj + /// * Someone clicks one of the menu items + /// * Someone opens the options page (it doesn't need to load in this case, but it seems to anyway) /// /// - /// - /// The minimum requirement for a class to be considered a valid package for Visual Studio - /// is to implement the IVsPackage interface and register itself with the shell. - /// This package uses the helper classes defined inside the Managed Package Framework (MPF) - /// to do it: it derives from the Package class that provides the implementation of the - /// IVsPackage interface and uses the registration attributes defined in the framework to - /// register itself and its components with the shell. These attributes tell the pkgdef creation - /// utility what data to put into .pkgdef file. - /// - /// - /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. - /// + /// Until the package is loaded, converting a multiple selection of projects won't work because there's no way to set a ProvideUIContextRule that covers that case /// [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [InstalledProductRegistration("#110", "#112", "1.0")] // Info on this package for Help/About diff --git a/Vsix/VisualStudioInteraction.cs b/Vsix/VisualStudioInteraction.cs index 688b1c851..bfaa6955f 100644 --- a/Vsix/VisualStudioInteraction.cs +++ b/Vsix/VisualStudioInteraction.cs @@ -124,21 +124,22 @@ public static async Task WriteStatusBarTextAsync(IAsyncServiceProvider servicePr } public static async Task GetFirstSelectedSpanInCurrentViewAsync(IAsyncServiceProvider serviceProvider, - Func predicate) + Func predicate, bool mustHaveFocus) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - var span = await FirstSelectedSpanInCurrentViewPrivateAsync(serviceProvider, predicate); + var span = await FirstSelectedSpanInCurrentViewPrivateAsync(serviceProvider, predicate, mustHaveFocus); await TaskScheduler.Default; return span; } - public static async Task<(string FilePath, Span? Selection)> GetCurrentFilenameAndSelectionAsync(IAsyncServiceProvider asyncServiceProvider, Func predicate) + public static async Task<(string FilePath, Span? Selection)> GetCurrentFilenameAndSelectionAsync( + IAsyncServiceProvider asyncServiceProvider, Func predicate, bool mustHaveFocus) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - var span = await GetFirstSelectedSpanInCurrentViewAsync(asyncServiceProvider, predicate); ; + var span = await GetFirstSelectedSpanInCurrentViewAsync(asyncServiceProvider, predicate, mustHaveFocus); ; var currentViewHostAsync = - await GetCurrentViewHostAsync(asyncServiceProvider, predicate); + await GetCurrentViewHostAsync(asyncServiceProvider, predicate, mustHaveFocus); var textDocumentAsync = await currentViewHostAsync.GetTextDocumentAsync(); var result = (textDocumentAsync?.FilePath, span); @@ -206,16 +207,15 @@ private static IEnumerable ObjectOfType(IReadOnlyCollection GetCurrentViewHostAsync(IAsyncServiceProvider serviceProvider) + private static async Task GetCurrentViewHostAsync(IAsyncServiceProvider serviceProvider, bool mustHaveFocus) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var txtMgr = await serviceProvider.GetServiceAsync(); - int mustHaveFocus = 1; if (txtMgr == null) { return null; } - txtMgr.GetActiveView(mustHaveFocus, null, out IVsTextView vTextView); + txtMgr.GetActiveView(mustHaveFocus ? 1 : 0, null, out IVsTextView vTextView); if (!(vTextView is IVsUserData userData)) { return null; } @@ -226,17 +226,18 @@ private static async Task GetCurrentViewHostAsync(IAsyncServic return holder as IWpfTextViewHost; } - private static async Task FirstSelectedSpanInCurrentViewPrivateAsync(IAsyncServiceProvider serviceProvider, - Func predicate) + private static async Task FirstSelectedSpanInCurrentViewPrivateAsync( + IAsyncServiceProvider serviceProvider, + Func predicate, bool mustHaveFocus) { - var selection = await GetSelectionInCurrentViewAsync(serviceProvider, predicate); + var selection = await GetSelectionInCurrentViewAsync(serviceProvider, predicate, mustHaveFocus); return selection?.SelectedSpans.First().Span; } private static async Task GetSelectionInCurrentViewAsync(IAsyncServiceProvider serviceProvider, - Func predicate) + Func predicate, bool mustHaveFocus) { - IWpfTextViewHost viewHost = await GetCurrentViewHostAsync(serviceProvider, predicate); + IWpfTextViewHost viewHost = await GetCurrentViewHostAsync(serviceProvider, predicate, mustHaveFocus); if (viewHost == null) return null; @@ -244,9 +245,9 @@ private static async Task GetSelectionInCurrentViewAsync(IAsyncS } private static async Task GetCurrentViewHostAsync(IAsyncServiceProvider serviceProvider, - Func predicate) + Func predicate, bool mustHaveFocus) { - IWpfTextViewHost viewHost = await GetCurrentViewHostAsync(serviceProvider); + IWpfTextViewHost viewHost = await GetCurrentViewHostAsync(serviceProvider, mustHaveFocus); if (viewHost == null) return null;