Skip to content

Commit

Permalink
Adding completion extension loading and execution logic (#833)
Browse files Browse the repository at this point in the history
* Adding ICompletionExtension interface

* Adding extension loading and execution logic

* Fixing compilation error in VS 2017

* Using MEF for completion extension discovery

* using await on GetCompletionItems

* Adding an integration test for completion extension and update the completion extension interface

* Update the completion extension test

* Fix issues based on review comments

* Remove try/cache based on review comments, fix a integration test.

* More changes based on review comments

* Fixing SendResult logic for completion extension loading

* Only load completion extension from the assembly passed in, add more comments in the test

* Adding right assert messages in the test.

* More fixes based on review comments

* Dropping ICompletionExtensionProvider, load assembly only if it's loaded at the first time or updated since last load.

* Fix based on the latest review comments

* Adding missing TSQL functions in default completion list

* Update jsonrpc documentation for completion/extLoad
  • Loading branch information
shengyfu authored and kburtram committed Jul 19, 2019
1 parent e3ec6eb commit e1b9890
Show file tree
Hide file tree
Showing 18 changed files with 1,000 additions and 354 deletions.
48 changes: 43 additions & 5 deletions docs/guide/jsonrpc_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ The SQL Tools Service implements the following portion Language Service Protocol
* :leftwards_arrow_with_hook: [textDocument/references](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#textDocument_references)
* :leftwards_arrow_with_hook: [textDocument/definition](https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#textDocument_definition)

### Language Service Protocol Extensions

* :leftwards_arrow_with_hook: [completion/extLoad](#completion_extLoad)

# Message Protocol

A message consists of two parts: a header section and the message body. For now, there is
Expand Down Expand Up @@ -450,7 +454,7 @@ Disconnect the connection specified in the request.
{
/// <summary>
/// A URI identifying the owner of the connection. This will most commonly be a file in the workspace
/// or a virtual file representing an object in a database.
/// or a virtual file representing an object in a database.
/// </summary>
public string OwnerUri { get; set; }
}
Expand Down Expand Up @@ -619,7 +623,7 @@ Save a resultset as CSV to a file.
/// End index of the selected rows (inclusive)
/// </summary>
public int? RowEndIndex { get; set; }

/// <summary>
/// Start index of the selected columns (inclusive)
/// </summary>
Expand Down Expand Up @@ -661,7 +665,7 @@ Save a resultset as CSV to a file.
public class SaveResultRequestResult
{
/// <summary>
/// Error messages for saving to file.
/// Error messages for saving to file.
/// </summary>
public string Messages { get; set; }
}
Expand Down Expand Up @@ -705,7 +709,7 @@ Save a resultset as JSON to a file.
/// End index of the selected rows (inclusive)
/// </summary>
public int? RowEndIndex { get; set; }

/// <summary>
/// Start index of the selected columns (inclusive)
/// </summary>
Expand Down Expand Up @@ -739,8 +743,42 @@ Save a resultset as JSON to a file.
public class SaveResultRequestResult
{
/// <summary>
/// Error messages for saving to file.
/// Error messages for saving to file.
/// </summary>
public string Messages { get; set; }
}
```
## Language Service Protocol Extensions
### <a name="completion_extload"></a>`completion/extLoad`
Load a completion extension.
#### Request
```typescript
public class CompletionExtensionParams
{
/// <summary>
/// Absolute path for the assembly containing the completion extension
/// </summary>
public string AssemblyPath { get; set; }

/// <summary>
/// The type name for the completion extension
/// </summary>
public string TypeName { get; set; }

/// <summary>
/// Property bag for initializing the completion extension
/// </summary>
public Dictionary<string, object> Properties { get; set; }
}
```
#### Response
```typescript
bool
```
9 changes: 9 additions & 0 deletions sqltoolsservice.sln
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SqlTools.ManagedB
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SqlTools.ManagedBatchParser.IntegrationTests", "test\Microsoft.SqlTools.ManagedBatchParser.IntegrationTests\Microsoft.SqlTools.ManagedBatchParser.IntegrationTests.csproj", "{D3696EFA-FB1E-4848-A726-FF7B168AFB96}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.SqlTools.Test.CompletionExtension", "test\CompletionExtSample\Microsoft.SqlTools.Test.CompletionExtension.csproj", "{0EC2B30C-0652-49AE-9594-85B3C3E9CA21}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -228,6 +230,12 @@ Global
{D3696EFA-FB1E-4848-A726-FF7B168AFB96}.Integration|Any CPU.Build.0 = Debug|Any CPU
{D3696EFA-FB1E-4848-A726-FF7B168AFB96}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D3696EFA-FB1E-4848-A726-FF7B168AFB96}.Release|Any CPU.Build.0 = Release|Any CPU
{0EC2B30C-0652-49AE-9594-85B3C3E9CA21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0EC2B30C-0652-49AE-9594-85B3C3E9CA21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EC2B30C-0652-49AE-9594-85B3C3E9CA21}.Integration|Any CPU.ActiveCfg = Debug|Any CPU
{0EC2B30C-0652-49AE-9594-85B3C3E9CA21}.Integration|Any CPU.Build.0 = Debug|Any CPU
{0EC2B30C-0652-49AE-9594-85B3C3E9CA21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EC2B30C-0652-49AE-9594-85B3C3E9CA21}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -256,6 +264,7 @@ Global
{EF02F89F-417E-4A40-B7E6-B102EE2DF24D} = {2BBD7364-054F-4693-97CD-1C395E3E84A9}
{3F82F298-700A-48DF-8A69-D048DFBA782C} = {2BBD7364-054F-4693-97CD-1C395E3E84A9}
{D3696EFA-FB1E-4848-A726-FF7B168AFB96} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
{0EC2B30C-0652-49AE-9594-85B3C3E9CA21} = {AB9CA2B8-6F70-431C-8A1D-67479D8A7BE4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B31CDF4B-2851-45E5-8C5F-BE97125D9DD8}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Composition.Convention;
using System.Composition.Hosting;
Expand All @@ -17,19 +18,19 @@
namespace Microsoft.SqlTools.Hosting.Extensibility
{
/// <summary>
/// A MEF-based service provider. Supports any MEF-based configuration but is optimized for
/// A MEF-based service provider. Supports any MEF-based configuration but is optimized for
/// service discovery over a set of DLLs in an application scope. Any service registering using
/// the <c>[Export(IServiceContract)]</c> attribute will be discovered and used by this service
/// provider if it's in the set of Assemblies / Types specified during its construction. Manual
/// override of this is supported by calling
/// override of this is supported by calling
/// <see cref="RegisteredServiceProvider.RegisterSingleService" /> and similar methods, since
/// this will initialize that service contract and avoid the MEF-based search and discovery
/// this will initialize that service contract and avoid the MEF-based search and discovery
/// process. This allows the service provider to link into existing singleton / known services
/// while using MEF-based dependency injection and inversion of control for most of the code.
/// </summary>
public class ExtensionServiceProvider : RegisteredServiceProvider
{
private readonly Func<ConventionBuilder, ContainerConfiguration> config;
private Func<ConventionBuilder, ContainerConfiguration> config;

public ExtensionServiceProvider(Func<ConventionBuilder, ContainerConfiguration> config)
{
Expand Down Expand Up @@ -106,8 +107,23 @@ private void EnsureExtensionStoreRegistered<T>()
Register(() => store.GetExports<T>());
}
}

/// <summary>
/// Merges in new assemblies to the existing container configuration.
/// </summary>
public void AddAssembliesToConfiguration(IEnumerable<Assembly> assemblies)
{
Validate.IsNotNull(nameof(assemblies), assemblies);
var previousConfig = config;
this.config = conventions => {
// Chain in the existing configuration function's result, then include additional
// assemblies
ContainerConfiguration containerConfig = previousConfig(conventions);
return containerConfig.WithAssemblies(assemblies, conventions);
};
}
}

/// <summary>
/// A store for MEF exports of a specific type. Provides basic wrapper functionality around MEF to standarize how
/// we lookup types and return to callers.
Expand All @@ -117,7 +133,7 @@ public class ExtensionStore
private readonly CompositionHost host;
private IList exports;
private readonly Type contractType;

/// <summary>
/// Initializes the store with a type to lookup exports of, and a function that configures the
/// lookup parameters.
Expand All @@ -142,7 +158,7 @@ public IEnumerable<T> GetExports<T>()
}
return exports.Cast<T>();
}

private ConventionBuilder GetExportBuilder()
{
// Define exports as matching a parent type, export as that parent type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Composition.Convention;
using System.Composition.Hosting;
Expand All @@ -19,7 +20,7 @@ namespace Microsoft.SqlTools.Extensibility
{
public class ExtensionServiceProvider : RegisteredServiceProvider
{
private static readonly string[] defaultInclusionList =
private static readonly string[] defaultInclusionList =
{
"microsofsqltoolscredentials.dll",
"microsoft.sqltools.hosting.dll",
Expand All @@ -36,8 +37,8 @@ public ExtensionServiceProvider(Func<ConventionBuilder, ContainerConfiguration>

public static ExtensionServiceProvider CreateDefaultServiceProvider(string[] inclusionList = null)
{
// only allow loading MEF dependencies from our assemblies until we can
// better seperate out framework assemblies and extension assemblies
// only allow loading MEF dependencies from our assemblies until we can
// better seperate out framework assemblies and extension assemblies
return CreateFromAssembliesInDirectory(inclusionList ?? defaultInclusionList);
}

Expand Down Expand Up @@ -114,8 +115,24 @@ private void EnsureExtensionStoreRegistered<T>()
base.Register<T>(() => store.GetExports<T>());
}
}

/// <summary>
/// Merges in new assemblies to the existing container configuration.
/// </summary>
public void AddAssembliesToConfiguration(IEnumerable<Assembly> assemblies)
{
Validate.IsNotNull(nameof(assemblies), assemblies);
var previousConfig = config;
this.config = conventions => {
// Chain in the existing configuration function's result, then include additional
// assemblies
ContainerConfiguration containerConfig = previousConfig(conventions);
return containerConfig.WithAssemblies(assemblies, conventions);
};
}

}

/// <summary>
/// A store for MEF exports of a specific type. Provides basic wrapper functionality around MEF to standarize how
/// we lookup types and return to callers.
Expand All @@ -125,7 +142,7 @@ public class ExtensionStore
private CompositionHost host;
private IList exports;
private Type contractType;

/// <summary>
/// Initializes the store with a type to lookup exports of, and a function that configures the
/// lookup parameters.
Expand All @@ -150,7 +167,7 @@ public static ExtensionStore CreateDefaultLoader<T>()
{
return CreateAssemblyStore<T>(typeof(ExtensionStore).GetTypeInfo().Assembly);
}

public static ExtensionStore CreateAssemblyStore<T>(Assembly assembly)
{
Validate.IsNotNull(nameof(assembly), assembly);
Expand All @@ -162,7 +179,7 @@ public static ExtensionStore CreateStoreForCurrentDirectory<T>()
{
string assemblyPath = typeof(ExtensionStore).GetTypeInfo().Assembly.Location;
string directory = Path.GetDirectoryName(assemblyPath);
return new ExtensionStore(typeof(T), (conventions) =>
return new ExtensionStore(typeof(T), (conventions) =>
new ContainerConfiguration().WithAssembliesInPath(directory, conventions));
}

Expand All @@ -174,7 +191,7 @@ public IEnumerable<T> GetExports<T>()
}
return exports.Cast<T>();
}

private ConventionBuilder GetExportBuilder()
{
// Define exports as matching a parent type, export as that parent type
Expand All @@ -183,7 +200,7 @@ private ConventionBuilder GetExportBuilder()
return builder;
}
}

public static class ContainerConfigurationExtensions
{
public static ContainerConfiguration WithAssembliesInPath(this ContainerConfiguration configuration, string path, SearchOption searchOption = SearchOption.TopDirectoryOnly)
Expand Down Expand Up @@ -230,7 +247,7 @@ protected override Assembly Load(AssemblyName assemblyName)
var apiApplicationFileInfo = new FileInfo($"{folderPath}{Path.DirectorySeparatorChar}{assemblyName.Name}.dll");
if (File.Exists(apiApplicationFileInfo.FullName))
{
// Creating a new AssemblyContext instance for the same folder puts us at risk
// Creating a new AssemblyContext instance for the same folder puts us at risk
// of loading the same DLL in multiple contexts, which leads to some unpredictable
// behavior in the loader. See https://github.com/dotnet/coreclr/issues/19632

Expand Down
Loading

0 comments on commit e1b9890

Please sign in to comment.