Log output from ADL language server to VS output window pane (#437)

* Log output from ADL language server to VS output window pane
* Use nullable
* Turn up warnings; treat warnings as errors
* Launch petstore sample on F5 to reduce iteration time
This commit is contained in:
Nick Guerrera 2021-04-08 16:52:26 -07:00 коммит произвёл GitHub
Родитель 24c7bd92a6
Коммит 33cf9b6dca
5 изменённых файлов: 206 добавлений и 31 удалений

6
packages/adl-vs/.gitignore поставляемый
Просмотреть файл

@ -1,6 +0,0 @@
bin/
obj/
.vs/
*.binlog
*.sln

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

@ -1,4 +1,4 @@
<Project>
<Project>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" />
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
@ -8,14 +8,19 @@
<DeployExtension Condition="'$(Configuration)' != 'Debug'">false</DeployExtension>
<EnableDefaultNoneItems>false</EnableDefaultNoneItems>
<LangVersion>Latest</LangVersion>
<Nullable>Enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningLevel>5</WarningLevel>
<Features>strict</Features>
</PropertyGroup>
<ItemGroup>
<Content Include="LICENSE" />
<Content Include="adl.pkgdef" />
<Content Include="LICENSE" Visible="False"/>
<Content Include="*.pkgdef" />
<Content Include="node_modules/adl-vscode/dist/adl.tmLanguage" Link="TextMate/adl.tmLanguage" />
<Content Include="$(IntermediateOutputPath)DebugSourceDirectory.txt" Link="DebugSourceDirectory.txt" Condition="'$(Configuration)' == 'Debug'"/>
<Content Include="$(IntermediateOutputPath)DebugSourceDirectory.txt" Link="DebugSourceDirectory.txt" Condition="'$(Configuration)' == 'Debug'" Visible="False" />
<Content Update="@(Content)" IncludeInVSIX="true" CopyToOutputDirectory="PreserveNewest" />
<None Include="*;Properties\*" Exclude="@(Content);*.sln" />
<None Include="*.vsixmanifest" />
<None Include="Properties\*.json" />
<Reference Include="System.ComponentModel.Composition" />
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" />
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="16.0.206" ExcludeAssets="runtime" />

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

@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31202.260
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Adl.VisualStudio", "Microsoft.Adl.VisualStudio.csproj", "{E56D4BDB-4A17-4BBB-81C1-258FBA49979F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E56D4BDB-4A17-4BBB-81C1-258FBA49979F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E56D4BDB-4A17-4BBB-81C1-258FBA49979F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E56D4BDB-4A17-4BBB-81C1-258FBA49979F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E56D4BDB-4A17-4BBB-81C1-258FBA49979F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1DFC3838-4459-4321-B2A1-D473D4654597}
EndGlobalSection
EndGlobal

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

@ -2,8 +2,8 @@
"profiles": {
"Microsoft.Adl.VisualStudio": {
"commandName": "Executable",
"executablePath": "devenv.exe",
"commandLineArgs": "/rootSuffix Exp",
"executablePath": "$(DevEnvDir)devenv.exe",
"commandLineArgs": "/rootSuffix Exp ../../../adl-samples/petstore",
"environmentVariables": {
"ADL_SERVER_NODE_OPTIONS": "--nolazy --inspect=4242",
"ADL_DEVELOPMENT_MODE": "true"

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

@ -7,39 +7,49 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Task = System.Threading.Tasks.Task;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;
using Task = System.Threading.Tasks.Task;
using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider;
namespace Microsoft.Adl.VisualStudio {
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
[Guid("88b9492f-c019-492c-8aeb-f325a7e4cf23")]
public sealed class Package : AsyncPackage { }
public class ContentDefinition {
public sealed class ContentDefinition {
[Export]
[Name("adl")]
[BaseDefinition(CodeRemoteContentDefinition.CodeRemoteContentTypeName)]
public static ContentTypeDefinition AdlContentTypeDefinition;
public ContentTypeDefinition? AdlContentTypeDefinition => null;
[Export]
[FileExtension(".adl")]
[ContentType("adl")]
public static FileExtensionToContentTypeDefinition AdlFileExtensionDefinition;
public FileExtensionToContentTypeDefinition? AdlFileExtensionDefinition => null;
}
[ContentType("adl")]
[Export(typeof(ILanguageClient))]
public class LanguageClient : ILanguageClient {
[ContentType("adl")]
public sealed class LanguageClient : ILanguageClient {
public string Name => "ADL Language Support";
public IEnumerable<string> ConfigurationSections => null;
public object InitializationOptions => null;
public IEnumerable<string> FilesToWatch => null;
public event AsyncEventHandler<EventArgs> StartAsync;
public event AsyncEventHandler<EventArgs> StopAsync { add { } remove { } } // unused
public IEnumerable<string>? ConfigurationSections => null;
public object? InitializationOptions => null;
public IEnumerable<string>? FilesToWatch => null;
public event AsyncEventHandler<EventArgs>? StartAsync;
public event AsyncEventHandler<EventArgs>? StopAsync { add { } remove { } } // unused
private readonly IOutputWindow _outputWindow;
[ImportingConstructor]
public LanguageClient(IOutputWindow window) {
_outputWindow = window;
}
public async Task<Connection> ActivateAsync(CancellationToken token) {
await Task.Yield();
@ -51,6 +61,7 @@ namespace Microsoft.Adl.VisualStudio {
Arguments = "--stdio",
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
Environment = { new("NODE_OPTIONS", options) },
@ -68,11 +79,19 @@ namespace Microsoft.Adl.VisualStudio {
#endif
var process = Process.Start(info);
return new Connection(process.StandardOutput.BaseStream, process.StandardInput.BaseStream);
process.BeginErrorReadLine();
process.ErrorDataReceived += (_, e) => LogMessage(e.Data);
return new Connection(
process.StandardOutput.BaseStream,
process.StandardInput.BaseStream);
}
public async Task OnLoadedAsync() {
await StartAsync?.InvokeAsync(this, EventArgs.Empty);
var start = StartAsync;
if (start is not null) {
await start.InvokeAsync(this, EventArgs.Empty);
}
}
public Task OnServerInitializeFailedAsync(Exception e) {
@ -83,22 +102,154 @@ namespace Microsoft.Adl.VisualStudio {
return Task.CompletedTask;
}
private void LogMessage(string? message) {
if (message is null || message.Length == 0) {
return;
}
Debug.WriteLine("ADL Server: " + message);
_outputWindow.LogMessage(message);
}
#if DEBUG
static bool InDevelopmentMode() {
private static bool InDevelopmentMode() {
return string.Equals(
Environment.GetEnvironmentVariable("ADL_DEVELOPMENT_MODE"),
"true",
StringComparison.OrdinalIgnoreCase);
}
static string GetDevelopmentAdlServerPath() {
// Even when debugging, we get deployed to an extension folder outside the source
// tree, so we stash the source directory in a file in debug builds so we can use it
// to run adl-server against the live developer build in the source tree.
private static string GetDevelopmentAdlServerPath() {
// Even when debugging, we get deployed to an extension folder outside the
// source tree, so we stash the source directory in a file in debug builds
// so we can use it to run adl-server against the live developer build in
// the source tree.
var thisDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var srcDir = File.ReadAllText(Path.Combine(thisDir, "DebugSourceDirectory.txt")).Trim();
return Path.GetFullPath(Path.Combine(srcDir, "../adl/cmd/adl-server.js"));
}
#endif
}
public interface IOutputWindow {
void LogMessage(string message);
}
[Export(typeof(IOutputWindow))]
internal sealed class OutputWindow : IOutputWindow {
private static readonly Guid _guid = new("{2C6CA609-4EC9-4AEE-B163-AFF26503CAA4}");
private readonly IAsyncServiceProvider _serviceProvider;
private readonly JoinableTaskContext _context;
private readonly AsyncLazy<IVsOutputWindowPane?> _pane;
[ImportingConstructor]
public OutputWindow(
[Import(typeof(SAsyncServiceProvider))]
IAsyncServiceProvider serviceProvider,
JoinableTaskContext context
) {
_serviceProvider = serviceProvider;
_context = context;
_pane = new AsyncLazy<IVsOutputWindowPane?>(CreateOutputWindowAsync, context.Factory);
}
public void LogMessage(string message) {
message += Environment.NewLine;
// If the volume of log messages ever gets high, we'd need to batch them
// to avoid creating too much pending async work by doing this threading
// dance for every message.
Task.Run(async () => {
await _context.Factory.SwitchToMainThreadAsync();
var pane = await _pane.GetValueAsync();
if (pane is null) {
return;
}
if (pane is IVsOutputWindowPaneNoPump paneNoPump) {
paneNoPump.OutputStringNoPump(message);
} else {
var hr = pane.OutputStringThreadSafe(message);
if (Failed(hr)) {
return;
}
}
}).Forget();
}
private async Task<IVsOutputWindowPane?> CreateOutputWindowAsync() {
await _context.Factory.SwitchToMainThreadAsync();
var window = await _serviceProvider.GetServiceAsync<SVsOutputWindow, IVsOutputWindow>();
if (window is null) {
return null;
}
// Creating an output window pane activates it, and we don't want to do
// that, so we have to take steps to restore the active pane here.
var activePane = GetActivePane(window);
var pane = CreateOutputWindowPane(window);
if (pane is null) {
return null;
}
if (activePane != Guid.Empty) {
ActivatePane(window, activePane);
}
return pane;
}
private static void ActivatePane(IVsOutputWindow window, Guid guid) {
ThreadHelper.ThrowIfNotOnUIThread();
var hr = window.GetPane(ref guid, out var pane);
if (Failed(hr)) {
return;
}
hr = pane.Activate();
if (Failed(hr)) {
return;
}
}
private static Guid GetActivePane(IVsOutputWindow window) {
ThreadHelper.ThrowIfNotOnUIThread();
if (window is not IVsOutputWindow2 window2) {
return Guid.Empty;
}
var hr = window2.GetActivePaneGUID(out var guid);
if (Failed(hr)) {
return Guid.Empty;
}
return guid;
}
private static IVsOutputWindowPane? CreateOutputWindowPane(IVsOutputWindow window) {
ThreadHelper.ThrowIfNotOnUIThread();
var guid = _guid;
var hr = window.CreatePane(ref guid, "ADL", 1, 0);
if (Failed(hr)) {
return null;
}
hr = window.GetPane(ref guid, out var pane);
if (Failed(hr)) {
return null;
}
return pane;
}
private static bool Failed(int hr) {
return hr < 0;
}
}
}