Merge pull request #115 from microsoft/dev/maiak/outputWindow

Add docs and a sample for the output window API.
This commit is contained in:
Maia Kelner 2022-08-08 12:03:43 -07:00 коммит произвёл GitHub
Родитель ee9d5609d5 df6263ae36
Коммит e7dd5a48b1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 470 добавлений и 8 удалений

Просмотреть файл

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>10</LangVersion>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<NeutralLanguage>en-US</NeutralLanguage>
<NoWarn>$(NoWarn);CS1591;IDE0008;CA1812</NoWarn>
<!-- The VisualStudio.Extensibility preview packages are available from the azure-public/vside/vssdk feed -->
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/azure-public/vside/_packaging/vssdk/nuget/v3/index.json;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.4.15-preview-1" />
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.4.15-preview-1" />
</ItemGroup>
<ItemGroup>
<Compile Update="Strings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Strings.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Strings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Strings.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,16 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace OutputWindowSample;
using System.Resources;
using Microsoft.VisualStudio.Extensibility;
/// <summary>
/// Extension entry point for the OutputWindowSampleExtension.
/// </summary>
public class OutputWindowSampleExtension : Extension
{
/// <inheritdoc />
protected override ResourceManager? ResourceManager => Strings.ResourceManager;
}

Просмотреть файл

@ -0,0 +1,41 @@
---
title: Output Window Sample Extension reference
description: A reference for Output Window Sample extension
date: 2022-08-01
---
# Walkthrough: Output Window Sample Extension
This extension demonstrates the most basic usage of the [Output Window API](./../../../docs/new-extensibility-model/extension-guides/outputWindow/outputWindow.md)
## Summary
This extension adds a [command](./../../../docs/new-extensibility-model/extension-guides/command/command.md) to the Tools menu called "Test the Output Window". When invoked, the command will print `"This is a test of the output window."` to the Output pane in a Channel called "MyOutputWindow" (look for the Channel name in the "Show output from:" dropdown in the Output pane).
## TestOutputWindowCommand
[`TestOutputWindowCommand.cs`](TestOutputWindowCommand.cs) contains a command that demonstrates two parts of the Output Window API.
### Getting an Output Window Channel
The `GetOutputWindowAsync()` method creates an Output Window Channel by calling `Extensibility.Views().Output.GetChannelAsync()`. `GetChannelAsync()` takes the name of a resource from the ResourceManager set up in the [`Extension` class](#extension-class).
See [Getting an Output Window Channel](./../../../docs/new-extensibility-model/extension-guides/outputWindow/outputWindow.md#getting-an-output-window-channel) for more information.
### Writing to the Output Window
In the `ExecuteCommandAsync()` method, the `Writer` property of the `OutputWindow` instance is used to write a message to the Output pane in the IDE.
See [Writing to the Output Window](./../../../docs/new-extensibility-model/extension-guides/outputWindow/outputWindow.md#writing-to-the-output-window) for more information.
## Extension Class
As described in the [Output Window Display Name Resource ID requirements](./../../../docs/new-extensibility-model/extension-guides/outputWindow/outputWindow.md#display-name-resource-id-requirements), the current version of the Output Window API requires that the display name for the Output Window Channel be stored in a [Resource File](https://docs.microsoft.com/en-us/dotnet/core/extensions/resources) (such as a [`.resx`](https://docs.microsoft.com/en-us/dotnet/core/extensions/resources) resource file.)
In order for the call to `OutputExtensibility.GetChannelAsync()` to find the diaplay name, the resource file must be associated with the [`Extension` Instance](../../inside-the-sdk/extension-anatomy.md#extension-instance) by overriding the `ResourceManager` property.
[`OutputWindowSampleExtension.cs`](OutputWindowSampleExtension.cs) demonstrates this functionality.
## Resources File
The [`Strings.resx`](Strings.resx) file is a [Resource File](https://docs.microsoft.com/en-us/dotnet/core/extensions/resources) that contains the display name of the Output Window Channel for this extension.

72
New_Extensibility_Model/Samples/OutputWindowSample/Strings.Designer.cs сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,72 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace OutputWindowSample {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Strings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Strings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OutputWindowSample.Strings", typeof(Strings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to My Output Window.
/// </summary>
internal static string OutputWindowDisplayName {
get {
return ResourceManager.GetString("OutputWindowDisplayName", resourceCulture);
}
}
}
}

Просмотреть файл

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="OutputWindowDisplayName" xml:space="preserve">
<value>My Output Window</value>
</data>
</root>

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace OutputWindowSample;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Commands;
using Microsoft.VisualStudio.Extensibility.Definitions;
using Microsoft.VisualStudio.Extensibility.Documents;
/// <summary>
/// A sample command for displaying a test message in the output window.
/// </summary>
[Command("TestOutputWindowCommand", "Test the Output Window", placement: CommandPlacement.ToolsMenu)]
[CommandIcon(KnownMonikers.ToolWindow, IconSettings.IconAndText)]
public class TestOutputWindowCommand : Command
{
private OutputWindow? outputWindow;
/// <summary>
/// Initializes a new instance of the <see cref="TestOutputWindowCommand" /> class.
/// </summary>
/// <param name="extensibility">
/// Extensibility object instance.
/// </param>
/// <param name="id">
/// Command identifier.
/// </param>
public TestOutputWindowCommand(VisualStudioExtensibility extensibility, string id)
: base(extensibility, id)
{
}
/// <inheritdoc />
public override async Task InitializeAsync(CancellationToken cancellationToken)
{
this.outputWindow = await this.GetOutputWindowAsync(cancellationToken);
await base.InitializeAsync(cancellationToken);
}
/// <inheritdoc />
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
{
if (this.outputWindow != null)
{
await this.outputWindow.Writer.WriteLineAsync("This is a test of the output window.");
}
}
private async Task<OutputWindow?> GetOutputWindowAsync(CancellationToken cancellationToken)
{
string id = "MyOutputWindow";
string displayNameResourceId = nameof(Strings.OutputWindowDisplayName);
return await this.Extensibility.Views().Output.GetChannelAsync(id, displayNameResourceId, cancellationToken);
}
}

Просмотреть файл

@ -23,20 +23,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ToolWindowExtension", "Tool
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserPromptSample", "UserPromptSample\UserPromptSample.csproj", "{6AAB3137-2974-45F1-A426-E28231F6FE13}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OutputWindowSample", "OutputWindowSample\OutputWindowSample.csproj", "{053DB39C-56F5-41DC-9C85-EA14FE3F49B7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{11EB62B3-79FD-450D-BC86-6FF414178B7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11EB62B3-79FD-450D-BC86-6FF414178B7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11EB62B3-79FD-450D-BC86-6FF414178B7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11EB62B3-79FD-450D-BC86-6FF414178B7E}.Release|Any CPU.Build.0 = Release|Any CPU
{F005B03E-66DC-4145-8BBA-9E21ABE582C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F005B03E-66DC-4145-8BBA-9E21ABE582C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F005B03E-66DC-4145-8BBA-9E21ABE582C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F005B03E-66DC-4145-8BBA-9E21ABE582C2}.Release|Any CPU.Build.0 = Release|Any CPU
{26601396-0E9A-4B38-8FE9-D5107B77D445}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26601396-0E9A-4B38-8FE9-D5107B77D445}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26601396-0E9A-4B38-8FE9-D5107B77D445}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -49,6 +43,14 @@ Global
{6D16A4D7-4CF1-4162-9538-06F2D8499821}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6D16A4D7-4CF1-4162-9538-06F2D8499821}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6D16A4D7-4CF1-4162-9538-06F2D8499821}.Release|Any CPU.Build.0 = Release|Any CPU
{F005B03E-66DC-4145-8BBA-9E21ABE582C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F005B03E-66DC-4145-8BBA-9E21ABE582C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F005B03E-66DC-4145-8BBA-9E21ABE582C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F005B03E-66DC-4145-8BBA-9E21ABE582C2}.Release|Any CPU.Build.0 = Release|Any CPU
{11EB62B3-79FD-450D-BC86-6FF414178B7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11EB62B3-79FD-450D-BC86-6FF414178B7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11EB62B3-79FD-450D-BC86-6FF414178B7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11EB62B3-79FD-450D-BC86-6FF414178B7E}.Release|Any CPU.Build.0 = Release|Any CPU
{4605F69C-8179-409E-A3FA-431DD41F35CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4605F69C-8179-409E-A3FA-431DD41F35CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4605F69C-8179-409E-A3FA-431DD41F35CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -57,6 +59,10 @@ Global
{6AAB3137-2974-45F1-A426-E28231F6FE13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6AAB3137-2974-45F1-A426-E28231F6FE13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6AAB3137-2974-45F1-A426-E28231F6FE13}.Release|Any CPU.Build.0 = Release|Any CPU
{053DB39C-56F5-41DC-9C85-EA14FE3F49B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{053DB39C-56F5-41DC-9C85-EA14FE3F49B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{053DB39C-56F5-41DC-9C85-EA14FE3F49B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{053DB39C-56F5-41DC-9C85-EA14FE3F49B7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

Просмотреть файл

@ -0,0 +1,113 @@
---
title: Output Window Reference
description: A reference for output window extensibility
date: 2022-08-01
---
# Output Window Overview
The Output Window in the Visual Studio IDE is a [Tool Window](./../toolWindow/toolWindow.md) that can be used to deliver status, diagnostics/logging, or any other informational text to the user. Unlike [User Prompts](./../userPrompts/userPrompts.md), which might display a message box, the messages written to the Output Window are only displayed if the user has the Output pane visible in the IDE (Output in the View menu) and your Channel selected in the "Show output from:" dropdown menu.
# Getting Started
To get started, follow the [create the project](./../../getting-started/create-your-first-extension.md) section in Getting Started section.
Next, see the [OutputWindowSample](./../../../../New_Extensibility_Model/Samples/OutputWindowSample) sample for a full example of creating an extension that uses the output window.
# Working with the Output Window
This guide is designed to cover the top user scenarios when working with the Output Window:
- [Getting an Output Window Channel](#getting-an-output-window-channel)
- [Writing to the Output Window](#writing-to-the-output-window)
# Getting an Output Window Channel
In order to write to the Output Window, you need an Output Window Channel which can be created by calling `VisualStudioExtensibility.Views().Output.GetChannelAsync()`.
## `OutputWindowExtensibility.GetChannelAsync()`
The `GetChannelAsync()` method has three parameters:
| Parameter | Type | Required | Description |
| --------- |----- | -------- | ----------- |
| `identifier` | `string` | yes | A unique identifier for the channel. |
| `displayNameResourceId` | `string` | yes | The name of the [resource](https://docs.microsoft.com/en-us/dotnet/core/extensions/resources) that contains the display name of the output window. This is what will be visible in the "Show output from:" dropdown menu in the Output pane.<br /><br />For example, if you had a [`.resx`](https://docs.microsoft.com/en-us/dotnet/core/extensions/resources) resource file called `MyStrings.resx` with a resource named "OutputWindowDisplayName", you would use `nameof(MyStrings.OutputWindowDisplayName)` for this parameter. |
| `cancellationToken` | [`CancellationToken`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) | yes | The [`CancellationToken`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) for the async operation. |
## Display Name Resource ID Requirements
The current version of the Output Window API requires that the display name for the Output Window Channel be stored in a Resource File:
- Add a [`.resx` file](https://docs.microsoft.com/en-us/dotnet/core/extensions/resources) ([sample](./../../../../New_Extensibility_Model/Samples/OutputWindowSample/Strings.resx)) and make sure it is configured with the ResXFileCodeGenerator in your project ([sample](./../../../../New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSample.csproj)).
- Add an [Extension class](../../inside-the-sdk/extension-anatomy.md#extension-instance) to your project, if it doesn't have one already ([sample](./../../../../New_Extensibility_Model/Samples/OutputWindowSample/OutputWindowSampleExtension.cs))
- In the Extension class override the `ResourceManager` property to return the ResourceManager corresponding to your .resx file.
## Example
### `.resx` [Resource File](https://docs.microsoft.com/en-us/dotnet/core/extensions/resources)
In this case, `MyStrings.resx`:
```xml
<data name="OutputWindowDisplayName" xml:space="preserve">
<value>My Output Window</value>
</data>
```
### `.csproj` Project File
```xml
<ItemGroup>
<Compile Update="MyStrings.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>MyStrings.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="MyStrings.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>MyStrings.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
```
### Extension Class
In this case, `MyExtension.cs`:
```csharp
public class MyExtension : Extension
{
protected override ResourceManager? ResourceManager => MyStrings.ResourceManager;
}
```
**Note:** Make sure that the MyExtension class is in the same namespace as the `MyStrings` resource class, which defaults to the name of the project, unless you have overridden it.
### Extension code:
```csharp
string id = "MyOutputWindow";
string displayNameResourceId = nameof(Strings.OutputWindowDisplayName);
OutputWindow? outputWindow = await this.Extensibility.Views().Output.GetChannelAsync(id, displayNameResourceId, cancellationToken);
```
# Writing to the Output Window
The [`OutputWindow`](./../api/../../api/Microsoft.VisualStudio.Extensibility.md#outputwindow-type) instance obtained in [Getting an Output Window Channel](#getting-an-output-window-channel) has a [`System.IO.TextWriter`](https://docs.microsoft.com/en-us/dotnet/api/system.io.textwriter) property called `Writer`, which supports familiar operations for writing text, such as:
- [`Write()`](https://docs.microsoft.com/en-us/dotnet/api/system.io.textwriter.write)
- [`WriteAsync()`](https://docs.microsoft.com/en-us/dotnet/api/system.io.textwriter.writeasync)
- [`WriteLine()`](https://docs.microsoft.com/en-us/dotnet/api/system.io.textwriter.writeline)
- [`WriteLineAsync()`](https://docs.microsoft.com/en-us/dotnet/api/system.io.textwriter.writelineasync)
## Example
```csharp
if (this.outputWindow != null)
{
await this.outputWindow.Writer.WriteLineAsync("This is a test of the output window.");
}
```