// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 using System; using System.Collections.Concurrent; using System.Text; using System.Threading; using System.Windows.Forms; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Threading; using static System.Environment; using Task = System.Threading.Tasks.Task; namespace QtVsTools.Core { using Options; using VisualStudio; public static class Messages { public static bool Initialized { get; set; } = false; private static OutputWindowPane Pane { get; set; } private const string Name = "Qt VS Tools"; private static readonly Guid PaneGuid = new("8f6a1e44-fa0b-49e5-9934-1c050555350e"); /// /// Show a message on the output pane. /// public static void Print(string text, bool clear = false, bool activate = false, bool trim = true) { msgQueue.Enqueue(new Msg { Clear = clear, Text = trim ? text.Trim(' ', '\t', '\r', '\n') : text, Activate = activate }); FlushMessages(); } public static void Log(this Exception exception, bool clear = false, bool activate = false) { msgQueue.Enqueue(new Msg { Clear = clear, Text = ExceptionToString(exception), Activate = activate }); FlushMessages(); } /// /// Activates the message pane of the Qt VS Tools extension. /// public static void ActivateMessagePane() { msgQueue.Enqueue(new Msg { Activate = true }); FlushMessages(); } static async Task OutputWindowPane_ActivateAsync() { await OutputWindowPane_InitAsync(); await Pane?.ActivateAsync(); } private static string ExceptionToString(Exception exception) { return $"An exception ({exception.GetType().Name}) occurred.\r\n" + $"Message:\r\n {exception.Message}\r\n" + $"Stack Trace:\r\n {exception.StackTrace.Trim()}\r\n"; } private const string ErrorString = "The following error occurred:"; private static readonly string WarningString = "Warning:" + NewLine; public static void DisplayCriticalErrorMessage(string msg) { MessageBox.Show(ErrorString + NewLine + msg, Name, MessageBoxButtons.OK, MessageBoxIcon.Error); } public static void DisplayErrorMessage(Exception e) { MessageBox.Show(ExceptionToString(e), Name, MessageBoxButtons.OK, MessageBoxIcon.Error); } public static void DisplayErrorMessage(string msg) { MessageBox.Show(ErrorString + NewLine + msg, Name, MessageBoxButtons.OK, MessageBoxIcon.Error); } public static void DisplayWarningMessage(Exception e, string solution) { MessageBox.Show(WarningString + ExceptionToString(e) + NewLine + NewLine + "To solve this problem:" + NewLine + solution, Name, MessageBoxButtons.OK, MessageBoxIcon.Warning); } public static void DisplayWarningMessage(string msg) { MessageBox.Show(WarningString + msg, Name, MessageBoxButtons.OK, MessageBoxIcon.Warning); } public static void ClearPane() { msgQueue.Enqueue(new Msg { Clear = true }); FlushMessages(); } static async Task OutputWindowPane_ClearAsync() { await OutputWindowPane_InitAsync(); await Pane?.ClearAsync(); } class Msg { public bool Clear { get; set; } public string Text { get; set; } public bool Activate { get; set; } } static readonly ConcurrentQueue msgQueue = new(); private static async Task OutputWindowPane_InitAsync() { try { Pane ??= await OutputWindowPane.CreateAsync(Name, PaneGuid); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } } public static JoinableTaskFactory JoinableTaskFactory { get; set; } static readonly object staticCriticalSection = new(); static Task FlushTask { get; set; } static EventWaitHandle MessageReady { get; set; } static void FlushMessages() { lock (staticCriticalSection) { if (FlushTask == null) { MessageReady = new EventWaitHandle(false, EventResetMode.AutoReset); FlushTask = Task.Run(async () => { while (VsServiceProvider.Instance == null) await Task.Delay(1000, VsShellUtilities.ShutdownToken); while (!VsShellUtilities.ShutdownToken.IsCancellationRequested) { await Task.Delay(Initialized ? 100 : 1000); if (!await MessageReady.ToTask(3000)) continue; bool clear = false; bool activate = false; var msgText = new StringBuilder(); while (!msgQueue.IsEmpty) { if (!msgQueue.TryDequeue(out var msg)) { await Task.Yield(); continue; } if (msg is null) continue; clear |= msg.Clear; activate |= msg.Activate; if (!string.IsNullOrEmpty(msg.Text)) msgText.AppendLine(msg.Text); } if (clear) await OutputWindowPane_ClearAsync(); if (msgText.Length > 0) await OutputWindowPane_PrintAsync(msgText.ToString()); if (activate && QtOptionsPage.AutoActivatePane) await OutputWindowPane_ActivateAsync(); } }); } } MessageReady.Set(); } static async Task OutputWindowPane_PrintAsync(string text) { await OutputWindowPane_InitAsync(); await Pane.PrintAsync(text); } } }