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:
Родитель
24c7bd92a6
Коммит
33cf9b6dca
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче