As some people have noticed, the Visual Studio 2010 SDK doesn’t have great support for the Output Window from MEF extensions. This post aims to change that. Like several other components I’m writing about, I hope to provide this one in a “general extensibility helper” that could be installed separately from extensions that use it so its features can be shared among them.
There are a couple goals here:
Provide a service for accessing the output window panes
Provide a simple method of creating new output window panes
I’ll start with the usage (since that’s the interface people will normally see) and follow it with the implementation.
Defining an output window pane
Now this is easy.
namespace JavaLanguageService
{
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Utilities;
using JavaLanguageService.Panes;
public static class Services
{
[Export]
[Name("ANTLR IntelliSense Engine")]
internal static OutputWindowDefinition AntlrIntellisenseOutputWindowDefinition;
}
}
Using an output window
First you import the IOutputWindowService
:
[Import]
internal IOutputWindowService OutputWindowService;
Then you use it to get an output window, which you can write to:
var outputWindow = OutputWindowService.TryGetPane("ANTLR IntelliSense Engine");
if (outputWindow != null)
outputWindow.WriteLine(message);
If you want to write to one of the standard panes, pass one of the following to TryGetPane
:
public static class PredefinedOutputWindowPanes
{
public static readonly string General;
public static readonly string Debug;
public static readonly string Build;
}
The IOutputWindowPane
and IOutputWindowService
interfaces
namespace JavaLanguageService.Panes
{
using System;
public interface IOutputWindowPane : IDisposable
{
string Name
{
get;
set;
}
void Activate();
void Hide();
void Write(string text);
void WriteLine(string text);
}
}
namespace JavaLanguageService.Panes
{
public interface IOutputWindowService
{
IOutputWindowPane TryGetPane(string name);
}
}
namespace JavaLanguageService.Panes
{
public sealed class OutputWindowDefinition
{
}
}
namespace JavaLanguageService.Panes
{
public static class PredefinedOutputWindowPanes
{
public static readonly string General = "General";
public static readonly string Debug = "Debug";
public static readonly string Build = "Build";
}
}
The internal implementation
namespace JavaLanguageService.Panes
{
internal interface IOutputWindowDefinitionMetadata
{
string Name
{
get;
}
}
}
namespace JavaLanguageService.Panes
{
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using JavaLanguageService.Extensions;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
[Export(typeof(IOutputWindowService))]
internal sealed class OutputWindowService : IOutputWindowService
{
[Import]
public IServiceProvider GlobalServiceProvider;
[ImportMany]
internal List<Lazy<OutputWindowDefinition, IOutputWindowDefinitionMetadata>> OutputWindowDefinitions;
private readonly Dictionary<string, Guid> _outputWindows =
new Dictionary<string, Guid>()
{
{ PredefinedOutputWindowPanes.Build, VSConstants.GUID_BuildOutputWindowPane },
{ PredefinedOutputWindowPanes.Debug, VSConstants.GUID_OutWindowDebugPane },
{ PredefinedOutputWindowPanes.General, VSConstants.GUID_OutWindowGeneralPane },
};
private readonly Dictionary<string, IOutputWindowPane> _panes = new Dictionary<string, IOutputWindowPane>();
public IOutputWindowPane TryGetPane(string name)
{
IOutputWindowPane pane = null;
if (_panes.TryGetValue(name, out pane))
return pane;
var olesp = (IOleServiceProvider)GlobalServiceProvider.GetService(typeof(IOleServiceProvider));
var outputWindow = olesp.TryGetGlobalService<SVsOutputWindow, IVsOutputWindow>();
if (outputWindow == null)
return null;
Guid guid;
if (!_outputWindows.TryGetValue(name, out guid))
{
var definition = OutputWindowDefinitions.FirstOrDefault(lazy => lazy.Metadata.Name.Equals(name));
if (definition == null)
return null;
guid = Guid.NewGuid();
// this controls whether the pane is listed in the output panes dropdown list, *not* whether the pane is initially selected
bool visible = true;
bool clearWithSolution = false;
if (ErrorHandler.Failed(ErrorHandler.CallWithCOMConvention(() => outputWindow.CreatePane(ref guid, definition.Metadata.Name, Convert.ToInt32(visible), Convert.ToInt32(clearWithSolution)))))
return null;
_outputWindows.Add(definition.Metadata.Name, guid);
}
IVsOutputWindowPane vspane = null;
if (ErrorHandler.Failed(ErrorHandler.CallWithCOMConvention(() => outputWindow.GetPane(ref guid, out vspane))))
return null;
pane = new VsOutputWindowPaneAdapter(vspane);
_panes[name] = pane;
return pane;
}
}
}
namespace JavaLanguageService.Panes
{
using System;
using System.Diagnostics.Contracts;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
internal sealed class VsOutputWindowPaneAdapter : IOutputWindowPane, IDisposable
{
private IVsOutputWindowPane _pane;
public VsOutputWindowPaneAdapter(IVsOutputWindowPane pane)
{
Contract.Requires<ArgumentNullException>(pane != null);
this._pane = pane;
}
public string Name
{
get
{
string name = null;
ErrorHandler.ThrowOnFailure(this._pane.GetName(ref name));
return name;
}
set
{
ErrorHandler.ThrowOnFailure(this._pane.SetName(value));
}
}
public void Dispose()
{
_pane = null;
}
public void Activate()
{
ErrorHandler.ThrowOnFailure(this._pane.Activate());
}
public void Hide()
{
ErrorHandler.ThrowOnFailure(this._pane.Hide());
}
public void Write(string text)
{
Contract.Requires<ArgumentNullException>(text != null);
ErrorHandler.ThrowOnFailure(this._pane.OutputStringThreadSafe(text));
}
public void WriteLine(string text)
{
Contract.Requires<ArgumentNullException>(text != null);
if (!text.EndsWith(Environment.NewLine))
text += Environment.NewLine;
Write(text);
}
}
}
Finally, one extension method that’s used to help with the IOleServiceProvider
namespace JavaLanguageService.Extensions
{
using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
public static class ServiceProviderExtensions
{
public static TServiceInterface TryGetGlobalService<TServiceClass, TServiceInterface>(this IOleServiceProvider sp)
where TServiceInterface : class
{
Contract.Requires<NullReferenceException>(sp != null);
Guid guidService = typeof(TServiceInterface).GUID;
Guid riid = typeof(TServiceInterface).GUID;
IntPtr obj = IntPtr.Zero;
int result = ErrorHandler.CallWithCOMConvention(() => sp.QueryService(ref guidService, ref riid, out obj));
if (ErrorHandler.Failed(result) || obj == IntPtr.Zero)
return null;
try
{
TServiceInterface service = (TServiceInterface)Marshal.GetObjectForIUnknown(obj);
return service;
}
finally
{
Marshal.Release(obj);
}
}
}
}