|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PdbSourceDocument;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.PdbSourceDocument;
[Export(typeof(IPdbSourceDocumentLogger)), Shared]
internal sealed class PdbSourceDocumentOutputWindowLogger : IPdbSourceDocumentLogger, IDisposable
{
private static readonly Guid s_outputPaneGuid = new Guid("f543e896-2e9c-48b8-8fac-d1d5030b4b89");
private IVsOutputWindowPane? _outputPane;
private readonly IThreadingContext _threadingContext;
private readonly AsyncBatchingWorkQueue<string?> _logItemsQueue;
private readonly IServiceProvider _serviceProvider;
private readonly CancellationTokenSource _cancellationTokenSource = new();
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public PdbSourceDocumentOutputWindowLogger(SVsServiceProvider serviceProvider, IThreadingContext threadingContext, IAsynchronousOperationListenerProvider listenerProvider)
{
_serviceProvider = serviceProvider;
_threadingContext = threadingContext;
var asyncListener = listenerProvider.GetListener(nameof(PdbSourceDocumentOutputWindowLogger));
_logItemsQueue = new AsyncBatchingWorkQueue<string?>(
DelayTimeSpan.NearImmediate,
ProcessLogMessagesAsync,
asyncListener,
_cancellationTokenSource.Token);
}
private async ValueTask ProcessLogMessagesAsync(ImmutableSegmentedList<string?> messages, CancellationToken cancellationToken)
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
foreach (var message in messages)
{
var pane = GetPane();
if (pane == null)
{
return;
}
if (message is null)
{
pane.Clear();
}
else if (pane is IVsOutputWindowPaneNoPump noPumpPane)
{
noPumpPane.OutputStringNoPump(message + Environment.NewLine);
}
else
{
pane.OutputStringThreadSafe(message + Environment.NewLine);
}
}
}
public void Clear()
{
_logItemsQueue.AddWork((string?)null);
}
public void Log(string value)
{
_logItemsQueue.AddWork(value);
}
private IVsOutputWindowPane? GetPane()
{
_threadingContext.ThrowIfNotOnUIThread();
if (_outputPane == null)
{
var outputWindow = (IVsOutputWindow)_serviceProvider.GetService(typeof(SVsOutputWindow));
_outputPane = TryCreateOutputPane(outputWindow);
}
return _outputPane;
}
private IVsOutputWindowPane? TryCreateOutputPane(IVsOutputWindow outputWindow)
{
_threadingContext.ThrowIfNotOnUIThread();
var paneGuid = s_outputPaneGuid;
if (ErrorHandler.Succeeded(outputWindow.CreatePane(ref paneGuid, ServicesVSResources.Navigate_to_external_sources, fInitVisible: 1, fClearWithSolution: 1)) &&
ErrorHandler.Succeeded(outputWindow.GetPane(ref paneGuid, out var pane)))
{
return pane;
}
return null;
}
void IDisposable.Dispose()
{
_cancellationTokenSource.Cancel();
}
}
|