From b58c75f9c4a9d6502af95d441358c58b32326c8f Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 3 Jul 2019 07:47:30 +0100 Subject: [PATCH 01/12] handling null in rendering --- .../RenderingTests.cs | 10 +++++++++- .../ExecuteRequestHandler.cs | 8 ++++---- .../Rendering/DefaultRenderer.cs | 19 ++++++++++++------- .../Rendering/RendererUtilities.cs | 4 ++-- WorkspaceServer/Kernel/RenderingEngine.cs | 6 ++++-- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/Microsoft.DotNet.Try.Jupyter.Tests/RenderingTests.cs b/Microsoft.DotNet.Try.Jupyter.Tests/RenderingTests.cs index 802a6b70c..3129e633f 100644 --- a/Microsoft.DotNet.Try.Jupyter.Tests/RenderingTests.cs +++ b/Microsoft.DotNet.Try.Jupyter.Tests/RenderingTests.cs @@ -23,13 +23,21 @@ private struct PriorityElement public RenderingTests() { - _engine = new RenderingEngine(new DefaultRenderer()); + _engine = new RenderingEngine(new DefaultRenderer(), new PlainTextRendering("")); _engine.RegisterRenderer(new DefaultRenderer()); _engine.RegisterRenderer(typeof(IDictionary), new DictionaryRenderer()); _engine.RegisterRenderer(typeof(IList), new ListRenderer()); _engine.RegisterRenderer(typeof(IEnumerable), new SequenceRenderer()); } + [Fact] + public void renders_null() + { + var rendering = _engine.Render(null); + rendering.Mime.Should().Be("text/plain"); + rendering.Content.Should().Be(""); + } + [Fact] public void objects_are_rendered_as_table() { diff --git a/Microsoft.DotNet.Try.Jupyter/ExecuteRequestHandler.cs b/Microsoft.DotNet.Try.Jupyter/ExecuteRequestHandler.cs index 1eacd097f..da799f343 100644 --- a/Microsoft.DotNet.Try.Jupyter/ExecuteRequestHandler.cs +++ b/Microsoft.DotNet.Try.Jupyter/ExecuteRequestHandler.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.Try.Jupyter.Protocol; @@ -42,8 +43,7 @@ public OpenRequest(JupyterRequestContext context, ExecuteRequest executeRequest, public ExecuteRequestHandler(IKernel kernel) { _kernel = kernel; - _renderingEngine = new RenderingEngine(new DefaultRenderer()); - _renderingEngine = new RenderingEngine(new DefaultRenderer()); + _renderingEngine = new RenderingEngine(new DefaultRenderer(), new PlainTextRendering("")); _renderingEngine.RegisterRenderer(new DefaultRenderer()); _renderingEngine.RegisterRenderer(typeof(IDictionary), new DictionaryRenderer()); _renderingEngine.RegisterRenderer(typeof(IList), new ListRenderer()); @@ -96,7 +96,7 @@ void IObserver.OnNext(IKernelEvent value) } } - private void OnCodeSubmissionEvaluatedFailed(CodeSubmissionEvaluationFailed codeSubmissionEvaluationFailed, ConcurrentDictionary openRequests) + private static void OnCodeSubmissionEvaluatedFailed(CodeSubmissionEvaluationFailed codeSubmissionEvaluationFailed, ConcurrentDictionary openRequests) { var openRequest = openRequests[codeSubmissionEvaluationFailed.ParentId]; @@ -122,7 +122,7 @@ private void OnCodeSubmissionEvaluatedFailed(CodeSubmissionEvaluationFailed code } // reply Error - var executeReplyPayload = new ExecuteReplyError(errorContent, executionCount: _executionCount); + var executeReplyPayload = new ExecuteReplyError(errorContent, executionCount: openRequest.ExecutionCount); // send to server var executeReply = Message.CreateResponse( diff --git a/Microsoft.DotNet.Try.Jupyter/Rendering/DefaultRenderer.cs b/Microsoft.DotNet.Try.Jupyter/Rendering/DefaultRenderer.cs index 15e90d3dc..f6aad9d9a 100644 --- a/Microsoft.DotNet.Try.Jupyter/Rendering/DefaultRenderer.cs +++ b/Microsoft.DotNet.Try.Jupyter/Rendering/DefaultRenderer.cs @@ -28,19 +28,24 @@ public IRendering Render(object source, IRenderingEngine engine = null) public IRendering RenderObject(object source, IRenderingEngine engine = null) { - - var rows = CreateRows(source, engine); - var table = $@" + try + { + var rows = CreateRows(source, engine); + var table = $@"
{rows}
"; - return new HtmlRendering(table); + return new HtmlRendering(table); + } + catch (Exception) + { + return new PlainTextRendering(source?.ToString()); + } } private string CreateRows(object source, IRenderingEngine engine) { - - var props = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + var props = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); var rows = new StringBuilder(); foreach (var propertyInfo in props) { @@ -51,7 +56,7 @@ private string CreateRows(object source, IRenderingEngine engine) rows.AppendLine(row); } - var fields = source.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + var fields = source.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance); foreach (var fieldInfo in fields) { diff --git a/Microsoft.DotNet.Try.Jupyter/Rendering/RendererUtilities.cs b/Microsoft.DotNet.Try.Jupyter/Rendering/RendererUtilities.cs index f19012385..a314fe6cd 100644 --- a/Microsoft.DotNet.Try.Jupyter/Rendering/RendererUtilities.cs +++ b/Microsoft.DotNet.Try.Jupyter/Rendering/RendererUtilities.cs @@ -46,8 +46,8 @@ public static IEnumerable GetAccessors(Type sourceType) { if (IsStructured(sourceType)) { - var props = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).OfType(); - var fields = sourceType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).OfType(); + var props = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OfType(); + var fields = sourceType.GetFields(BindingFlags.Public | BindingFlags.Instance).OfType(); return props.Concat(fields); } diff --git a/WorkspaceServer/Kernel/RenderingEngine.cs b/WorkspaceServer/Kernel/RenderingEngine.cs index 230e85378..efefad776 100644 --- a/WorkspaceServer/Kernel/RenderingEngine.cs +++ b/WorkspaceServer/Kernel/RenderingEngine.cs @@ -11,17 +11,19 @@ namespace WorkspaceServer.Kernel public class RenderingEngine : IRenderingEngine { private readonly IRenderer _defaultRenderer; + private readonly IRendering _nullRendering; private readonly Dictionary _rendererRegistry = new Dictionary(); - public RenderingEngine(IRenderer defaultRenderer) + public RenderingEngine(IRenderer defaultRenderer, IRendering nullRendering) { _defaultRenderer = defaultRenderer; + _nullRendering = nullRendering; } public IRendering Render(object source) { if (source == null) { - throw new ArgumentNullException(nameof(source)); + return _nullRendering; } var renderer = FindRenderer(source.GetType()); return renderer.Render(source, this); From 45e9e6b8074970f10d8454f083b9b62ca6bd2ead Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 3 Jul 2019 13:50:28 +0100 Subject: [PATCH 02/12] add support for linq --- .../Kernel/CSharpReplTests.cs | 3 +- WorkspaceServer.Tests/Kernel/Class1.cs | 45 +++++++++++++++++++ WorkspaceServer/Kernel/CSharpRepl.cs | 36 ++++++++++++++- .../Kernel/ICodeSubmissionPreProcessor.cs | 39 ++++++++++++++++ 4 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 WorkspaceServer.Tests/Kernel/Class1.cs create mode 100644 WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs diff --git a/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs b/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs index 17ee21358..73809edae 100644 --- a/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs +++ b/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs @@ -48,7 +48,8 @@ public async Task it_returns_exceptions_thrown_in_user_code() { var repl = await CreateKernelAsync(); - await repl.SendAsync(new SubmitCode("throw new System.NotImplementedException();")); + await repl.SendAsync(new SubmitCode("using System;")); + await repl.SendAsync(new SubmitCode("throw new NotImplementedException();")); KernelEvents.Last() .Should() diff --git a/WorkspaceServer.Tests/Kernel/Class1.cs b/WorkspaceServer.Tests/Kernel/Class1.cs new file mode 100644 index 000000000..4eca8edf6 --- /dev/null +++ b/WorkspaceServer.Tests/Kernel/Class1.cs @@ -0,0 +1,45 @@ +// 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.Threading.Tasks; +using FluentAssertions; +using WorkspaceServer.Kernel; +using Xunit; + +namespace WorkspaceServer.Tests.Kernel +{ + + public class CodeSubmissionPreProcessorTests + { + private readonly CodeSubmissionProcessors _processors; + + public CodeSubmissionPreProcessorTests() + { + _processors = new CodeSubmissionProcessors(); + } + [Fact] + public void can_register_processorHandlers() + { + var action = new Action(() => _processors.Register(new ReplaceAllProcessor())); + action.Should().NotThrow(); + _processors.ProcessorsCount.Should().BeGreaterThan(0); + throw new NotImplementedException(); + } + + [Fact] + public void processing_code_submission_removes_directive() + { + + throw new NotImplementedException(); + } + + class ReplaceAllProcessor : ICodeSubmissionProcessor + { + public Task ProcessAsync(SubmitCode codeSubmission) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/WorkspaceServer/Kernel/CSharpRepl.cs b/WorkspaceServer/Kernel/CSharpRepl.cs index fd992951e..56ba87e64 100644 --- a/WorkspaceServer/Kernel/CSharpRepl.cs +++ b/WorkspaceServer/Kernel/CSharpRepl.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Linq; using System.Reactive.Subjects; using System.Reflection; using System.Text; @@ -23,6 +25,8 @@ public class CSharpRepl : IKernel private ScriptState _scriptState; protected CSharpParseOptions ParseOptions = new CSharpParseOptions(LanguageVersion.Latest, kind: SourceCodeKind.Script); + protected ScriptOptions ScriptOptions; + protected StringBuilder _inputBuffer = new StringBuilder(); public IObservable KernelEvents => _channel; @@ -30,6 +34,23 @@ public class CSharpRepl : IKernel public CSharpRepl() { _channel = new Subject(); + SetupScriptOptions(); + } + + private void SetupScriptOptions() + { + ScriptOptions = ScriptOptions.Default + .AddImports( + "System", + "System.Text", + "System.Collections", + "System.Collections.Generic", + "System.Threading.Tasks", + "System.Linq") + .AddReferences( + typeof(Enumerable).GetTypeInfo().Assembly, + typeof(IEnumerable<>).GetTypeInfo().Assembly, + typeof(Task<>).GetTypeInfo().Assembly); } public async Task SendAsync(SubmitCode submitCode, CancellationToken cancellationToken) @@ -46,11 +67,22 @@ public async Task SendAsync(SubmitCode submitCode, CancellationToken cancellatio { if (_scriptState == null) { - _scriptState = await CSharpScript.RunAsync(code, cancellationToken: cancellationToken); + _scriptState = await CSharpScript.RunAsync( + code, + ScriptOptions, + cancellationToken: cancellationToken); } else { - _scriptState = await _scriptState.ContinueWithAsync(code, cancellationToken: cancellationToken); + _scriptState = await _scriptState.ContinueWithAsync( + code, + ScriptOptions, + e => + { + exception = e; + return true; + }, + cancellationToken); } } catch (Exception e) diff --git a/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs b/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs new file mode 100644 index 000000000..eaf53106a --- /dev/null +++ b/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs @@ -0,0 +1,39 @@ +// 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.Threading.Tasks; + +namespace WorkspaceServer.Kernel +{ + public interface ICodeSubmissionProcessor + { + Task ProcessAsync(SubmitCode codeSubmission); + } + + public class CodeSubmissionProcessors + { + public int ProcessorsCount { get; private set; } + public void Register(ICodeSubmissionProcessor processor) + { + throw new NotImplementedException(); + } + + public Task ProcessAsync(SubmitCode codeSubmission) + { + throw new NotImplementedException(); + } + } + + public class EmitProcessor : ICodeSubmissionProcessor + { + public EmitProcessor() + { + + } + public Task ProcessAsync(SubmitCode codeSubmission) + { + throw new NotImplementedException(); + } + } +} From d45f3bd8b0570785fa51f281cddc0e19134e886b Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 3 Jul 2019 14:06:40 +0100 Subject: [PATCH 03/12] test with generic collections and linq --- WorkspaceServer.Tests/Kernel/CSharpReplTests.cs | 7 ++++--- .../{Class1.cs => CodeSubmissionPreProcessorTests.cs} | 0 2 files changed, 4 insertions(+), 3 deletions(-) rename WorkspaceServer.Tests/Kernel/{Class1.cs => CodeSubmissionPreProcessorTests.cs} (100%) diff --git a/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs b/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs index 73809edae..15117159e 100644 --- a/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs +++ b/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs @@ -126,8 +126,9 @@ public async Task it_aggregates_multiple_submissions() { var repl = await CreateKernelAsync(); - await repl.SendAsync(new SubmitCode("var x = 123;")); - await repl.SendAsync(new SubmitCode("x")); + await repl.SendAsync(new SubmitCode("var x = new List{1,2};")); + await repl.SendAsync(new SubmitCode("x.Add(3);")); + await repl.SendAsync(new SubmitCode("x.Max()")); KernelEvents.OfType() .Last() @@ -136,7 +137,7 @@ public async Task it_aggregates_multiple_submissions() .Which .Value .Should() - .Be(123); + .Be(3); } } } \ No newline at end of file diff --git a/WorkspaceServer.Tests/Kernel/Class1.cs b/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs similarity index 100% rename from WorkspaceServer.Tests/Kernel/Class1.cs rename to WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs From d682f197c38b51b6ca417884bd23f0b1c0daee20 Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 3 Jul 2019 17:56:01 +0100 Subject: [PATCH 04/12] add processors tests --- .../Kernel/CodeSubmissionPreProcessorTests.cs | 43 +++++++++++++-- .../Kernel/ICodeSubmissionPreProcessor.cs | 54 ++++++++++++++----- WorkspaceServer/Kernel/IKernelEvent.cs | 10 ---- WorkspaceServer/Kernel/SubmitCode.cs | 22 ++++++++ 4 files changed, 102 insertions(+), 27 deletions(-) create mode 100644 WorkspaceServer/Kernel/SubmitCode.cs diff --git a/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs b/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs index 4eca8edf6..66d9c3ef9 100644 --- a/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs +++ b/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.CommandLine; using System.Threading.Tasks; using FluentAssertions; using WorkspaceServer.Kernel; @@ -24,21 +25,55 @@ public void can_register_processorHandlers() var action = new Action(() => _processors.Register(new ReplaceAllProcessor())); action.Should().NotThrow(); _processors.ProcessorsCount.Should().BeGreaterThan(0); - throw new NotImplementedException(); } [Fact] - public void processing_code_submission_removes_directive() + public async Task processing_code_submission_removes_processors() { + _processors.Register(new PassThroughAllProcessor()); + var submission = new SubmitCode("#pass\nthis should remain"); + submission = await _processors.ProcessAsync(submission); + submission.Value.Should().NotContain("#pass") + .And.Contain("this should remain"); + } - throw new NotImplementedException(); + [Fact] + public async Task processing_code_submission_leaves_unprocessed_directives() + { + _processors.Register(new PassThroughAllProcessor()); + var submission = new SubmitCode("#pass\n#region code\nthis should remain\n#endregion"); + submission = await _processors.ProcessAsync(submission); + submission.Value.Should().NotContain("#pass") + .And.Match("*#region code\nthis should remain\n#endregion*"); } class ReplaceAllProcessor : ICodeSubmissionProcessor { + public ReplaceAllProcessor() + { + Command = new Command("#replace", "replace submission with empty string"); + } + + public Command Command { get; } + + public Task ProcessAsync(SubmitCode codeSubmission) + { + return Task.FromResult(new SubmitCode(string.Empty, codeSubmission.Id, codeSubmission.ParentId)); + } + } + + class PassThroughAllProcessor : ICodeSubmissionProcessor + { + public PassThroughAllProcessor() + { + Command = new Command("#pass", "pass all code"); + } + + public Command Command { get; } + public Task ProcessAsync(SubmitCode codeSubmission) { - throw new NotImplementedException(); + return Task.FromResult(codeSubmission); } } } diff --git a/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs b/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs index eaf53106a..b08f061ef 100644 --- a/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs +++ b/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs @@ -2,6 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Builder; +using System.Text; using System.Threading.Tasks; namespace WorkspaceServer.Kernel @@ -9,31 +13,55 @@ namespace WorkspaceServer.Kernel public interface ICodeSubmissionProcessor { Task ProcessAsync(SubmitCode codeSubmission); + Command Command { get; } } public class CodeSubmissionProcessors { - public int ProcessorsCount { get; private set; } - public void Register(ICodeSubmissionProcessor processor) + private readonly RootCommand _rootCommand; + private readonly Dictionary _processors = new Dictionary(); + private Parser _parser; + + public int ProcessorsCount => _processors.Count; + + public CodeSubmissionProcessors() { - throw new NotImplementedException(); + _rootCommand = new RootCommand(); } - public Task ProcessAsync(SubmitCode codeSubmission) + + + public void Register(ICodeSubmissionProcessor processor) { - throw new NotImplementedException(); + _processors[processor.Command] = processor; + _rootCommand.AddCommand(processor.Command); + _parser = new CommandLineBuilder(_rootCommand).Build(); } - } - public class EmitProcessor : ICodeSubmissionProcessor - { - public EmitProcessor() + public async Task ProcessAsync(SubmitCode codeSubmission) { + var lines = new Queue( codeSubmission.Value.Split(new[] {"\r\n", "\n"}, StringSplitOptions.None)); + var unhandledLines = new Queue(); + while (lines.Count > 0) + { + var currentLine = lines.Dequeue(); + var result = _parser.Parse(currentLine); + + if (result.CommandResult != null && _processors.TryGetValue(result.CommandResult.Command, out var processor)) + { + var newSubmission = await processor.ProcessAsync(new SubmitCode(string.Join("\n", lines), codeSubmission.Id, codeSubmission.ParentId)); + lines = new Queue(newSubmission.Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None)); + } + else + { + unhandledLines.Enqueue(currentLine); + } + } + + return new SubmitCode(string.Join("\n", unhandledLines), codeSubmission.Id, codeSubmission.ParentId); } - public Task ProcessAsync(SubmitCode codeSubmission) - { - throw new NotImplementedException(); - } } + + } diff --git a/WorkspaceServer/Kernel/IKernelEvent.cs b/WorkspaceServer/Kernel/IKernelEvent.cs index dfff438ce..06cf4275a 100644 --- a/WorkspaceServer/Kernel/IKernelEvent.cs +++ b/WorkspaceServer/Kernel/IKernelEvent.cs @@ -50,16 +50,6 @@ public class AddPackage : KernelCommandBase { } - public class SubmitCode : KernelCommandBase - { - public string Value { get; } - - public SubmitCode(string value) : base() - { - Value = value ?? throw new ArgumentNullException(nameof(value)); - } - } - public class RequestCompletion : KernelCommandBase { } diff --git a/WorkspaceServer/Kernel/SubmitCode.cs b/WorkspaceServer/Kernel/SubmitCode.cs new file mode 100644 index 000000000..5a17e4974 --- /dev/null +++ b/WorkspaceServer/Kernel/SubmitCode.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. + +using System; + +namespace WorkspaceServer.Kernel +{ + public class SubmitCode : KernelCommandBase + { + public string Value { get; } + + public SubmitCode(string value) + { + Value = value ?? throw new ArgumentNullException(nameof(value)); + } + + public SubmitCode(string value, Guid id, Guid parentId) : base(id, parentId) + { + Value = value ?? throw new ArgumentNullException(nameof(value)); + } + } +} \ No newline at end of file From cc2b15057d5d2dc300f97d7f4c5b4e0e246dcc0f Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 3 Jul 2019 18:00:39 +0100 Subject: [PATCH 05/12] clean up --- .../Kernel/CodeSubmissionPreProcessorTests.cs | 7 +++---- WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs | 7 ------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs b/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs index 66d9c3ef9..ffc86b383 100644 --- a/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs +++ b/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs @@ -9,8 +9,7 @@ using Xunit; namespace WorkspaceServer.Tests.Kernel -{ - +{ public class CodeSubmissionPreProcessorTests { private readonly CodeSubmissionProcessors _processors; @@ -47,7 +46,7 @@ public async Task processing_code_submission_leaves_unprocessed_directives() .And.Match("*#region code\nthis should remain\n#endregion*"); } - class ReplaceAllProcessor : ICodeSubmissionProcessor + private class ReplaceAllProcessor : ICodeSubmissionProcessor { public ReplaceAllProcessor() { @@ -62,7 +61,7 @@ public Task ProcessAsync(SubmitCode codeSubmission) } } - class PassThroughAllProcessor : ICodeSubmissionProcessor + private class PassThroughAllProcessor : ICodeSubmissionProcessor { public PassThroughAllProcessor() { diff --git a/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs b/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs index b08f061ef..730623e19 100644 --- a/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs +++ b/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Builder; -using System.Text; using System.Threading.Tasks; namespace WorkspaceServer.Kernel @@ -29,8 +28,6 @@ public CodeSubmissionProcessors() _rootCommand = new RootCommand(); } - - public void Register(ICodeSubmissionProcessor processor) { _processors[processor.Command] = processor; @@ -57,11 +54,7 @@ public async Task ProcessAsync(SubmitCode codeSubmission) unhandledLines.Enqueue(currentLine); } } - return new SubmitCode(string.Join("\n", unhandledLines), codeSubmission.Id, codeSubmission.ParentId); - } } - - } From 5e43f8ae0f19a205392b084f2f481de54a1c6265 Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 3 Jul 2019 18:02:03 +0100 Subject: [PATCH 06/12] split files --- .../Kernel/CodeSubmissionProcessors.cs | 54 +++++++++++++++++++ .../Kernel/ICodeSubmissionPreProcessor.cs | 46 ---------------- 2 files changed, 54 insertions(+), 46 deletions(-) create mode 100644 WorkspaceServer/Kernel/CodeSubmissionProcessors.cs diff --git a/WorkspaceServer/Kernel/CodeSubmissionProcessors.cs b/WorkspaceServer/Kernel/CodeSubmissionProcessors.cs new file mode 100644 index 000000000..e6f75260a --- /dev/null +++ b/WorkspaceServer/Kernel/CodeSubmissionProcessors.cs @@ -0,0 +1,54 @@ +// 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.CommandLine; +using System.CommandLine.Builder; +using System.Threading.Tasks; + +namespace WorkspaceServer.Kernel +{ + public class CodeSubmissionProcessors + { + private readonly RootCommand _rootCommand; + private readonly Dictionary _processors = new Dictionary(); + private Parser _parser; + + public int ProcessorsCount => _processors.Count; + + public CodeSubmissionProcessors() + { + _rootCommand = new RootCommand(); + } + + public void Register(ICodeSubmissionProcessor processor) + { + _processors[processor.Command] = processor; + _rootCommand.AddCommand(processor.Command); + _parser = new CommandLineBuilder(_rootCommand).Build(); + } + + public async Task ProcessAsync(SubmitCode codeSubmission) + { + var lines = new Queue( codeSubmission.Value.Split(new[] {"\r\n", "\n"}, StringSplitOptions.None)); + var unhandledLines = new Queue(); + while (lines.Count > 0) + { + var currentLine = lines.Dequeue(); + var result = _parser.Parse(currentLine); + + if (result.CommandResult != null && _processors.TryGetValue(result.CommandResult.Command, out var processor)) + { + var newSubmission = await processor.ProcessAsync(new SubmitCode(string.Join("\n", lines), codeSubmission.Id, codeSubmission.ParentId)); + lines = new Queue(newSubmission.Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None)); + } + else + { + unhandledLines.Enqueue(currentLine); + } + } + return new SubmitCode(string.Join("\n", unhandledLines), codeSubmission.Id, codeSubmission.ParentId); + } + } +} \ No newline at end of file diff --git a/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs b/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs index 730623e19..bb7b1e4f1 100644 --- a/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs +++ b/WorkspaceServer/Kernel/ICodeSubmissionPreProcessor.cs @@ -1,10 +1,7 @@ // 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.CommandLine; -using System.CommandLine.Builder; using System.Threading.Tasks; namespace WorkspaceServer.Kernel @@ -14,47 +11,4 @@ public interface ICodeSubmissionProcessor Task ProcessAsync(SubmitCode codeSubmission); Command Command { get; } } - - public class CodeSubmissionProcessors - { - private readonly RootCommand _rootCommand; - private readonly Dictionary _processors = new Dictionary(); - private Parser _parser; - - public int ProcessorsCount => _processors.Count; - - public CodeSubmissionProcessors() - { - _rootCommand = new RootCommand(); - } - - public void Register(ICodeSubmissionProcessor processor) - { - _processors[processor.Command] = processor; - _rootCommand.AddCommand(processor.Command); - _parser = new CommandLineBuilder(_rootCommand).Build(); - } - - public async Task ProcessAsync(SubmitCode codeSubmission) - { - var lines = new Queue( codeSubmission.Value.Split(new[] {"\r\n", "\n"}, StringSplitOptions.None)); - var unhandledLines = new Queue(); - while (lines.Count > 0) - { - var currentLine = lines.Dequeue(); - var result = _parser.Parse(currentLine); - - if (result.CommandResult != null && _processors.TryGetValue(result.CommandResult.Command, out var processor)) - { - var newSubmission = await processor.ProcessAsync(new SubmitCode(string.Join("\n", lines), codeSubmission.Id, codeSubmission.ParentId)); - lines = new Queue(newSubmission.Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None)); - } - else - { - unhandledLines.Enqueue(currentLine); - } - } - return new SubmitCode(string.Join("\n", unhandledLines), codeSubmission.Id, codeSubmission.ParentId); - } - } } From 7f2075d9a2b3fc391085796677ed111904cb16b9 Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 3 Jul 2019 20:32:38 +0100 Subject: [PATCH 07/12] better error messages --- MLS.Agent/Properties/launchSettings.json | 4 ++ .../ExecuteRequestHandler.cs | 2 +- .../Kernel/CSharpReplTests.cs | 17 ++++++++ WorkspaceServer/Kernel/CSharpRepl.cs | 42 ++++++++++++++++++- .../Kernel/CodeSubmissionEvaluationFailed.cs | 6 ++- 5 files changed, 68 insertions(+), 3 deletions(-) diff --git a/MLS.Agent/Properties/launchSettings.json b/MLS.Agent/Properties/launchSettings.json index 3135a219e..aec9d512a 100644 --- a/MLS.Agent/Properties/launchSettings.json +++ b/MLS.Agent/Properties/launchSettings.json @@ -31,6 +31,10 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "Jupyter": { + "commandName": "Project", + "commandLineArgs": "try jupyter ../MLS.Agent.Tests/kernel_connection_file.json" } } } \ No newline at end of file diff --git a/Microsoft.DotNet.Try.Jupyter/ExecuteRequestHandler.cs b/Microsoft.DotNet.Try.Jupyter/ExecuteRequestHandler.cs index da799f343..ee46fb38b 100644 --- a/Microsoft.DotNet.Try.Jupyter/ExecuteRequestHandler.cs +++ b/Microsoft.DotNet.Try.Jupyter/ExecuteRequestHandler.cs @@ -102,7 +102,7 @@ private static void OnCodeSubmissionEvaluatedFailed(CodeSubmissionEvaluationFail var errorContent = new Error( eName: "Unhandled Exception", - eValue: $"{codeSubmissionEvaluationFailed.Error}" + eValue: $"{codeSubmissionEvaluationFailed.Message}" ); if (!openRequest.ExecuteRequest.Silent) diff --git a/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs b/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs index 15117159e..ace30d0ed 100644 --- a/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs +++ b/WorkspaceServer.Tests/Kernel/CSharpReplTests.cs @@ -60,6 +60,23 @@ public async Task it_returns_exceptions_thrown_in_user_code() .BeOfType(); } + [Fact] + public async Task it_returns_diagnostics() + { + var repl = await CreateKernelAsync(); + + await repl.SendAsync(new SubmitCode("using System;")); + await repl.SendAsync(new SubmitCode("aaaadd")); + + KernelEvents.Last() + .Should() + .BeOfType() + .Which + .Message + .Should() + .Be("(1,1): error CS0103: The name 'aaaadd' does not exist in the current context"); + } + [Fact] public async Task it_notifies_when_submission_is_complete() { diff --git a/WorkspaceServer/Kernel/CSharpRepl.cs b/WorkspaceServer/Kernel/CSharpRepl.cs index 56ba87e64..c8f3a30e4 100644 --- a/WorkspaceServer/Kernel/CSharpRepl.cs +++ b/WorkspaceServer/Kernel/CSharpRepl.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; +using Microsoft.CodeAnalysis.VisualBasic.Syntax; namespace WorkspaceServer.Kernel { @@ -98,7 +99,17 @@ public async Task SendAsync(SubmitCode submitCode, CancellationToken cancellatio } if (exception != null) { - _channel.OnNext(new CodeSubmissionEvaluationFailed(submitCode.Id, exception)); + var diagnostics = _scriptState?.Script?.GetDiagnostics() ?? Enumerable.Empty(); + if (diagnostics.Any()) + { + var message = string.Join("\n", diagnostics.Select(d => d.GetMessage())); + + _channel.OnNext(new CodeSubmissionEvaluationFailed(submitCode.Id, exception, message)); + } + else + { + _channel.OnNext(new CodeSubmissionEvaluationFailed(submitCode.Id, exception)); + } } else { @@ -144,4 +155,33 @@ public Task SendAsync(IKernelCommand command, CancellationToken cancellationToke } } } + + internal static class EnumerableExtensions + { + public static IOrderedEnumerable OrderBy(this IEnumerable source, Comparison compare) + { + var comparer = Comparer.Create(compare); + return source.OrderBy(t => t, comparer); + } + } + + internal static class ScriptExtensions + { + public static IEnumerable GetDiagnostics(this Script script) + { + if(script == null) + { + return Enumerable.Empty(); + } + + var compilation = script.GetCompilation(); + var orderedDiagnostics = compilation.GetDiagnostics().OrderBy((d1, d2) => + { + var severityDiff = (int)d2.Severity - (int)d1.Severity; + return severityDiff != 0 ? severityDiff : d1.Location.SourceSpan.Start - d2.Location.SourceSpan.Start; + }); + + return orderedDiagnostics; + } + } } \ No newline at end of file diff --git a/WorkspaceServer/Kernel/CodeSubmissionEvaluationFailed.cs b/WorkspaceServer/Kernel/CodeSubmissionEvaluationFailed.cs index e19a0c871..ef9b1a2a0 100644 --- a/WorkspaceServer/Kernel/CodeSubmissionEvaluationFailed.cs +++ b/WorkspaceServer/Kernel/CodeSubmissionEvaluationFailed.cs @@ -9,10 +9,14 @@ namespace WorkspaceServer.Kernel public class CodeSubmissionEvaluationFailed : KernelEventBase { public object Error { get; } + public string Message { get; } - public CodeSubmissionEvaluationFailed(Guid parentId, object error): base(parentId) + public CodeSubmissionEvaluationFailed(Guid parentId, object error, string message = null): base(parentId) { Error = error; + Message = string.IsNullOrWhiteSpace(message) + ? error is Exception exception ? exception.Message : error.ToString() + : message; } } } \ No newline at end of file From 34cb65a1ddcbe87b245bfdf6f09f57f81c4a9e4b Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 3 Jul 2019 20:35:45 +0100 Subject: [PATCH 08/12] refactor --- WorkspaceServer/Kernel/CSharpRepl.cs | 30 ------------------- .../Kernel/EnumerableExtensions.cs | 18 +++++++++++ WorkspaceServer/Kernel/ScriptExtensions.cs | 30 +++++++++++++++++++ 3 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 WorkspaceServer/Kernel/EnumerableExtensions.cs create mode 100644 WorkspaceServer/Kernel/ScriptExtensions.cs diff --git a/WorkspaceServer/Kernel/CSharpRepl.cs b/WorkspaceServer/Kernel/CSharpRepl.cs index c8f3a30e4..e11cce6f7 100644 --- a/WorkspaceServer/Kernel/CSharpRepl.cs +++ b/WorkspaceServer/Kernel/CSharpRepl.cs @@ -13,7 +13,6 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Scripting; using Microsoft.CodeAnalysis.Scripting; -using Microsoft.CodeAnalysis.VisualBasic.Syntax; namespace WorkspaceServer.Kernel { @@ -155,33 +154,4 @@ public Task SendAsync(IKernelCommand command, CancellationToken cancellationToke } } } - - internal static class EnumerableExtensions - { - public static IOrderedEnumerable OrderBy(this IEnumerable source, Comparison compare) - { - var comparer = Comparer.Create(compare); - return source.OrderBy(t => t, comparer); - } - } - - internal static class ScriptExtensions - { - public static IEnumerable GetDiagnostics(this Script script) - { - if(script == null) - { - return Enumerable.Empty(); - } - - var compilation = script.GetCompilation(); - var orderedDiagnostics = compilation.GetDiagnostics().OrderBy((d1, d2) => - { - var severityDiff = (int)d2.Severity - (int)d1.Severity; - return severityDiff != 0 ? severityDiff : d1.Location.SourceSpan.Start - d2.Location.SourceSpan.Start; - }); - - return orderedDiagnostics; - } - } } \ No newline at end of file diff --git a/WorkspaceServer/Kernel/EnumerableExtensions.cs b/WorkspaceServer/Kernel/EnumerableExtensions.cs new file mode 100644 index 000000000..19bf7291a --- /dev/null +++ b/WorkspaceServer/Kernel/EnumerableExtensions.cs @@ -0,0 +1,18 @@ +// 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.Linq; + +namespace WorkspaceServer.Kernel +{ + internal static class EnumerableExtensions + { + public static IOrderedEnumerable OrderBy(this IEnumerable source, Comparison compare) + { + var comparer = Comparer.Create(compare); + return source.OrderBy(t => t, comparer); + } + } +} \ No newline at end of file diff --git a/WorkspaceServer/Kernel/ScriptExtensions.cs b/WorkspaceServer/Kernel/ScriptExtensions.cs new file mode 100644 index 000000000..7653c6c2c --- /dev/null +++ b/WorkspaceServer/Kernel/ScriptExtensions.cs @@ -0,0 +1,30 @@ +// 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.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Scripting; + +namespace WorkspaceServer.Kernel +{ + internal static class ScriptExtensions + { + public static IEnumerable GetDiagnostics(this Script script) + { + if(script == null) + { + return Enumerable.Empty(); + } + + var compilation = script.GetCompilation(); + var orderedDiagnostics = compilation.GetDiagnostics().OrderBy((d1, d2) => + { + var severityDiff = (int)d2.Severity - (int)d1.Severity; + return severityDiff != 0 ? severityDiff : d1.Location.SourceSpan.Start - d2.Location.SourceSpan.Start; + }); + + return orderedDiagnostics; + } + } +} \ No newline at end of file From 6189c0a3b14b677b5363d1b9015a03022c4ceeaf Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 3 Jul 2019 23:32:42 +0100 Subject: [PATCH 09/12] fixed bug in asp.net (@rchande 's magic) --- MLS.Agent/Properties/launchSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MLS.Agent/Properties/launchSettings.json b/MLS.Agent/Properties/launchSettings.json index aec9d512a..69def8824 100644 --- a/MLS.Agent/Properties/launchSettings.json +++ b/MLS.Agent/Properties/launchSettings.json @@ -34,7 +34,7 @@ }, "Jupyter": { "commandName": "Project", - "commandLineArgs": "try jupyter ../MLS.Agent.Tests/kernel_connection_file.json" + "commandLineArgs": "jupyter ../MLS.Agent.Tests/kernel_connection_file.json" } } } \ No newline at end of file From 10c01d9ac42dda4551b6ed08cd488af5c8c10d97 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 4 Jul 2019 01:08:22 +0100 Subject: [PATCH 10/12] inconsistent names fix --- ...onPreProcessorTests.cs => CodeSubmissionProcessorTests.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename WorkspaceServer.Tests/Kernel/{CodeSubmissionPreProcessorTests.cs => CodeSubmissionProcessorTests.cs} (96%) diff --git a/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs b/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs similarity index 96% rename from WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs rename to WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs index ffc86b383..c22e778aa 100644 --- a/WorkspaceServer.Tests/Kernel/CodeSubmissionPreProcessorTests.cs +++ b/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs @@ -10,11 +10,11 @@ namespace WorkspaceServer.Tests.Kernel { - public class CodeSubmissionPreProcessorTests + public class CodeSubmissionProcessorTests { private readonly CodeSubmissionProcessors _processors; - public CodeSubmissionPreProcessorTests() + public CodeSubmissionProcessorTests() { _processors = new CodeSubmissionProcessors(); } From 24dc969b88b91341e3b1a613b248edef76694d91 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 4 Jul 2019 01:42:16 +0100 Subject: [PATCH 11/12] processor with options --- .../Kernel/CodeSubmissionProcessorTests.cs | 48 +++++++++++++++++++ .../Kernel/CodeSubmissionProcessors.cs | 2 + 2 files changed, 50 insertions(+) diff --git a/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs b/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs index c22e778aa..a3c0ca2d1 100644 --- a/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs +++ b/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs @@ -3,6 +3,7 @@ using System; using System.CommandLine; +using System.CommandLine.Invocation; using System.Threading.Tasks; using FluentAssertions; using WorkspaceServer.Kernel; @@ -46,6 +47,17 @@ public async Task processing_code_submission_leaves_unprocessed_directives() .And.Match("*#region code\nthis should remain\n#endregion*"); } + + [Fact] + public async Task processing_code_submission_respect_directive_order() + { + _processors.Register(new AppendProcessor()); + var submission = new SubmitCode("#append --value PART1\n#append --value PART2\n#region code\nthis should remain\n#endregion"); + submission = await _processors.ProcessAsync(submission); + submission.Value.Should().NotContain("#pass") + .And.Match("*#region code\nthis should remain\n#endregion\nPART1\nPART2*"); + } + private class ReplaceAllProcessor : ICodeSubmissionProcessor { public ReplaceAllProcessor() @@ -75,5 +87,41 @@ public Task ProcessAsync(SubmitCode codeSubmission) return Task.FromResult(codeSubmission); } } + public class AppendProcessorOptions + { + public string Value { get; } + + public AppendProcessorOptions(string value) + { + Value = value; + } + } + + private class AppendProcessor : ICodeSubmissionProcessor + { + private string _valueToAppend; + + public AppendProcessor() + { + Command = new Command("#append"); + var valueOption = new Option("--value") + { + Argument = new Argument() + }; + Command.AddOption(valueOption); + + Command.Handler = CommandHandler.Create((options) => + { + _valueToAppend = options.Value; + }); + } + + public Command Command { get; } + + public Task ProcessAsync(SubmitCode codeSubmission) + { + return Task.FromResult(new SubmitCode(codeSubmission.Value + $"\n{_valueToAppend}" , codeSubmission.Id, codeSubmission.ParentId)); + } + } } } diff --git a/WorkspaceServer/Kernel/CodeSubmissionProcessors.cs b/WorkspaceServer/Kernel/CodeSubmissionProcessors.cs index e6f75260a..55b157bd5 100644 --- a/WorkspaceServer/Kernel/CodeSubmissionProcessors.cs +++ b/WorkspaceServer/Kernel/CodeSubmissionProcessors.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.CommandLine; using System.CommandLine.Builder; +using System.CommandLine.Invocation; using System.Threading.Tasks; namespace WorkspaceServer.Kernel @@ -40,6 +41,7 @@ public async Task ProcessAsync(SubmitCode codeSubmission) if (result.CommandResult != null && _processors.TryGetValue(result.CommandResult.Command, out var processor)) { + await _parser.InvokeAsync(result); var newSubmission = await processor.ProcessAsync(new SubmitCode(string.Join("\n", lines), codeSubmission.Id, codeSubmission.ParentId)); lines = new Queue(newSubmission.Value.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None)); } From f9e153a0bf960827e456271589f67e863486bbf0 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 4 Jul 2019 01:44:09 +0100 Subject: [PATCH 12/12] test refactor --- .../Kernel/CodeSubmissionProcessorTests.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs b/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs index a3c0ca2d1..91b7fa5bc 100644 --- a/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs +++ b/WorkspaceServer.Tests/Kernel/CodeSubmissionProcessorTests.cs @@ -87,20 +87,22 @@ public Task ProcessAsync(SubmitCode codeSubmission) return Task.FromResult(codeSubmission); } } - public class AppendProcessorOptions + + + private class AppendProcessor : ICodeSubmissionProcessor { - public string Value { get; } + private string _valueToAppend; - public AppendProcessorOptions(string value) + private class AppendProcessorOptions { - Value = value; + public string Value { get; } + + public AppendProcessorOptions(string value) + { + Value = value; + } } - } - private class AppendProcessor : ICodeSubmissionProcessor - { - private string _valueToAppend; - public AppendProcessor() { Command = new Command("#append");