Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Completion and input API improvements #3727

Merged
merged 7 commits into from
Oct 25, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Microsoft.DotNet.Interactive
public System.String DefaultKernelName { get; set;}
public KernelHost Host { get;}
public System.Void Add(Kernel kernel, System.Collections.Generic.IEnumerable<System.String> aliases = null)
public System.Void AddKernelConnector<T>(ConnectKernelDirective<T> connectDirective)
public System.Void AddConnectDirective<T>(ConnectKernelDirective<T> connectDirective)
public System.Collections.Generic.IEnumerator<Kernel> GetEnumerator()
Kernel GetHandlingKernel(Microsoft.DotNet.Interactive.Commands.KernelCommand command, KernelInvocationContext context)
System.Threading.Tasks.Task HandleRequestKernelInfoAsync(Microsoft.DotNet.Interactive.Commands.RequestKernelInfo command, KernelInvocationContext context)
Expand Down Expand Up @@ -94,6 +94,7 @@ Microsoft.DotNet.Interactive
public static System.Void CSS(System.String content)
public static DisplayedValue display(System.Object value, System.String[] mimeTypes)
public static System.Threading.Tasks.Task<System.String> GetInputAsync(System.String prompt = , System.String typeHint = text)
public static System.Threading.Tasks.Task<System.Collections.Generic.IDictionary<System.String,System.String>> GetInputsAsync(System.Collections.Generic.IEnumerable<Microsoft.DotNet.Interactive.Commands.InputDescription> inputDescriptions)
public static System.Threading.Tasks.Task<PasswordString> GetPasswordAsync(System.String prompt = )
public static Microsoft.AspNetCore.Html.IHtmlContent HTML(System.String content)
public static System.Void Javascript(System.String scriptContent)
Expand Down Expand Up @@ -562,7 +563,7 @@ Microsoft.DotNet.Interactive.Directives
public System.String ToString()
public class KernelDirectiveCompletionContext
.ctor()
public System.Collections.Generic.IList<Microsoft.DotNet.Interactive.Events.CompletionItem> Completions { get;}
public System.Collections.Generic.IList<Microsoft.DotNet.Interactive.Events.CompletionItem> CompletionItems { get;}
public class KernelDirectiveParameter
.ctor(System.String name, System.String description = null)
public System.Boolean AllowImplicitName { get; set;}
Expand All @@ -572,9 +573,8 @@ Microsoft.DotNet.Interactive.Directives
public System.String Name { get;}
public System.Boolean Required { get; set;}
public System.String TypeHint { get; set;}
public KernelDirectiveParameter AddCompletions(System.Func<KernelDirectiveCompletionContext,System.Collections.Generic.IEnumerable<Microsoft.DotNet.Interactive.Events.CompletionItem>> getCompletions)
public KernelDirectiveParameter AddCompletions(System.Func<KernelDirectiveCompletionContext,System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<Microsoft.DotNet.Interactive.Events.CompletionItem>>> getCompletions)
public KernelDirectiveParameter AddCompletions(System.Func<KernelDirectiveCompletionContext,System.Collections.Generic.IEnumerable<System.String>> getCompletions)
public KernelDirectiveParameter AddCompletions(System.Func<KernelDirectiveCompletionContext,System.Threading.Tasks.Task> getCompletions)
public KernelDirectiveParameter AddCompletions(System.Func<System.Collections.Generic.IEnumerable<System.String>> getCompletions)
public System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Microsoft.DotNet.Interactive.Events.CompletionItem>> GetValueCompletionsAsync()
public System.String ToString()
public class KernelSpecifierDirective : KernelDirective
Expand Down
40 changes: 40 additions & 0 deletions src/Microsoft.DotNet.Interactive.CSharp.Tests/CSharpKernelTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.DotNet.Interactive.Commands;
using Microsoft.DotNet.Interactive.Events;
using Microsoft.DotNet.Interactive.Tests;
using Microsoft.DotNet.Interactive.Tests.Utility;
using Xunit;
Expand Down Expand Up @@ -56,4 +58,42 @@ public async Task GetValueInfos_only_returns_non_shadowed_values()
.Should()
.ContainSingle(v => v.Name == "x");
}

[Fact]
public async Task Use_of_interactive_API_in_submitted_code_does_not_produce_diagnostics()
{
using var kernel = new CSharpKernel();

var result1 = await kernel.SendAsync(
new SubmitCode(
"""
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.Commands;
using Microsoft.DotNet.Interactive.Events;

Kernel.Root.GetType()
"""));

result1.Events.Should().NotContainErrors();
result1.Events.OfType<DiagnosticsProduced>()
.SelectMany(d => d.FormattedDiagnostics)
.Should().BeEmpty();

var result2 = await kernel.SendAsync(
new RequestDiagnostics(
"""
using Microsoft.DotNet.Interactive;
using Microsoft.DotNet.Interactive.Commands;
using Microsoft.DotNet.Interactive.Events;



Kernel.Root.GetType()
"""));

result2.Events.Should().NotContainErrors();
result2.Events.OfType<DiagnosticsProduced>()
.SelectMany(d => d.FormattedDiagnostics)
.Should().BeEmpty();
}
}
48 changes: 30 additions & 18 deletions src/Microsoft.DotNet.Interactive.CSharp/InteractiveWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,7 @@ internal class InteractiveWorkspace : Workspace
private readonly List<MetadataReference> _packageManagerReferences = new();
private readonly SemaphoreSlim _workspaceLock = new(1, 1);

public InteractiveWorkspace() : base(MefHostServices.DefaultHost, WorkspaceKind.Interactive)
{
_parseOptions = new CSharpParseOptions(
LanguageVersion.Latest,
DocumentationMode.Parse,
SourceCodeKind.Script);

_referenceAssemblies = ResolveRefAssemblies();

_disposables.Add(Disposable.Create(() =>
{
_currentCompilation = null;
}));
}

private static string ResolveRefAssemblyPath()
private static readonly Lazy<string> _referenceAssembliesPath = new(() =>
{
var runtimeDir = Path.GetDirectoryName(typeof(object).Assembly.Location);
var refAssemblyDir = runtimeDir; // if any of the below path probing fails, fall back to the runtime so we can still run
Expand Down Expand Up @@ -75,6 +60,21 @@ private static string ResolveRefAssemblyPath()
}

return refAssemblyDir;
});

public InteractiveWorkspace() : base(MefHostServices.DefaultHost, WorkspaceKind.Interactive)
{
_parseOptions = new CSharpParseOptions(
LanguageVersion.Latest,
DocumentationMode.Parse,
SourceCodeKind.Script);

_referenceAssemblies = ResolveRefAssemblies();

_disposables.Add(Disposable.Create(() =>
{
_currentCompilation = null;
}));
}

private static bool TryParseVersion(string versionString, out Version v)
Expand All @@ -92,7 +92,7 @@ private static bool TryParseVersion(string versionString, out Version v)
private static IReadOnlyCollection<MetadataReference> ResolveRefAssemblies()
{
var assemblyRefs = new List<MetadataReference>();
foreach (var assemblyRef in Directory.EnumerateFiles(ResolveRefAssemblyPath(), "*.dll"))
foreach (var assemblyRef in Directory.EnumerateFiles(_referenceAssembliesPath.Value, "*.dll"))
{
try
{
Expand Down Expand Up @@ -149,7 +149,19 @@ private IReadOnlyCollection<MetadataReference> GetReferenceSet(Compilation compi
_referenceAssemblies
.Concat(_packageManagerReferences)
.Concat(compilation.DirectiveReferences)
.ToArray();
.ToHashSet();

foreach (var reference in compilation.ExternalReferences.Distinct())
{
if (reference.Display is { } display)
{
if (!display.Contains("Microsoft.NETCore.App"))
{
references.Add(reference);
}
}
}

return references;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,20 @@ public static Task LoadAsync(Kernel rootKernel)
AllowImplicitName = true
};

variableNameParameter.AddCompletions(_ => cSharpKernel.ScriptState
.Variables
.Where(v => v.Value is DataFrame)
.Select(v => new CompletionItem(v.Name, WellKnownTags.Parameter)));
variableNameParameter.AddCompletions(context =>
{
var completionItems = cSharpKernel.ScriptState
.Variables
.Where(v => v.Value is DataFrame)
.Select(v => new CompletionItem(v.Name, WellKnownTags.Parameter));

foreach (var item in completionItems)
{
context.CompletionItems.Add(item);
}

return Task.CompletedTask;
});

var directive = new KernelActionDirective("#!linqify")
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static Task LoadAsync(Kernel kernel)
inspect.Parameters.Add(new KernelDirectiveParameter("--configuration")
{
Description = "Build configuration to use. Debug or Release."
}.AddCompletions(_ => [ "Debug", "Release" ]));
}.AddCompletions(() => [ "Debug", "Release" ]));
inspect.Parameters.Add(new KernelDirectiveParameter("--kind")
{
Description = "Source code kind. Script or Regular."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ public async Task json_with_xml_content_type_produces_errors()
[Fact]
public async Task xml_with_json_content_type_produces_errors()
{
using var kernel = new HttpKernel();
using var kernel = new HttpKernel("http");

var firstCode = """
@baseUrl = https://httpbin.org/anything
Expand Down Expand Up @@ -594,7 +594,7 @@ public async Task xml_with_json_content_type_produces_errors()

var diagnostics = secondResult.Events.Should().ContainSingle<DiagnosticsProduced>().Which;

diagnostics.Diagnostics.First().Message.Should().Be($$$"""The supplied named request has content type of 'application/json' which differs from the required content type of 'application/xml'.""");
diagnostics.Diagnostics.First().Message.Should().Be("""The supplied named request has content type of 'application/json' which differs from the required content type of 'application/xml'.""");
}

[Theory]
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.DotNet.Interactive.Http/HttpKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ async Task IKernelCommandHandler<SubmitCode>.HandleAsync(SubmitCode command, Ker
{
var docVariableNodes = parsedDoc.SyntaxTree.RootNode.ChildNodes.OfType<HttpVariableDeclarationAndAssignmentNode>();
var docVariableNames = docVariableNodes.Where(n => n.Span.Start < lastSpan?.Start).Select(n => n.DeclarationNode?.VariableName).ToHashSet();

foreach (DeclaredVariable dv in parsedDoc.SyntaxTree.RootNode.TryGetDeclaredVariables(BindExpressionValues).declaredVariables.Values)
{
if (docVariableNames.Contains(dv.Name))
Expand All @@ -152,7 +153,6 @@ async Task IKernelCommandHandler<SubmitCode>.HandleAsync(SubmitCode command, Ker
}
}
}

}
else
{
Expand Down
16 changes: 6 additions & 10 deletions src/Microsoft.DotNet.Interactive.Http/Model/HttpNamedRequest.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
#nullable enable
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using Microsoft.CodeAnalysis.Text;
using System;
using Microsoft.DotNet.Interactive.Http.Parsing;
using Microsoft.CodeAnalysis;
using System;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Reactive.Concurrency;
using System.Xml.XPath;
using System.Xml;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Microsoft.DotNet.Interactive.Http;

using Diagnostic = CodeAnalysis.Diagnostic;

internal class HttpNamedRequest
{
internal HttpNamedRequest(HttpRequestNode httpRequestNode, HttpResponse response)
Expand All @@ -25,7 +21,7 @@ internal HttpNamedRequest(HttpRequestNode httpRequestNode, HttpResponse response
Response = response;
}

public string? Name { get; private set; }
public string? Name { get; }

private readonly HttpRequestNode RequestNode;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Microsoft.DotNet.Interactive.Http;

[TypeFormatterSource(
typeof(HttpResponseFormatterSource),
PreferredMimeTypes = new[] { HtmlFormatter.MimeType })]
PreferredMimeTypes = [HtmlFormatter.MimeType])]
public sealed class HttpResponse : PartialHttpResponse
{
public Dictionary<string, string[]> Headers { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Microsoft.DotNet.Interactive.Http;

[TypeFormatterSource(
typeof(HttpResponseFormatterSource),
PreferredMimeTypes = new[] { HtmlFormatter.MimeType })]
PreferredMimeTypes = [HtmlFormatter.MimeType])]
public class PartialHttpResponse : EmptyHttpResponse
{
public int StatusCode { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ protected CompositeKernel CreateCompositeKernelAsync(params IJupyterKernelConnec
jupyterKernelCommand.AddConnectionOptions(options);
}

kernel.AddKernelConnector(jupyterKernelCommand);
kernel.AddConnectDirective(jupyterKernelCommand);
_disposables.Add(kernel);
return kernel;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.DotNet.Interactive.Jupyter.Connection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.DotNet.Interactive.Directives;
using Microsoft.DotNet.Interactive.Events;
Expand Down Expand Up @@ -89,28 +90,31 @@ private IJupyterConnection GetJupyterConnection(ConnectJupyterKernel connectComm
return null;
}

private async Task<IEnumerable<CompletionItem>> GetKernelSpecsCompletionsAsync(KernelDirectiveCompletionContext ctx)
private async Task GetKernelSpecsCompletionsAsync(KernelDirectiveCompletionContext context)
{
var hash = GetParseResultHash(ctx);
var hash = GetParseResultHash(context);
if (_mruKernelSpecSuggestions.Key == hash)
{
return _mruKernelSpecSuggestions.Value;
}
foreach (var item in _mruKernelSpecSuggestions.Value)
{
context.CompletionItems.Add(item);
}

IEnumerable<CompletionItem> completions = [];
return;
}

var connection = GetJupyterConnection(new ConnectJupyterKernel(""));
using (connection as IDisposable)
{
completions = await GetKernelSpecsCompletionsAsync(connection);
}
var completions = (await GetKernelSpecsCompletionsAsync(connection)).ToArray();

if (completions is not null)
{
_mruKernelSpecSuggestions = new(hash, completions);
}

return completions;
foreach (var item in completions)
{
context.CompletionItems.Add(item);
}
}
}

private async Task<IEnumerable<CompletionItem>> GetKernelSpecsCompletionsAsync(IJupyterConnection connection)
Expand Down
Loading