Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions common/changes/cadl-vs/solution-settings_2022-08-24-18-47.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "cadl-vs",
"comment": "Fix issue with configured cadl-server location not being found when opening a solution.",
"type": "patch"
}
],
"packageName": "cadl-vs"
}
12 changes: 8 additions & 4 deletions packages/cadl-vs/VS2019/Microsoft.Cadl.VS2019.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
<VisualStudioMaxVersionExclusive>17.0</VisualStudioMaxVersionExclusive>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="16.0.206" ExcludeAssets="Runtime" />
<PackageReference Include="Microsoft.VisualStudio.Workspace" Version="16.3.43" ExcludeAssets="Runtime" />
<PackageReference Include="Microsoft.VisualStudio.Workspace.VSIntegration" Version="16.3.43" ExcludeAssets="Runtime" />
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="16.9.1050" PrivateAssets="All" />
<!-- Use 16.0.x here for compatible API -->
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="16.0.208" ExcludeAssets="Runtime" />
<PackageReference Include="Microsoft.VisualStudio.Workspace" Version="16.0.59" ExcludeAssets="Runtime" />
<PackageReference Include="Microsoft.VisualStudio.Workspace.VSIntegration" Version="16.0.59" ExcludeAssets="Runtime" />
<!-- Use 16.latest for build tools -->
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="16.11.65" PrivateAssets="All" />
<!-- Align with VS 16.0 version here: https://devblogs.microsoft.com/visualstudio/using-newtonsoft-json-in-a-visual-studio-extension/-->
<PackageReference Include="NewtonSoft.JSON" Version="9.0.1" ExcludeAssets="Runtime" />
</ItemGroup>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />
<Import Project="../Microsoft.Cadl.VS.targets" />
Expand Down
6 changes: 5 additions & 1 deletion packages/cadl-vs/VS2022/Microsoft.Cadl.VS2022.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@
<VisualStudioMaxVersionExclusive>18.0</VisualStudioMaxVersionExclusive>
</PropertyGroup>
<ItemGroup>
<!-- Use 17.0.x or latest 16.x if no 17.0.x for compatible API-->
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.0.31902.203" ExcludeAssets="Runtime" />
<PackageReference Include="Microsoft.VisualStudio.Workspace" Version="16.3.43" ExcludeAssets="Runtime" />
<PackageReference Include="Microsoft.VisualStudio.Workspace.VSIntegration" Version="16.3.43" ExcludeAssets="Runtime" />
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.1.4057" PrivateAssets="All" />
<!-- Use latest 17.x for build tools-->
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.3.2093" PrivateAssets="All" />
<!-- Align with VS 17.0 version here: https://devblogs.microsoft.com/visualstudio/using-newtonsoft-json-in-a-visual-studio-extension/-->
<PackageReference Include="NewtonSoft.JSON" Version="13.0.1" ExcludeAssets="Runtime" />
</ItemGroup>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />
<Import Project="../Microsoft.Cadl.VS.targets" />
Expand Down
5 changes: 5 additions & 0 deletions packages/cadl-vs/src/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public CadlUserErrorException(string message)
{

}

public CadlUserErrorException(string message, Exception innerException)
: base(message, innerException)
{
}
}


Expand Down
136 changes: 97 additions & 39 deletions packages/cadl-vs/src/VSExtension.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
using EnvDTE;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.Workspace;
using Microsoft.VisualStudio.Workspace.Settings;
using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Workspace;
using Microsoft.VisualStudio.Workspace.Settings;
using Microsoft.VisualStudio.Workspace.VSIntegration.Contracts;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;
using Debugger = System.Diagnostics.Debugger;
using Process = System.Diagnostics.Process;
using Task = System.Threading.Tasks.Task;
using System.Linq;
using System.ComponentModel;

namespace Microsoft.Cadl.VisualStudio
{
Expand Down Expand Up @@ -51,23 +55,24 @@ public sealed class LanguageClient : ILanguageClient
public event AsyncEventHandler<EventArgs>? StartAsync;
public event AsyncEventHandler<EventArgs>? StopAsync { add { } remove { } } // unused

private readonly IVsFolderWorkspaceService workspaceService;
private readonly IVsFolderWorkspaceService _workspaceService;
private readonly SVsServiceProvider _serviceProvider;
private string? _workspaceFolder;
private string? _configuredCadlServerPath;

[ImportingConstructor]
public LanguageClient([Import] IVsFolderWorkspaceService workspaceService)
public LanguageClient([Import] IVsFolderWorkspaceService workspaceService, [Import] SVsServiceProvider serviceProvider)
{
this.workspaceService = workspaceService;
_workspaceService = workspaceService;
_serviceProvider = serviceProvider;
}

public async Task<Connection?> ActivateAsync(CancellationToken token)
{
await Task.Yield();

var workspace = workspaceService.CurrentWorkspace;
var settingsManager = workspace?.GetSettingsManager();
var settings = settingsManager?.GetAggregatedSettings(SettingsTypes.Generic);
await ReadSettingsAsync();
var options = Environment.GetEnvironmentVariable("CADL_SERVER_NODE_OPTIONS");
var (serverCommand, serverArgs, env) = resolveCadlServer(settings);
var (serverCommand, serverArgs, env) = resolveCadlServer();
var info = new ProcessStartInfo
{
// Use cadl-server on PATH in production
Expand All @@ -79,7 +84,7 @@ public LanguageClient([Import] IVsFolderWorkspaceService workspaceService)
UseShellExecute = false,
CreateNoWindow = true,
Environment = { new("NODE_OPTIONS", options) },
WorkingDirectory = settings?.ScopePath,
WorkingDirectory = _workspaceFolder,
};

foreach (var entry in env)
Expand Down Expand Up @@ -110,7 +115,7 @@ public LanguageClient([Import] IVsFolderWorkspaceService workspaceService)
{
throw new CadlServerNotFoundException(info.FileName);
}
throw e;
throw;
}

}
Expand Down Expand Up @@ -140,17 +145,19 @@ public Task OnServerInitializeFailedAsync(Exception e)
#endif

#if VS2022
public Task<InitializationFailureContext?> OnServerInitializeFailedAsync(ILanguageClientInitializationInfo initializationState) {
var exception = initializationState.InitializationException;
var message = exception is CadlUserErrorException
? exception.Message
: $"File issue at https://github.com/microsoft/cadl\r\n\r\n{exception}";
Debug.Assert(exception is CadlUserErrorException, "Unexpected error initializing cadl-server:\r\n\r\n" + exception);
return Task.FromResult<InitializationFailureContext?>(
new InitializationFailureContext {
FailureMessage = "Failed to activate Cadl language server!\r\n" + message
});
}
public Task<InitializationFailureContext?> OnServerInitializeFailedAsync(ILanguageClientInitializationInfo initializationState)
{
var exception = initializationState.InitializationException;
var message = exception is CadlUserErrorException
? exception.Message
: $"File issue at https://github.com/microsoft/cadl\r\n\r\n{exception}";
Debug.Assert(exception is CadlUserErrorException, "Unexpected error initializing cadl-server:\r\n\r\n" + exception);
return Task.FromResult<InitializationFailureContext?>(
new InitializationFailureContext
{
FailureMessage = "Failed to activate Cadl language server!\r\n" + message
});
}
#endif

public Task OnServerInitializedAsync()
Expand Down Expand Up @@ -189,31 +196,36 @@ private static string GetDevelopmentCadlServerPath()
}
#endif

private (string, string[], IDictionary<string, string>) resolveCadlServer(IWorkspaceSettings? settings)
private (string, string[], IDictionary<string, string>) resolveCadlServer()
{
var env = new Dictionary<string, string>();
var args = new string[] { "--stdio" };
#if DEBUG
// Use local build of cadl-server in development (lauched from F5 in VS)
if (InDevelopmentMode())
{
var options = Environment.GetEnvironmentVariable("CADL_SERVER_NODE_OPTIONS");
var module = GetDevelopmentCadlServerPath();
return ("node.exe", new string[] { module, options }.Concat(args).ToArray(), env);
var options = Environment.GetEnvironmentVariable("CADL_SERVER_NODE_OPTIONS");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hhm did the formatter not catch this before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It breaks with #if it seems, I ran VS format command :(

var module = GetDevelopmentCadlServerPath();
return ("node.exe", new string[] { module, options }.Concat(args).ToArray(), env);
}
#endif

var serverPath = settings?.Property<string>("cadl.cadl-server.path");
if (serverPath == null)
var serverPath = _configuredCadlServerPath;
if (serverPath == null || serverPath.Length == 0)
{
return ("cadl-server.cmd", args, env);
}

var variables = new Dictionary<string, string>();
variables.Add("workspaceFolder", workspaceService.CurrentWorkspace.Location);
var variableResolver = new VariableResolver(variables);
if (_workspaceFolder != null)
{
variables.Add("workspaceFolder", _workspaceFolder);
}

var variableResolver = new VariableResolver(variables);
serverPath = variableResolver.ResolveVariables(serverPath);
serverPath = Path.GetFullPath(serverPath);

if (!serverPath.EndsWith(".js"))
{
if (File.Exists(serverPath))
Expand All @@ -223,13 +235,59 @@ private static string GetDevelopmentCadlServerPath()
}
else
{
serverPath = Path.Combine(serverPath, "cmd/cadl-server.js");
serverPath = Path.Combine(serverPath, "cmd", "cadl-server.js");
}
}

// We need to check this as the later check when process is started would
// only trigger if node.exe is not found, not if the .js file passed to it
// is not found.
if (!File.Exists(serverPath))
{
throw new CadlServerNotFoundException(serverPath);
}

env["CADL_SKIP_COMPILER_RESOLVE"] = "1";
return ("node.exe", new string[] { serverPath }.Concat(args).ToArray(), env);
}

private async Task ReadSettingsAsync()
{
var workspace = _workspaceService?.CurrentWorkspace;
if (workspace != null)
{
var settings = workspace.GetSettingsManager()?.GetAggregatedSettings(SettingsTypes.Generic);
_configuredCadlServerPath = settings?.Property<string>("cadl.cadl-server.path");
_workspaceFolder = workspace.Location;
}
else
{
// When a solution is open, there is no workspace settings manager,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't believe they don't have this story sorted out.

// so we read the settings file ourselves.
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var dte = (DTE)_serviceProvider.GetService(typeof(DTE));
if (dte != null && dte.Solution != null)
{
_workspaceFolder = Path.GetDirectoryName(dte.Solution.FullName);
await Task.Run(() =>
{
var settingsFile = Path.Combine(_workspaceFolder, ".vs", "VSWorkspaceSettings.json");
if (File.Exists(settingsFile))
{
try
{
var content = File.ReadAllText(settingsFile);
var settings = JsonConvert.DeserializeObject<Dictionary<string, string>>(content);
settings?.TryGetValue("cadl.cadl-server.path", out _configuredCadlServerPath);
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is JsonException)
{
throw new CadlUserErrorException($"Unable to read {settingsFile}: {ex.Message}", ex);
}
}
});
}
}
}
}
}
Expand Down