|
// 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.
#nullable disable
using System;
using System.Threading;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.CodeAnalysis.Internal.Log
{
/// <summary>
/// Implementation of <see cref="ILogger"/> that output to output window
/// </summary>
internal sealed class OutputWindowLogger : ILogger
{
private readonly Func<FunctionId, bool> _isEnabledPredicate;
public OutputWindowLogger(Func<FunctionId, bool> isEnabledPredicate)
{
_isEnabledPredicate = isEnabledPredicate;
}
public bool IsEnabled(FunctionId functionId)
=> _isEnabledPredicate(functionId);
public void Log(FunctionId functionId, LogMessage logMessage)
{
OutputPane.WriteLine(string.Format("[{0}] {1} - {2}", Environment.CurrentManagedThreadId, functionId.ToString(), logMessage.GetMessage()));
}
public void LogBlockStart(FunctionId functionId, LogMessage logMessage, int uniquePairId, CancellationToken cancellationToken)
{
OutputPane.WriteLine(string.Format("[{0}] Start({1}) : {2} - {3}", Environment.CurrentManagedThreadId, uniquePairId, functionId.ToString(), logMessage.GetMessage()));
}
public void LogBlockEnd(FunctionId functionId, LogMessage logMessage, int uniquePairId, int delta, CancellationToken cancellationToken)
{
var functionString = functionId.ToString() + (cancellationToken.IsCancellationRequested ? " Canceled" : string.Empty);
OutputPane.WriteLine(string.Format("[{0}] End({1}) : [{2}ms] {3}", Environment.CurrentManagedThreadId, uniquePairId, delta, functionString));
}
private class OutputPane
{
private static readonly Guid s_outputPaneGuid = new Guid("BBAFF416-4AF5-41F2-9F93-91F283E43C3B");
public static readonly OutputPane s_instance = new OutputPane();
private readonly IServiceProvider _serviceProvider;
private readonly IThreadingContext _threadingContext;
public static void WriteLine(string value)
{
s_instance.WriteLineInternal(value);
}
public OutputPane()
{
_serviceProvider = ServiceProvider.GlobalProvider;
var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel));
_threadingContext = componentModel.GetService<IThreadingContext>();
}
private IVsOutputWindowPane _doNotAccessDirectlyOutputPane;
private void WriteLineInternal(string value)
{
var pane = GetPane();
if (pane == null)
{
return;
}
pane.OutputStringThreadSafe(value + Environment.NewLine);
}
private IVsOutputWindowPane GetPane()
{
if (_doNotAccessDirectlyOutputPane == null)
{
_threadingContext.JoinableTaskFactory.Run(async () =>
{
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync();
if (_doNotAccessDirectlyOutputPane != null)
{
// check whether other one already initialized output window.
// the output API already handle double initialization, so this is just quick bail
// rather than any functional issue
return;
}
var outputWindow = (IVsOutputWindow)_serviceProvider.GetService(typeof(SVsOutputWindow));
// this should bring outout window to the front
_doNotAccessDirectlyOutputPane = CreateOutputPane(outputWindow);
});
}
return _doNotAccessDirectlyOutputPane;
}
private IVsOutputWindowPane CreateOutputPane(IVsOutputWindow outputWindow)
{
_threadingContext.ThrowIfNotOnUIThread();
// Try to get the workspace pane if it has already been registered
var workspacePaneGuid = s_outputPaneGuid;
// If the pane has already been created, CreatePane returns it
if (ErrorHandler.Succeeded(outputWindow.CreatePane(ref workspacePaneGuid, "Roslyn Logger Output", fInitVisible: 1, fClearWithSolution: 1)) &&
ErrorHandler.Succeeded(outputWindow.GetPane(ref workspacePaneGuid, out var pane)))
{
return pane;
}
return null;
}
}
}
}
|