using IdesLspPoc.ContentDefinitions;
using IdesLspPoc.LspClient.S3;
using Microsoft.VisualStudio.LanguageServer.Client;
using Microsoft.VisualStudio.Utilities;
using Newtonsoft.Json;
using Org.BouncyCastle.Security;
using StreamJsonRpc;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace IdesLspPoc.LspClient
{
///
/// This is a sample integration that provides S3 bucket names as completion text for
/// files named "*.s3.json".
///
// for some reason, we have associate LSPs with JSON using a custom type - see JsonContentType for comments
[ContentType(JsonContentType.ContentTypeName)]
[Export(typeof(ILanguageClient))]
public class S3LspClient : ToolkitLspClient, ILanguageClientCustomMessage2
{
///
/// Name of Language Client; displayed to user
/// For example, if the LSP writes logs to an output window, this is where they will appear
///
public override string Name => "AWS Toolkit language client for Amazon S3";
public override object InitializationOptions
{
get
{
// This is how we configure the behavior of AWS Language Servers.
// The structure needs to be formalized across all AWS hosts/extensions.
//
// This structure is exploration/conceptual/speculative at this time.
// See lsp\core\aws-lsp-core\src\initialization\awsInitializationOptions.ts
return new
{
// CONCEPT: We signal that we can provide credentials.
credentials = new
{
providesIam = true,
}
};
}
}
private readonly byte[] _aesKey = new byte[32];
private S3CredentialsUpdater _credentialsUpdater;
public S3LspClient()
{
// Create encryption key for this session
SecureRandom random = new SecureRandom();
random.NextBytes(_aesKey);
}
protected override string GetServerWorkingDir()
{
// to try using this extension, update dir to wherever your lsp service executable is
return @"C:\code\aws-toolkit-common\lsp\app\aws-lsp-s3-binary\bin";
}
protected override string GetServerPath()
{
return $@"{GetServerWorkingDir()}\aws-lsp-s3-binary-win.exe";
}
protected override IEnumerable GetLspProcessArgs()
{
return base.GetLspProcessArgs()
.Concat(new[] {
// Signal that we will send the encryption key before starting the LSP protocol channel
"--pre-init-encryption"
});
}
protected override async Task OnBeforeLspConnectionStartsAsync(Process lspProcess)
{
WriteEncryptionInit(lspProcess.StandardInput);
await base.OnBeforeLspConnectionStartsAsync(lspProcess);
}
private void WriteEncryptionInit(StreamWriter writer)
{
var json = JsonConvert.SerializeObject(new
{
version = "1.0",
key = Convert.ToBase64String(_aesKey),
});
writer.WriteLine(json);
}
#region ILanguageClientCustomMessage2
private JsonRpc _rpc;
public object MiddleLayer => null;
///
/// Allows extensions to handle LSP messages
/// Must be set before starting the language server (occurs in the base class OnLoadedAsync)
/// https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.languageserver.client.ilanguageclientcustommessage2.custommessagetarget?view=visualstudiosdk-2022#microsoft-visualstudio-languageserver-client-ilanguageclientcustommessage2-custommessagetarget
///
public object CustomMessageTarget { get; private set; }
public Task AttachForCustomMessageAsync(JsonRpc rpc)
{
_rpc = rpc;
// This is how we would call custom notifications to push credentials to the server
_credentialsUpdater = new S3CredentialsUpdater(_rpc, _aesKey, _outputWindow);
_credentialsUpdater.StartCredentialsRefreshSimulation();
return Task.CompletedTask;
}
#endregion
}
}