From 8a6614dd2e94e63ceb2c0aa8b31b2a21913b1c71 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Wed, 7 Aug 2019 14:58:43 -0700 Subject: [PATCH 1/4] R: cleanup --- .../ExecuteRequestHandler.cs | 23 +++++++++---------- .../{ => Commands}/LoadExtension.cs | 3 +-- .../Kernel/CSharpKernelRenderingTests.cs | 11 +++++---- .../Kernel/CSharpKernelTests.cs | 4 +--- 4 files changed, 19 insertions(+), 22 deletions(-) rename Microsoft.DotNet.Interactive/{ => Commands}/LoadExtension.cs (85%) diff --git a/Microsoft.DotNet.Interactive.Jupyter/ExecuteRequestHandler.cs b/Microsoft.DotNet.Interactive.Jupyter/ExecuteRequestHandler.cs index f8f8c43f9..740c395e4 100644 --- a/Microsoft.DotNet.Interactive.Jupyter/ExecuteRequestHandler.cs +++ b/Microsoft.DotNet.Interactive.Jupyter/ExecuteRequestHandler.cs @@ -90,13 +90,13 @@ protected override void OnKernelEvent(IKernelEvent @event) switch (@event) { case ValueProduced valueProduced: - OnValueProduced(valueProduced, InFlightRequests); + OnValueProduced(valueProduced); break; case CodeSubmissionEvaluated codeSubmissionEvaluated: - OnCodeSubmissionEvaluated(codeSubmissionEvaluated, InFlightRequests); + OnCodeSubmissionEvaluated(codeSubmissionEvaluated); break; case CodeSubmissionEvaluationFailed codeSubmissionEvaluationFailed: - OnCodeSubmissionEvaluatedFailed(codeSubmissionEvaluationFailed, InFlightRequests); + OnCodeSubmissionEvaluatedFailed(codeSubmissionEvaluationFailed); break; case CodeSubmissionReceived _: case IncompleteCodeSubmissionReceived _: @@ -105,9 +105,9 @@ protected override void OnKernelEvent(IKernelEvent @event) } } - private static void OnCodeSubmissionEvaluatedFailed(CodeSubmissionEvaluationFailed codeSubmissionEvaluationFailed, ConcurrentDictionary openRequests) + private void OnCodeSubmissionEvaluatedFailed(CodeSubmissionEvaluationFailed codeSubmissionEvaluationFailed) { - openRequests.TryRemove(codeSubmissionEvaluationFailed.Command, out var openRequest); + InFlightRequests.TryRemove(codeSubmissionEvaluationFailed.Command, out var openRequest); var errorContent = new Error( eName: "Unhandled Exception", @@ -144,11 +144,10 @@ private static void OnCodeSubmissionEvaluatedFailed(CodeSubmissionEvaluationFail openRequest.Dispose(); } - private static void OnValueProduced( - ValueProduced valueProduced, - ConcurrentDictionary openRequests) + private void OnValueProduced(ValueProduced valueProduced) { - openRequests.TryGetValue(valueProduced.Command, out var openRequest); + var openRequest = InFlightRequests.Values.SingleOrDefault(); + if (openRequest == null) { return; @@ -203,10 +202,10 @@ private static void OnValueProduced( } } - private static void OnCodeSubmissionEvaluated(CodeSubmissionEvaluated codeSubmissionEvaluated, - ConcurrentDictionary openRequests) + private void OnCodeSubmissionEvaluated(CodeSubmissionEvaluated codeSubmissionEvaluated) { - openRequests.TryRemove(codeSubmissionEvaluated.Command, out var openRequest); + InFlightRequests.TryRemove(codeSubmissionEvaluated.Command, out var openRequest); + // reply ok var executeReplyPayload = new ExecuteReplyOk(executionCount: openRequest.ExecutionCount); diff --git a/Microsoft.DotNet.Interactive/LoadExtension.cs b/Microsoft.DotNet.Interactive/Commands/LoadExtension.cs similarity index 85% rename from Microsoft.DotNet.Interactive/LoadExtension.cs rename to Microsoft.DotNet.Interactive/Commands/LoadExtension.cs index b3367159e..27a498866 100644 --- a/Microsoft.DotNet.Interactive/LoadExtension.cs +++ b/Microsoft.DotNet.Interactive/Commands/LoadExtension.cs @@ -3,9 +3,8 @@ using System; using System.IO; -using Microsoft.DotNet.Interactive.Commands; -namespace Microsoft.DotNet.Interactive +namespace Microsoft.DotNet.Interactive.Commands { public class LoadExtension : KernelCommandBase { diff --git a/WorkspaceServer.Tests/Kernel/CSharpKernelRenderingTests.cs b/WorkspaceServer.Tests/Kernel/CSharpKernelRenderingTests.cs index 53fb7e34e..d16b0891f 100644 --- a/WorkspaceServer.Tests/Kernel/CSharpKernelRenderingTests.cs +++ b/WorkspaceServer.Tests/Kernel/CSharpKernelRenderingTests.cs @@ -14,6 +14,7 @@ using Pocket; using Xunit; using Xunit.Abstractions; +using static Pocket.Logger; namespace WorkspaceServer.Tests.Kernel { @@ -41,7 +42,7 @@ public async Task Default_rendering_is_HTML( .Timeout(5.Seconds()) .FirstAsync(); - Logger.Log.Info(valueProduced.ToDisplayString()); + Log.Info(valueProduced.ToDisplayString()); valueProduced .FormattedValues @@ -52,13 +53,13 @@ public async Task Default_rendering_is_HTML( } [Theory] - [InlineData("div(123).ToString()", "
123
" )] - [InlineData("\"hi\"", "hi" )] + [InlineData("div(123).ToString()", "
123
")] + [InlineData("\"hi\"", "hi")] public async Task String_is_rendered_as_plain_text( string submission, string expectedContent) { - var kernel = CreateKernel(); + var kernel = CreateKernel(); var result = await kernel.SendAsync(new SubmitCode(submission)); @@ -68,7 +69,7 @@ public async Task String_is_rendered_as_plain_text( .Timeout(5.Seconds()) .FirstAsync(); - Logger.Log.Info(valueProduced.ToDisplayString()); + Log.Info(valueProduced.ToDisplayString()); valueProduced .FormattedValues diff --git a/WorkspaceServer.Tests/Kernel/CSharpKernelTests.cs b/WorkspaceServer.Tests/Kernel/CSharpKernelTests.cs index adb6b384e..9fa0772ca 100644 --- a/WorkspaceServer.Tests/Kernel/CSharpKernelTests.cs +++ b/WorkspaceServer.Tests/Kernel/CSharpKernelTests.cs @@ -414,9 +414,7 @@ public async Task OnLoadAsync(IKernel kernel) .Single() .FullName; - var kernel = CreateKernel() - .UseNugetDirective() - .UseExtendDirective(); + var kernel = CreateKernel(); await kernel.SendAsync(new SubmitCode($"#extend \"{extensionDllPath}\"")); From 8be482f69b79c08d4b9a39cd5b6dcc3409fff56e Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Wed, 7 Aug 2019 15:00:56 -0700 Subject: [PATCH 2/4] R: improve some ToString methods --- .../Events/CodeSubmissionEvaluationFailed.cs | 4 ++++ .../Events/CodeSubmissionReceived.cs | 4 ++++ .../Events/CompleteCodeSubmissionReceived.cs | 4 ++++ .../Events/KernelEventBase.cs | 2 ++ .../StringExtensions.cs | 22 +++++++++++++++++++ 5 files changed, 36 insertions(+) create mode 100644 Microsoft.DotNet.Interactive/StringExtensions.cs diff --git a/Microsoft.DotNet.Interactive/Events/CodeSubmissionEvaluationFailed.cs b/Microsoft.DotNet.Interactive/Events/CodeSubmissionEvaluationFailed.cs index 64016fb1f..dc77db616 100644 --- a/Microsoft.DotNet.Interactive/Events/CodeSubmissionEvaluationFailed.cs +++ b/Microsoft.DotNet.Interactive/Events/CodeSubmissionEvaluationFailed.cs @@ -19,8 +19,12 @@ public CodeSubmissionEvaluationFailed( : message; } + public string Code => ((SubmitCode)Command).Code; + public Exception Exception { get; } public string Message { get; } + + public override string ToString() => $"{base.ToString()}: {Code}"; } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/Events/CodeSubmissionReceived.cs b/Microsoft.DotNet.Interactive/Events/CodeSubmissionReceived.cs index 06c332ab4..ecc17c5cc 100644 --- a/Microsoft.DotNet.Interactive/Events/CodeSubmissionReceived.cs +++ b/Microsoft.DotNet.Interactive/Events/CodeSubmissionReceived.cs @@ -13,6 +13,10 @@ public CodeSubmissionReceived(string value, SubmitCode submitCode) : base(submit Value = value ?? throw new ArgumentNullException(nameof(value)); } + public string Code => ((SubmitCode)Command).Code; + public string Value { get; } + + public override string ToString() => $"{base.ToString()}: {Value.TruncateForDisplay()}"; } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/Events/CompleteCodeSubmissionReceived.cs b/Microsoft.DotNet.Interactive/Events/CompleteCodeSubmissionReceived.cs index 6513ca37f..c2c4a774f 100644 --- a/Microsoft.DotNet.Interactive/Events/CompleteCodeSubmissionReceived.cs +++ b/Microsoft.DotNet.Interactive/Events/CompleteCodeSubmissionReceived.cs @@ -10,5 +10,9 @@ public class CompleteCodeSubmissionReceived : KernelEventBase public CompleteCodeSubmissionReceived(SubmitCode submitCode) : base(submitCode) { } + + public string Code => ((SubmitCode)Command).Code; + + public override string ToString() => $"{base.ToString()}: {Code.TruncateForDisplay()}"; } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/Events/KernelEventBase.cs b/Microsoft.DotNet.Interactive/Events/KernelEventBase.cs index 29965702e..5a47ae1c0 100644 --- a/Microsoft.DotNet.Interactive/Events/KernelEventBase.cs +++ b/Microsoft.DotNet.Interactive/Events/KernelEventBase.cs @@ -14,7 +14,9 @@ protected KernelEventBase(IKernelCommand command) } protected KernelEventBase() + public override string ToString() { + return $"{GetType().Name}"; } public IKernelCommand Command { get; } diff --git a/Microsoft.DotNet.Interactive/StringExtensions.cs b/Microsoft.DotNet.Interactive/StringExtensions.cs new file mode 100644 index 000000000..6e4f044c4 --- /dev/null +++ b/Microsoft.DotNet.Interactive/StringExtensions.cs @@ -0,0 +1,22 @@ +// 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. + +namespace Microsoft.DotNet.Interactive +{ + internal static class StringExtensions + { + public static string TruncateForDisplay( + this string value, + int length = 25) + { + value = value.Trim(); + + if (value.Length > length) + { + value = value.Substring(0, length) + " ..."; + } + + return value; + } + } +} \ No newline at end of file From 45fe69f78e1cdd2ab000ee3e77d11d1034df9d61 Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Wed, 7 Aug 2019 15:04:22 -0700 Subject: [PATCH 3/4] remove KernelPipelineContext, add KernelCommandBase.Handler --- .../Commands/AddNugetPackage.cs | 17 +++ .../Commands/IKernelCommand.cs | 3 + .../Commands/KernelCommandBase.cs | 8 + .../Commands/SubmitCode.cs | 2 + .../CompositeKernel.cs | 6 +- .../Events/CodeSubmissionEvaluated.cs | 2 + .../Events/KernelEventBase.cs | 9 +- Microsoft.DotNet.Interactive/KernelBase.cs | 139 ++++++++++-------- .../KernelCommandPipeline.cs | 2 +- .../KernelCommandPipelineMiddleware.cs | 2 +- .../KernelExtensions.cs | 2 +- .../KernelInvocationContext.cs | 73 +++++++-- .../KernelPipelineContext.cs | 66 --------- .../KernelPipelineContinuation.cs | 2 +- .../Kernel/CSharpKernelTestBase.cs | 7 +- .../Kernel/CompositeKernelTests.cs | 35 +++-- .../Kernel/KernelInvocationContextTests.cs | 51 +++++++ WorkspaceServer/Kernel/CSharpKernel.cs | 78 +++++----- .../Kernel/CSharpKernelExtensions.cs | 21 ++- .../Servers/Roslyn/TrackingStringWriter.cs | 20 +-- 20 files changed, 315 insertions(+), 230 deletions(-) create mode 100644 Microsoft.DotNet.Interactive/Commands/AddNugetPackage.cs delete mode 100644 Microsoft.DotNet.Interactive/KernelPipelineContext.cs create mode 100644 WorkspaceServer.Tests/Kernel/KernelInvocationContextTests.cs diff --git a/Microsoft.DotNet.Interactive/Commands/AddNugetPackage.cs b/Microsoft.DotNet.Interactive/Commands/AddNugetPackage.cs new file mode 100644 index 000000000..9c83226d7 --- /dev/null +++ b/Microsoft.DotNet.Interactive/Commands/AddNugetPackage.cs @@ -0,0 +1,17 @@ +// 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; + +namespace Microsoft.DotNet.Interactive.Commands +{ + public class AddNugetPackage : KernelCommandBase + { + public AddNugetPackage(NugetPackageReference packageReference) + { + PackageReference = packageReference ?? throw new ArgumentNullException(nameof(packageReference)); + } + + public NugetPackageReference PackageReference { get; } + } +} \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/Commands/IKernelCommand.cs b/Microsoft.DotNet.Interactive/Commands/IKernelCommand.cs index baf555035..bd51d03d0 100644 --- a/Microsoft.DotNet.Interactive/Commands/IKernelCommand.cs +++ b/Microsoft.DotNet.Interactive/Commands/IKernelCommand.cs @@ -1,9 +1,12 @@ // 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.Threading.Tasks; + namespace Microsoft.DotNet.Interactive.Commands { public interface IKernelCommand { + Task InvokeAsync(KernelInvocationContext context); } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/Commands/KernelCommandBase.cs b/Microsoft.DotNet.Interactive/Commands/KernelCommandBase.cs index 51c2b683e..c80670fd6 100644 --- a/Microsoft.DotNet.Interactive/Commands/KernelCommandBase.cs +++ b/Microsoft.DotNet.Interactive/Commands/KernelCommandBase.cs @@ -1,9 +1,17 @@ // 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.Threading.Tasks; + namespace Microsoft.DotNet.Interactive.Commands { public abstract class KernelCommandBase : IKernelCommand { + public KernelCommandInvocation Handler { get; set; } + + public async Task InvokeAsync(KernelInvocationContext context) + { + await Handler(context); + } } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/Commands/SubmitCode.cs b/Microsoft.DotNet.Interactive/Commands/SubmitCode.cs index fbf611134..3ff46822b 100644 --- a/Microsoft.DotNet.Interactive/Commands/SubmitCode.cs +++ b/Microsoft.DotNet.Interactive/Commands/SubmitCode.cs @@ -18,5 +18,7 @@ public SubmitCode( public string Code { get; set; } public string TargetKernelName { get; set; } + + public override string ToString() => $"{base.ToString()}: {Code.TruncateForDisplay()}"; } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/CompositeKernel.cs b/Microsoft.DotNet.Interactive/CompositeKernel.cs index 552467f8b..7decabc39 100644 --- a/Microsoft.DotNet.Interactive/CompositeKernel.cs +++ b/Microsoft.DotNet.Interactive/CompositeKernel.cs @@ -28,7 +28,7 @@ public CompositeKernel() }; chooseKernelCommand.Handler = - CommandHandler.Create((kernelName, context) => + CommandHandler.Create((kernelName, context) => { DefaultKernel = this.Single(k => k.Name == kernelName); }); @@ -56,7 +56,7 @@ public void Add(IKernel kernel) protected override void SetKernel( IKernelCommand command, - KernelPipelineContext context) + KernelInvocationContext context) { if (context.Kernel == null) { @@ -73,7 +73,7 @@ protected override void SetKernel( protected internal override async Task HandleAsync( IKernelCommand command, - KernelPipelineContext context) + KernelInvocationContext context) { var kernel = context.Kernel; diff --git a/Microsoft.DotNet.Interactive/Events/CodeSubmissionEvaluated.cs b/Microsoft.DotNet.Interactive/Events/CodeSubmissionEvaluated.cs index 2706a9324..1a7d2feca 100644 --- a/Microsoft.DotNet.Interactive/Events/CodeSubmissionEvaluated.cs +++ b/Microsoft.DotNet.Interactive/Events/CodeSubmissionEvaluated.cs @@ -12,5 +12,7 @@ public CodeSubmissionEvaluated(SubmitCode command) : base(command) } public string Code => ((SubmitCode) Command).Code; + + public override string ToString() => $"{base.ToString()}: {Code.TruncateForDisplay()}"; } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/Events/KernelEventBase.cs b/Microsoft.DotNet.Interactive/Events/KernelEventBase.cs index 5a47ae1c0..af4ce1a68 100644 --- a/Microsoft.DotNet.Interactive/Events/KernelEventBase.cs +++ b/Microsoft.DotNet.Interactive/Events/KernelEventBase.cs @@ -8,17 +8,16 @@ namespace Microsoft.DotNet.Interactive.Events { public abstract class KernelEventBase : IKernelEvent { - protected KernelEventBase(IKernelCommand command) + protected KernelEventBase(IKernelCommand command = null) { - Command = command ?? throw new ArgumentNullException(nameof(command)); + Command = command ?? KernelInvocationContext.Current?.Command; } - protected KernelEventBase() + public IKernelCommand Command { get; } + public override string ToString() { return $"{GetType().Name}"; } - - public IKernelCommand Command { get; } } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/KernelBase.cs b/Microsoft.DotNet.Interactive/KernelBase.cs index 9060dda8c..ef774fbac 100644 --- a/Microsoft.DotNet.Interactive/KernelBase.cs +++ b/Microsoft.DotNet.Interactive/KernelBase.cs @@ -23,6 +23,7 @@ public abstract class KernelBase : IKernel private readonly Subject _channel = new Subject(); private readonly CompositeDisposable _disposables; private readonly List _directiveCommands = new List(); + private Parser _directiveParser; protected KernelBase() { @@ -49,62 +50,65 @@ private void AddSetKernelMiddleware() private void AddDirectiveMiddleware() { Pipeline.AddMiddleware( - (command, pipelineContext, next) => + (command, context, next) => command switch { SubmitCode submitCode => HandleDirectivesAndSubmitCode( submitCode, - pipelineContext, + context, next), LoadExtension loadExtension => HandleLoadExtension( loadExtension, - pipelineContext, + context, next), DisplayValue displayValue => HandleDisplayValue( displayValue, - pipelineContext, + context, next), - _ => next(command, pipelineContext) + _ => next(command, context) }); } private async Task HandleLoadExtension( LoadExtension loadExtension, - KernelPipelineContext pipelineContext, + KernelInvocationContext pipelineContext, KernelPipelineContinuation next) { - var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(loadExtension.AssemblyFile.FullName); + loadExtension.Handler = async context => + { + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(loadExtension.AssemblyFile.FullName); - var extensionTypes = assembly - .ExportedTypes - .Where(t => typeof(IKernelExtension).IsAssignableFrom(t)) - .ToArray(); + var extensionTypes = assembly + .ExportedTypes + .Where(t => typeof(IKernelExtension).IsAssignableFrom(t)) + .ToArray(); - foreach (var extensionType in extensionTypes) - { - var extension = (IKernelExtension) Activator.CreateInstance(extensionType); + foreach (var extensionType in extensionTypes) + { + var extension = (IKernelExtension) Activator.CreateInstance(extensionType); - await extension.OnLoadAsync(pipelineContext.Kernel); - } + await extension.OnLoadAsync(pipelineContext.Kernel); + } + + context.OnCompleted(); + }; await next(loadExtension, pipelineContext); } private async Task HandleDirectivesAndSubmitCode( SubmitCode submitCode, - KernelPipelineContext pipelineContext, + KernelInvocationContext pipelineContext, KernelPipelineContinuation next) { var modified = false; - var directiveParser = BuildDirectiveParser(pipelineContext); - var lines = new Queue( submitCode.Code.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None)); @@ -115,12 +119,12 @@ private async Task HandleDirectivesAndSubmitCode( { var currentLine = lines.Dequeue(); - var parseResult = directiveParser.Parse(currentLine); + var parseResult = BuildDirectiveParser().Parse(currentLine); if (parseResult.Errors.Count == 0) { modified = true; - await directiveParser.InvokeAsync(parseResult); + await _directiveParser.InvokeAsync(parseResult); } else { @@ -134,12 +138,12 @@ private async Task HandleDirectivesAndSubmitCode( { if (string.IsNullOrWhiteSpace(code)) { - pipelineContext.OnExecute(context => + submitCode.Handler = context => { context.OnNext(new CodeSubmissionEvaluated(submitCode)); context.OnCompleted(); return Task.CompletedTask; - }); + }; return; } @@ -154,40 +158,59 @@ private async Task HandleDirectivesAndSubmitCode( private async Task HandleDisplayValue( DisplayValue displayValue, - KernelPipelineContext pipelineContext, + KernelInvocationContext pipelineContext, KernelPipelineContinuation next) { - pipelineContext.OnExecute(invocationContext => + displayValue.Handler = context => + { + context.OnNext( + new ValueProduced( + displayValue.FormattedValue, + displayValue, + formattedValues: new[] { displayValue.FormattedValue })); + + context.OnCompleted(); + + return Task.CompletedTask; + }; + + displayValue.Handler = invocationContext => { invocationContext.OnNext( new ValueProduced( displayValue.FormattedValue, displayValue, formattedValues: new[] { displayValue.FormattedValue })); + invocationContext.OnCompleted(); + return Task.CompletedTask; - }); + }; await next(displayValue, pipelineContext); } - protected Parser BuildDirectiveParser( - KernelPipelineContext pipelineContext) + private Parser BuildDirectiveParser() { - var root = new RootCommand(); - - foreach (var c in _directiveCommands) + if (_directiveParser == null) { - root.Add(c); + var root = new RootCommand(); + + foreach (var c in _directiveCommands) + { + root.Add(c); + } + + _directiveParser = new CommandLineBuilder(root) + .UseMiddleware( + context => context.BindingContext + .AddService( + typeof(KernelInvocationContext), + () => KernelInvocationContext.Current)) + .Build(); } - return new CommandLineBuilder(root) - .UseMiddleware( - context => context.BindingContext - .AddService( - typeof(KernelPipelineContext), - () => pipelineContext)) - .Build(); + return _directiveParser; } public IObservable KernelEvents => _channel; @@ -197,6 +220,7 @@ protected Parser BuildDirectiveParser( public void AddDirective(Command command) { _directiveCommands.Add(command); + _directiveParser = null; } private class KernelOperation @@ -212,6 +236,18 @@ public KernelOperation(IKernelCommand command, TaskCompletionSource TaskCompletionSource { get; } } + private async Task ExecuteCommand(KernelOperation operation) + { + using var context = KernelInvocationContext.Establish(operation.Command); + using var _ = context.KernelEvents.Subscribe(PublishEvent); + + await Pipeline.SendAsync(operation.Command, context); + + var result = await context.InvokeAsync(); + + operation.TaskCompletionSource.SetResult(result); + } + private readonly ConcurrentQueue _commandQueue = new ConcurrentQueue(); @@ -232,32 +268,15 @@ public Task SendAsync( Task.Run(async () => { - while (_commandQueue.TryPeek(out var nextOperation) && - nextOperation != operation) + if (_commandQueue.TryDequeue(out var currentOperation)) { - // FIX: (SendAsync) make this less nasty - } - - if (_commandQueue.TryDequeue(out var theOperationWeCareAbout)) - { - await ExecuteCommand(theOperationWeCareAbout); + await ExecuteCommand(currentOperation); } }, cancellationToken).ConfigureAwait(false); return tcs.Task; } - private async Task ExecuteCommand(KernelOperation operation) - { - var pipelineContext = new KernelPipelineContext(PublishEvent); - - await Pipeline.SendAsync(operation.Command, pipelineContext); - - var result = await pipelineContext.InvokeAsync(); - - operation.TaskCompletionSource.SetResult(result); - } - protected void PublishEvent(IKernelEvent kernelEvent) { if (kernelEvent == null) @@ -280,11 +299,11 @@ protected void AddDisposable(IDisposable disposable) protected internal abstract Task HandleAsync( IKernelCommand command, - KernelPipelineContext context); + KernelInvocationContext context); protected virtual void SetKernel( IKernelCommand command, - KernelPipelineContext context) => context.Kernel = this; + KernelInvocationContext context) => context.Kernel = this; public void Dispose() => _disposables.Dispose(); } diff --git a/Microsoft.DotNet.Interactive/KernelCommandPipeline.cs b/Microsoft.DotNet.Interactive/KernelCommandPipeline.cs index 79f81dee0..2e28a6113 100644 --- a/Microsoft.DotNet.Interactive/KernelCommandPipeline.cs +++ b/Microsoft.DotNet.Interactive/KernelCommandPipeline.cs @@ -32,7 +32,7 @@ private void EnsureMiddlewarePipelineIsInitialized() public async Task SendAsync( IKernelCommand command, - KernelPipelineContext context) + KernelInvocationContext context) { EnsureMiddlewarePipelineIsInitialized(); diff --git a/Microsoft.DotNet.Interactive/KernelCommandPipelineMiddleware.cs b/Microsoft.DotNet.Interactive/KernelCommandPipelineMiddleware.cs index 3afc0e53b..7c66a417c 100644 --- a/Microsoft.DotNet.Interactive/KernelCommandPipelineMiddleware.cs +++ b/Microsoft.DotNet.Interactive/KernelCommandPipelineMiddleware.cs @@ -8,6 +8,6 @@ namespace Microsoft.DotNet.Interactive { public delegate Task KernelCommandPipelineMiddleware( IKernelCommand command, - KernelPipelineContext context, + KernelInvocationContext context, KernelPipelineContinuation next); } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/KernelExtensions.cs b/Microsoft.DotNet.Interactive/KernelExtensions.cs index ceb01334b..23147f7d7 100644 --- a/Microsoft.DotNet.Interactive/KernelExtensions.cs +++ b/Microsoft.DotNet.Interactive/KernelExtensions.cs @@ -38,7 +38,7 @@ public static T UseExtendDirective(this T kernel) extensionDllArg }; - extend.Handler = CommandHandler.Create((dll, pipelineContext) => + extend.Handler = CommandHandler.Create((dll, pipelineContext) => kernel.SendAsync(new LoadExtension(dll))); kernel.AddDirective(extend); diff --git a/Microsoft.DotNet.Interactive/KernelInvocationContext.cs b/Microsoft.DotNet.Interactive/KernelInvocationContext.cs index ef78f6e26..ea2dae07d 100644 --- a/Microsoft.DotNet.Interactive/KernelInvocationContext.cs +++ b/Microsoft.DotNet.Interactive/KernelInvocationContext.cs @@ -2,26 +2,34 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.Reactive.Subjects; +using System.Threading; using System.Threading.Tasks; +using Microsoft.DotNet.Interactive.Commands; using Microsoft.DotNet.Interactive.Events; namespace Microsoft.DotNet.Interactive { - public class KernelInvocationContext : IObserver + public class KernelInvocationContext : IObserver, IDisposable { + private readonly KernelInvocationContext _parentContext; + private static readonly AsyncLocal> _currentStack = new AsyncLocal>(); + private readonly KernelCommandInvocation _invocation; - private readonly Action _publishEvent; private readonly ReplaySubject _events = new ReplaySubject(); - public KernelInvocationContext( - KernelCommandInvocation invocation, - Action publishEvent) + private KernelInvocationContext( + IKernelCommand command, + KernelInvocationContext parentContext = null) { - _invocation = invocation; - _publishEvent = publishEvent; + _parentContext = parentContext; + Command = command; + _invocation = command.InvokeAsync; } + public IKernelCommand Command { get; } + public void OnCompleted() { _events.OnCompleted(); @@ -34,15 +42,56 @@ public void OnError(Exception exception) public void OnNext(IKernelEvent @event) { - _events.OnNext(@event); - _publishEvent(@event); + if (_parentContext != null) + { + _parentContext.OnNext(@event); + } + else + { + _events.OnNext(@event); + } } - internal IObservable KernelEvents => _events; + public IObservable KernelEvents => _events; - public async Task InvokeAsync() + public async Task InvokeAsync() { - await _invocation(this); + try + { + await _invocation(this); + } + catch (Exception exception) + { + OnError(exception); + } + + return new KernelCommandResult(KernelEvents); } + + public static KernelInvocationContext Establish(IKernelCommand command) + { + KernelInvocationContext parent = null; + + if (_currentStack.Value == null) + { + _currentStack.Value = new Stack(); + } + else + { + parent = Current; + } + + var context = new KernelInvocationContext(command, parent); + + _currentStack.Value.Push(context); + + return context; + } + + public static KernelInvocationContext Current => _currentStack?.Value?.Peek(); + + public IKernel Kernel { get; set; } + + void IDisposable.Dispose() => _currentStack?.Value?.Pop(); } } \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/KernelPipelineContext.cs b/Microsoft.DotNet.Interactive/KernelPipelineContext.cs deleted file mode 100644 index 056e01b59..000000000 --- a/Microsoft.DotNet.Interactive/KernelPipelineContext.cs +++ /dev/null @@ -1,66 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Reactive.Linq; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.DotNet.Interactive.Commands; -using Microsoft.DotNet.Interactive.Events; - -namespace Microsoft.DotNet.Interactive -{ - public class KernelPipelineContext - { - private readonly Action _publishEvent; - private readonly List _invocations = new List(); - - public KernelPipelineContext( - Action publishEvent, - IKernel kernel = null) - { - Kernel = kernel; - _publishEvent = publishEvent; - } - - public IKernel Kernel { get; internal set; } - - public void OnExecute(KernelCommandInvocation invocation) - { - if (invocation == null) - { - throw new ArgumentNullException(nameof(invocation)); - } - - _invocations.Add(new KernelInvocationContext( - invocation, - _publishEvent)); - } - - internal async Task InvokeAsync() - { - var invocationContexts = _invocations.ToArray(); - - var observable = invocationContexts.Select(i => i.KernelEvents).Merge(); - - try - { - foreach (var invocation in invocationContexts) - { - await invocation.InvokeAsync(); - } - } - catch (Exception exception) - { - // FIX: (InvokeAsync) - _publishEvent( - new ValueProduced( - exception, - new SubmitCode(""))); - } - - return new KernelCommandResult(observable); - } - } -} \ No newline at end of file diff --git a/Microsoft.DotNet.Interactive/KernelPipelineContinuation.cs b/Microsoft.DotNet.Interactive/KernelPipelineContinuation.cs index be9a6f5e0..f72484dad 100644 --- a/Microsoft.DotNet.Interactive/KernelPipelineContinuation.cs +++ b/Microsoft.DotNet.Interactive/KernelPipelineContinuation.cs @@ -8,5 +8,5 @@ namespace Microsoft.DotNet.Interactive { public delegate Task KernelPipelineContinuation( IKernelCommand command, - KernelPipelineContext context); + KernelInvocationContext context); } \ No newline at end of file diff --git a/WorkspaceServer.Tests/Kernel/CSharpKernelTestBase.cs b/WorkspaceServer.Tests/Kernel/CSharpKernelTestBase.cs index de6080e6c..cb199a61a 100644 --- a/WorkspaceServer.Tests/Kernel/CSharpKernelTestBase.cs +++ b/WorkspaceServer.Tests/Kernel/CSharpKernelTestBase.cs @@ -13,9 +13,9 @@ namespace WorkspaceServer.Tests.Kernel { - public class CSharpKernelTestBase : IDisposable + public abstract class CSharpKernelTestBase : IDisposable { - public CSharpKernelTestBase(ITestOutputHelper output) + protected CSharpKernelTestBase(ITestOutputHelper output) { DisposeAfterTest(output.SubscribeToPocketLogger()); } @@ -24,6 +24,9 @@ protected CSharpKernel CreateKernel() { var kernel = new CSharpKernel() .UseDefaultRendering() + .UseNugetDirective() + .UseExtendDirective() + .UseKernelHelpers() .LogEventsToPocketLogger(); DisposeAfterTest( diff --git a/WorkspaceServer.Tests/Kernel/CompositeKernelTests.cs b/WorkspaceServer.Tests/Kernel/CompositeKernelTests.cs index 8f5d2cef9..46396f1ad 100644 --- a/WorkspaceServer.Tests/Kernel/CompositeKernelTests.cs +++ b/WorkspaceServer.Tests/Kernel/CompositeKernelTests.cs @@ -93,9 +93,9 @@ public async Task Kernel_can_be_chosen_by_specifying_kernel_name() new CSharpKernel(), new FakeKernel("fake") { - Handle = (command, context) => + Handle = context => { - receivedOnFakeRepl.Add(command); + receivedOnFakeRepl.Add(context.Command); return Task.CompletedTask; } } @@ -108,25 +108,30 @@ public async Task Kernel_can_be_chosen_by_specifying_kernel_name() await kernel.SendAsync(new SubmitCode("#kernel csharp")); await kernel.SendAsync(new SubmitCode("x")); - receivedOnFakeRepl.Should() - .BeEquivalentTo(new SubmitCode("hello!")); + receivedOnFakeRepl + .Should() + .ContainSingle(c => c is SubmitCode && + c.As().Code == "hello!"); } - } - public class FakeKernel : KernelBase - { - public FakeKernel([CallerMemberName] string name = null) + public class FakeKernel : KernelBase { - Name = name; - } + public FakeKernel([CallerMemberName] string name = null) + { + Name = name; + } - public override string Name { get; } + public override string Name { get; } - public Func Handle { get; set; } + public KernelCommandInvocation Handle { get; set; } - protected override Task HandleAsync(IKernelCommand command, KernelPipelineContext context) - { - return Handle(command, context); + protected override Task HandleAsync( + IKernelCommand command, + KernelInvocationContext context) + { + command.As().Handler = Handle; + return Task.CompletedTask; + } } } } \ No newline at end of file diff --git a/WorkspaceServer.Tests/Kernel/KernelInvocationContextTests.cs b/WorkspaceServer.Tests/Kernel/KernelInvocationContextTests.cs new file mode 100644 index 000000000..df5942a03 --- /dev/null +++ b/WorkspaceServer.Tests/Kernel/KernelInvocationContextTests.cs @@ -0,0 +1,51 @@ +// 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; +using FluentAssertions; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.Interactive; +using Microsoft.DotNet.Interactive.Commands; +using Microsoft.DotNet.Interactive.Events; +using Xunit; + +namespace WorkspaceServer.Tests.Kernel +{ + public class KernelInvocationContextTests + { + [Fact] + public async Task Current_differs_per_async_context() + { + var barrier = new Barrier(2); + + IKernelCommand commandInTask1 = null; + + IKernelCommand commandInTask2 = null; + + await Task.Run(() => + { + using (var x = KernelInvocationContext.Establish(new SubmitCode(""))) + { + barrier.SignalAndWait(1000); + commandInTask1 = KernelInvocationContext.Current.Command; + } + }); + + await Task.Run(() => + { + using (KernelInvocationContext.Establish(new SubmitCode(""))) + { + barrier.SignalAndWait(1000); + commandInTask2 = KernelInvocationContext.Current.Command; + } + }); + + commandInTask1.Should() + .NotBe(commandInTask2) + .And + .NotBeNull(); + } + } +} \ No newline at end of file diff --git a/WorkspaceServer/Kernel/CSharpKernel.cs b/WorkspaceServer/Kernel/CSharpKernel.cs index 94402f43e..d7bb9e817 100644 --- a/WorkspaceServer/Kernel/CSharpKernel.cs +++ b/WorkspaceServer/Kernel/CSharpKernel.cs @@ -64,6 +64,8 @@ private void SetupScriptOptions() typeof(Enumerable).Assembly, typeof(IEnumerable<>).Assembly, typeof(Task<>).Assembly, + typeof(IKernel).Assembly, + typeof(CSharpKernel).Assembly, typeof(PocketView).Assembly); } @@ -85,77 +87,76 @@ private void SetupScriptOptions() protected override async Task HandleAsync( IKernelCommand command, - KernelPipelineContext context) + KernelInvocationContext context) { switch (command) { case SubmitCode submitCode: - context.OnExecute(async invocationContext => + submitCode.Handler = async invocationContext => { await HandleSubmitCode(submitCode, invocationContext); - }); + }; break; case RequestCompletion requestCompletion: - context.OnExecute(async invocationContext => + requestCompletion.Handler = async invocationContext => { await HandleRequestCompletion(requestCompletion, invocationContext, _scriptState); - }); + }; break; } } private async Task HandleSubmitCode( - SubmitCode codeSubmission, + SubmitCode submitCode, KernelInvocationContext context) { var codeSubmissionReceived = new CodeSubmissionReceived( - codeSubmission.Code, - codeSubmission); + submitCode.Code, + submitCode); context.OnNext(codeSubmissionReceived); - var (shouldExecute, code) = IsBufferACompleteSubmission(codeSubmission.Code); + var (shouldExecute, code) = IsBufferACompleteSubmission(submitCode.Code); if (shouldExecute) { - context.OnNext(new CompleteCodeSubmissionReceived(codeSubmission)); + context.OnNext(new CompleteCodeSubmissionReceived(submitCode)); Exception exception = null; - using (var console = await ConsoleOutput.Capture()) - { - console.SubscribeToStandardOutput(std => PublishOutput(std, context, codeSubmission)); - try + using var console = await ConsoleOutput.Capture(); + using var _ = console.SubscribeToStandardOutput(std => PublishOutput(std, context, submitCode)); + + try + { + if (_scriptState == null) { - if (_scriptState == null) - { - _scriptState = await CSharpScript.RunAsync( - code, - ScriptOptions); - } - else - { - _scriptState = await _scriptState.ContinueWithAsync( - code, - ScriptOptions, - e => - { - exception = e; - return true; - }); - } + _scriptState = await CSharpScript.RunAsync( + code, + ScriptOptions); } - catch (Exception e) + else { - exception = e; + _scriptState = await _scriptState.ContinueWithAsync( + code, + ScriptOptions, + e => + { + exception = e; + return true; + }); } } + catch (Exception e) + { + exception = e; + } if (exception != null) { var message = string.Join("\n", (_scriptState?.Script?.GetDiagnostics() ?? Enumerable.Empty()).Select(d => d.GetMessage())); - context.OnNext(new CodeSubmissionEvaluationFailed(exception, message, codeSubmission)); + context.OnNext(new CodeSubmissionEvaluationFailed(exception, message, submitCode)); context.OnError(exception); } else @@ -176,19 +177,20 @@ private async Task HandleSubmitCode( context.OnNext( new ValueProduced( _scriptState.ReturnValue, - codeSubmission, + submitCode, true, formattedValues)); } - context.OnNext(new CodeSubmissionEvaluated(codeSubmission)); + context.OnNext(new CodeSubmissionEvaluated(submitCode)); + context.OnCompleted(); } } else { - context.OnNext(new IncompleteCodeSubmissionReceived(codeSubmission)); - context.OnNext(new CodeSubmissionEvaluated(codeSubmission)); + context.OnNext(new IncompleteCodeSubmissionReceived(submitCode)); + context.OnNext(new CodeSubmissionEvaluated(submitCode)); context.OnCompleted(); } } diff --git a/WorkspaceServer/Kernel/CSharpKernelExtensions.cs b/WorkspaceServer/Kernel/CSharpKernelExtensions.cs index ade663b27..034372038 100644 --- a/WorkspaceServer/Kernel/CSharpKernelExtensions.cs +++ b/WorkspaceServer/Kernel/CSharpKernelExtensions.cs @@ -42,19 +42,24 @@ public static CSharpKernel UseNugetDirective(this CSharpKernel kernel) var restoreContext = new PackageRestoreContext(); - r.Handler = CommandHandler.Create(async (package, pipelineContext) => + r.Handler = CommandHandler.Create(async (package, pipelineContext) => { - pipelineContext.OnExecute(async invocationContext => + var addPackage = new AddNugetPackage(package) { - var refs = await restoreContext.AddPackage(package.PackageName, package.PackageVersion); - if (refs != null) + Handler = async context => { - kernel.AddMetatadaReferences(refs); + var refs = await restoreContext.AddPackage(package.PackageName, package.PackageVersion); + if (refs != null) + { + kernel.AddMetatadaReferences(refs); + } + + context.OnNext(new NuGetPackageAdded(package)); + context.OnCompleted(); } + }; - invocationContext.OnNext(new NuGetPackageAdded(package)); - invocationContext.OnCompleted(); - }); + await pipelineContext.Kernel.SendAsync(addPackage); }); kernel.AddDirective(r); diff --git a/WorkspaceServer/Servers/Roslyn/TrackingStringWriter.cs b/WorkspaceServer/Servers/Roslyn/TrackingStringWriter.cs index 45dc59e47..fa4c1b5c8 100644 --- a/WorkspaceServer/Servers/Roslyn/TrackingStringWriter.cs +++ b/WorkspaceServer/Servers/Roslyn/TrackingStringWriter.cs @@ -5,9 +5,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reactive.Concurrency; using System.Reactive.Disposables; -using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading; using System.Threading.Tasks; @@ -28,22 +26,11 @@ private class Region private int _observerCount; private readonly CompositeDisposable _disposable; - private readonly IObservable _scheduleEvents; public TrackingStringWriter() { - var scheduler = new EventLoopScheduler(t => - { - var thread = new Thread(t); - thread.IsBackground = true; - return thread; - }); - - _scheduleEvents = _writeEvents.ObserveOn(scheduler); - _disposable = new CompositeDisposable { - scheduler, _writeEvents }; } @@ -343,11 +330,10 @@ public IEnumerable Writes() public IDisposable Subscribe(IObserver observer) { Interlocked.Increment(ref _observerCount); - return new CompositeDisposable() + return new CompositeDisposable { - Disposable.Create( - () => Interlocked.Decrement(ref _observerCount)), - _scheduleEvents.Subscribe(observer) + Disposable.Create(() => Interlocked.Decrement(ref _observerCount)), + _writeEvents.Subscribe(observer) }; } } From 215aebf005ebd702d5bd024ec8afcea9bab74cec Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Wed, 7 Aug 2019 15:04:56 -0700 Subject: [PATCH 4/4] add Display helper --- MLS.Agent/CommandLine/CommandLineParser.cs | 3 ++- Microsoft.DotNet.Interactive/Kernel.cs | 27 +++++++++++++++++++ .../Kernel/CSharpKernelRenderingTests.cs | 20 ++++++++++++++ .../Kernel/CompositeKernelTests.cs | 5 ++++ .../Kernel/CSharpKernelExtensions.cs | 12 +++++++++ 5 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 Microsoft.DotNet.Interactive/Kernel.cs diff --git a/MLS.Agent/CommandLine/CommandLineParser.cs b/MLS.Agent/CommandLine/CommandLineParser.cs index c490e5ca8..0e0a43072 100644 --- a/MLS.Agent/CommandLine/CommandLineParser.cs +++ b/MLS.Agent/CommandLine/CommandLineParser.cs @@ -375,12 +375,13 @@ Command Jupyter() .Trace() .Handle(delivery)); }) - .AddTransient(c => new CompositeKernel + .AddSingleton(c => new CompositeKernel { new CSharpKernel() .UseDefaultRendering() .UseNugetDirective() .UseExtendDirective() + .UseKernelHelpers() }) .AddSingleton(c => new JupyterRequestContextHandler( c.GetRequiredService(), diff --git a/Microsoft.DotNet.Interactive/Kernel.cs b/Microsoft.DotNet.Interactive/Kernel.cs new file mode 100644 index 000000000..833936e92 --- /dev/null +++ b/Microsoft.DotNet.Interactive/Kernel.cs @@ -0,0 +1,27 @@ +// 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.Threading.Tasks; +using Microsoft.DotNet.Interactive.Commands; +using Microsoft.DotNet.Interactive.Rendering; + +namespace Microsoft.DotNet.Interactive +{ + public static class Kernel + { + public static void Display( + object value, + string mimeType = HtmlFormatter.MimeType) + { + var formatted = new FormattedValue( + mimeType, + value.ToDisplayString(mimeType)); + + var kernel = KernelInvocationContext.Current.Kernel; + + Task.Run(() => + kernel.SendAsync(new DisplayValue(formatted))) + .Wait(); + } + } +} \ No newline at end of file diff --git a/WorkspaceServer.Tests/Kernel/CSharpKernelRenderingTests.cs b/WorkspaceServer.Tests/Kernel/CSharpKernelRenderingTests.cs index d16b0891f..da16583f4 100644 --- a/WorkspaceServer.Tests/Kernel/CSharpKernelRenderingTests.cs +++ b/WorkspaceServer.Tests/Kernel/CSharpKernelRenderingTests.cs @@ -78,5 +78,25 @@ public async Task String_is_rendered_as_plain_text( v.MimeType == "text/plain" && v.Value.ToString().Contains(expectedContent)); } + + [Fact] + public async Task Display_helper_can_be_called_without_specifying_class_name() + { + var kernel = CreateKernel(); + + await kernel.SendAsync(new SubmitCode("Display(b(\"hi!\"));")); + + var formatted = + KernelEvents + .ValuesOnly() + .OfType() + .SelectMany(v => v.FormattedValues); + + formatted + .Should() + .ContainSingle(v => + v.MimeType == "text/html" && + v.Value.ToString().Contains("hi!")); + } } } \ No newline at end of file diff --git a/WorkspaceServer.Tests/Kernel/CompositeKernelTests.cs b/WorkspaceServer.Tests/Kernel/CompositeKernelTests.cs index 46396f1ad..724eb81c7 100644 --- a/WorkspaceServer.Tests/Kernel/CompositeKernelTests.cs +++ b/WorkspaceServer.Tests/Kernel/CompositeKernelTests.cs @@ -29,18 +29,22 @@ public CompositeKernelTests(ITestOutputHelper output) [Fact(Skip = "WIP")] public void When_SubmitCode_command_adds_packages_to_fsharp_kernel_then_the_submission_is_passed_to_fsi() { + // FIX: move to FSharpKernelTests throw new NotImplementedException(); } [Fact(Skip = "WIP")] public void When_SubmitCode_command_adds_packages_to_fsharp_kernel_then_PackageAdded_event_is_raised() { + // FIX: move to FSharpKernelTests throw new NotImplementedException(); } [Fact] public async Task When_SubmitCode_command_adds_packages_to_csharp_kernel_then_the_submission_is_not_passed_to_csharpScript() { + // FIX: move to CSharpKernelTests + var kernel = new CompositeKernel { new CSharpKernel().UseNugetDirective() @@ -55,6 +59,7 @@ public async Task When_SubmitCode_command_adds_packages_to_csharp_kernel_then_th [Fact] public async Task When_SubmitCode_command_adds_packages_to_csharp_kernel_then_PackageAdded_event_is_raised() { + // FIX: move to CSharpKernelTests var kernel = new CompositeKernel { new CSharpKernel().UseNugetDirective() diff --git a/WorkspaceServer/Kernel/CSharpKernelExtensions.cs b/WorkspaceServer/Kernel/CSharpKernelExtensions.cs index 034372038..684fea073 100644 --- a/WorkspaceServer/Kernel/CSharpKernelExtensions.cs +++ b/WorkspaceServer/Kernel/CSharpKernelExtensions.cs @@ -27,6 +27,18 @@ public static CSharpKernel UseDefaultRendering( return kernel; } + public static CSharpKernel UseKernelHelpers( + this CSharpKernel kernel) + { + Task.Run(() => + kernel.SendAsync( + new SubmitCode($@" +using static {typeof(Microsoft.DotNet.Interactive.Kernel).FullName}; +"))).Wait(); + + return kernel; + } + public static CSharpKernel UseNugetDirective(this CSharpKernel kernel) { var packageRefArg = new Argument((SymbolResult result, out NugetPackageReference reference) =>