diff options
author | Karsten Heimrich <karsten.heimrich@qt.io> | 2022-02-14 14:59:59 +0100 |
---|---|---|
committer | Karsten Heimrich <karsten.heimrich@qt.io> | 2022-02-17 15:26:41 +0000 |
commit | ab62be90eb00433e7d40f76873b2ccdca7fb0a1e (patch) | |
tree | c18de71bd5712319b7bc32c921d5d59acdcdd72d | |
parent | 108d008f6d3ec23ee063ac0210d337ec27809665 (diff) |
Refactor OutputWindowPane handling
Refactor access to the OutputWindowPane into a separate class
and enable async get/write of the output window pane.
Remove some of the previously introduced function calls to
ThreadHelper.ThrowIfNotOnUIThread(); as they are not necessary
in the VSIX loading code path.
Change-Id: I2c7f5f84ec556526fe78612d981f28210c545f45
Reviewed-by: Miguel Costa <miguel.costa@qt.io>
-rw-r--r-- | QtVsTools.Core/Messages.cs | 122 | ||||
-rw-r--r-- | QtVsTools.Core/OutputWindowPane.cs | 179 | ||||
-rw-r--r-- | QtVsTools.Core/QtVsTools.Core.csproj | 1 |
3 files changed, 216 insertions, 86 deletions
diff --git a/QtVsTools.Core/Messages.cs b/QtVsTools.Core/Messages.cs index aa39acfd..4ff66d8f 100644 --- a/QtVsTools.Core/Messages.cs +++ b/QtVsTools.Core/Messages.cs @@ -27,41 +27,25 @@ ****************************************************************************/ using EnvDTE; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using QtVsTools.VisualStudio; +using System; using System.Collections.Concurrent; -using System.Diagnostics; -using System.Linq; using System.Threading; -using System.Windows.Forms; using System.Threading.Tasks; -using Microsoft.VisualStudio.Threading; -using QtVsTools.VisualStudio; -using Microsoft.VisualStudio.Shell; - -using Thread = System.Threading.Thread; -using Task = System.Threading.Tasks.Task; +using System.Windows.Forms; namespace QtVsTools.Core { + using Task = System.Threading.Tasks.Task; + public static class Messages { - private static OutputWindow Window { get; set; } private static OutputWindowPane Pane { get; set; } - private static OutputWindowPane _BuildPane; - private static OutputWindowPane BuildPane - { - get - { - ThreadHelper.ThrowIfNotOnUIThread(); - return _BuildPane ?? (_BuildPane = Window.OutputWindowPanes.Cast<OutputWindowPane>() - .Where(pane => - { - ThreadHelper.ThrowIfNotOnUIThread(); - return pane.Guid == "{1BD8A850-02D1-11D1-BEE7-00A0C913D1F8}"; - }) - .FirstOrDefault()); - } - } + private static readonly string PaneName = "Qt VS Tools"; + private static readonly Guid PaneGuid = new Guid("8f6a1e44-fa0b-49e5-9934-1c050555350e"); /// <summary> /// Show a message on the output pane. @@ -74,21 +58,9 @@ namespace QtVsTools.Core Text = text, Activate = activate }); - - ThreadHelper.ThrowIfNotOnUIThread(); FlushMessages(); } - static void OutputWindowPane_Print(string text) - { - ThreadHelper.ThrowIfNotOnUIThread(); - OutputWindowPane_Init(); - Pane.OutputString(text + "\r\n"); - // show buildPane if a build is in progress - if (Dte.Solution.SolutionBuild.BuildState == vsBuildState.vsBuildStateInProgress) - BuildPane?.Activate(); - } - /// <summary> /// Activates the message pane of the Qt VS Tools extension. /// </summary> @@ -98,18 +70,13 @@ namespace QtVsTools.Core { Activate = true }); - - ThreadHelper.ThrowIfNotOnUIThread(); FlushMessages(); } - static void OutputWindowPane_Activate() + static async Task OutputWindowPane_ActivateAsync() { - OutputWindowPane_Init(); - - ThreadHelper.ThrowIfNotOnUIThread(); - - Pane?.Activate(); + await OutputWindowPane_InitAsync(); + await Pane?.ActivateAsync(); } private static string ExceptionToString(System.Exception e) @@ -171,19 +138,13 @@ namespace QtVsTools.Core { Clear = true }); - - ThreadHelper.ThrowIfNotOnUIThread(); - FlushMessages(); } - static void OutputWindowPane_Clear() + static async Task OutputWindowPane_ClearAsync() { - OutputWindowPane_Init(); - - ThreadHelper.ThrowIfNotOnUIThread(); - - Pane?.Clear(); + await OutputWindowPane_InitAsync(); + await Pane?.ClearAsync(); } class Msg @@ -193,36 +154,20 @@ namespace QtVsTools.Core public bool Activate { get; set; } = false; } - static bool shuttingDown = false; static readonly ConcurrentQueue<Msg> msgQueue = new ConcurrentQueue<Msg>(); - static DTE Dte { get; set; } = null; - - private static void OnBeginShutdown() - { - shuttingDown = true; - } - private static void OutputWindowPane_Init() + private static async Task OutputWindowPane_InitAsync() { - if (Dte == null) - Dte = VsServiceProvider.GetService<DTE>(); - var t = Stopwatch.StartNew(); - - ThreadHelper.ThrowIfNotOnUIThread(); - - while (Pane == null && t.ElapsedMilliseconds < 5000) { - try { - Window = Dte.Windows.Item(Constants.vsWindowKindOutput).Object as OutputWindow; - Pane = Window?.OutputWindowPanes.Add(SR.GetString("Resources_QtVsTools")); - } catch { - } + try { if (Pane == null) - Thread.Yield(); + Pane = await OutputWindowPane.CreateAsync(PaneName, PaneGuid); + } catch (Exception ex) { + System.Diagnostics.Debug.WriteLine(ex); } - Dte.Events.DTEEvents.OnBeginShutdown += OnBeginShutdown; } public static JoinableTaskFactory JoinableTaskFactory { get; set; } + static readonly object staticCriticalSection = new object(); static Task FlushTask { get; set; } static EventWaitHandle MessageReady { get; set; } @@ -234,7 +179,8 @@ namespace QtVsTools.Core MessageReady = new EventWaitHandle(false, EventResetMode.AutoReset); FlushTask = Task.Run(async () => { - while (!shuttingDown) { + var package = VsServiceProvider.Instance as Package; + while (!package.Zombied) { if (!await MessageReady.ToTask(3000)) continue; while (!msgQueue.IsEmpty) { @@ -242,18 +188,12 @@ namespace QtVsTools.Core await Task.Yield(); continue; } - //////////////////////////////////////////////////////////////////// - // Switch to main (UI) thread - await JoinableTaskFactory.SwitchToMainThreadAsync(); if (msg.Clear) - OutputWindowPane_Clear(); + await OutputWindowPane_ClearAsync(); if (msg.Text != null) - OutputWindowPane_Print(msg.Text); + await OutputWindowPane_PrintAsync(msg.Text); if (msg.Activate) - OutputWindowPane_Activate(); - //////////////////////////////////////////////////////////////////// - // Switch to background thread - await TaskScheduler.Default; + await OutputWindowPane_ActivateAsync(); } } }); @@ -261,5 +201,15 @@ namespace QtVsTools.Core } MessageReady.Set(); } + + static async Task OutputWindowPane_PrintAsync(string text) + { + var active = await OutputWindowPane.GetActiveAsync(); + + await OutputWindowPane_InitAsync(); + await Pane.PrintAsync(text); + + (active?.ActivateAsync()).Forget(); + } } } diff --git a/QtVsTools.Core/OutputWindowPane.cs b/QtVsTools.Core/OutputWindowPane.cs new file mode 100644 index 00000000..1cdc5c2a --- /dev/null +++ b/QtVsTools.Core/OutputWindowPane.cs @@ -0,0 +1,179 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt VS Tools. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +using System; +using System.Threading.Tasks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using QtVsTools.VisualStudio; + +namespace QtVsTools.Core +{ + using Task = System.Threading.Tasks.Task; + + public class OutputWindowPane + { + public enum VSOutputWindowPane + { + General, + Build, + Debug, + } + + public static Task<OutputWindowPane> GetVSOutputWindowPaneAsync(VSOutputWindowPane pane) + { + switch (pane) { + case VSOutputWindowPane.General: + return GetAsync(VSConstants.OutputWindowPaneGuid.GeneralPane_guid); + case VSOutputWindowPane.Build: + return GetAsync(VSConstants.OutputWindowPaneGuid.BuildOutputPane_guid); + case VSOutputWindowPane.Debug: + return GetAsync(VSConstants.OutputWindowPaneGuid.DebugPane_guid); + default: + throw new InvalidOperationException("Unsupported Visual Studio output pane"); + }; + } + + public static async Task<OutputWindowPane> GetAsync(Guid guid) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + try { + IVsOutputWindow w = null; + if (guid == VSConstants.OutputWindowPaneGuid.GeneralPane_guid) { + w = await VsServiceProvider.GetServiceAsync<SVsGeneralOutputWindowPane, + IVsOutputWindow>(); + } else { + w = await VsServiceProvider.GetServiceAsync<SVsOutputWindow, IVsOutputWindow>(); + } + ErrorHandler.ThrowOnFailure(w.GetPane(guid, out IVsOutputWindowPane pane)); + + return new OutputWindowPane(guid, pane); + } catch (Exception ex) { + System.Diagnostics.Debug.WriteLine(ex); + return null; + } + } + + public static async Task<OutputWindowPane> CreateAsync(string name, Guid guid) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException($"{ nameof(name) } cannot be null"); + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + try { + var w = await VsServiceProvider.GetServiceAsync<SVsOutputWindow, IVsOutputWindow>(); + + const int visible = 1, clear = 1; + ErrorHandler.ThrowOnFailure(w.CreatePane(guid, name, visible, clear)); + ErrorHandler.ThrowOnFailure(w.GetPane(guid, out IVsOutputWindowPane pane)); + + return new OutputWindowPane(guid, pane); + } catch (Exception ex) { + System.Diagnostics.Debug.WriteLine(ex); + return null; + } + } + + public static async Task<OutputWindowPane> GetActiveAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + try { + var w2 = await VsServiceProvider + .GetServiceAsync<SVsOutputWindow, IVsOutputWindow>() as IVsOutputWindow2; + ErrorHandler.ThrowOnFailure(w2.GetActivePaneGUID(out Guid guid)); + + IVsOutputWindow w = w2 as IVsOutputWindow; + ErrorHandler.ThrowOnFailure(w.GetPane(guid, out IVsOutputWindowPane pane)); + + return new OutputWindowPane(guid, pane); + } catch (Exception ex) { + System.Diagnostics.Debug.WriteLine(ex); + return null; + } + } + + private Guid Guid { get; } + private IVsOutputWindowPane Pane { get; set; } = null; + + private OutputWindowPane(Guid guid, IVsOutputWindowPane pane) + { + Guid = guid; + Pane = pane; + } + + public async Task ActivateAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (Pane == null) + throw new InvalidOperationException($"{ nameof(Pane) } cannot be null"); + Pane.Activate(); + } + + public async Task HideAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (Pane == null) + throw new InvalidOperationException($"{ nameof(Pane) } cannot be null"); + Pane.Hide(); + } + + public async Task ClearAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (Pane == null) + throw new InvalidOperationException($"{ nameof(Pane) } cannot be null"); + Pane.Clear(); + } + + public void Print() + { + ThreadHelper.JoinableTaskFactory.Run(async () => { await PrintAsync(""); }); + } + + public void Print(string value) + { + ThreadHelper.JoinableTaskFactory.Run(async () => { await PrintAsync(value); }); + } + + public Task PrintAsync() + { + return PrintAsync(""); + } + + public async Task PrintAsync(string value) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (Pane is IVsOutputWindowPaneNoPump noPumpPane) + noPumpPane.OutputStringNoPump(value + Environment.NewLine); + else + ErrorHandler.ThrowOnFailure(Pane.OutputStringThreadSafe(value + Environment.NewLine)); + } + } +} diff --git a/QtVsTools.Core/QtVsTools.Core.csproj b/QtVsTools.Core/QtVsTools.Core.csproj index 9be3a2b5..9ac5f6fa 100644 --- a/QtVsTools.Core/QtVsTools.Core.csproj +++ b/QtVsTools.Core/QtVsTools.Core.csproj @@ -143,6 +143,7 @@ <Compile Include="MocCmdChecker.cs" /> <Compile Include="MsBuildProject.cs" /> <Compile Include="Observable.cs" /> + <Compile Include="OutputWindowPane.cs" /> <Compile Include="ProFileContent.cs" /> <Compile Include="ProFileOption.cs" /> <Compile Include="ProjectExporter.cs" /> |