From 4be9052731ad1b84a3bce213acbdc1dad8076a4a Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Mon, 21 Nov 2022 20:23:16 +0000 Subject: [PATCH 01/14] try to upgrade to csharp11 --- src/Microsoft.DotNet.Interactive.CSharp/CSharpKernel.cs | 4 ++-- .../CSharpProjectKernel.cs | 4 ++-- .../Servers/Roslyn/RoslynWorkspaceServer.cs | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.DotNet.Interactive.CSharp/CSharpKernel.cs b/src/Microsoft.DotNet.Interactive.CSharp/CSharpKernel.cs index f32970791e..fedf88fe44 100644 --- a/src/Microsoft.DotNet.Interactive.CSharp/CSharpKernel.cs +++ b/src/Microsoft.DotNet.Interactive.CSharp/CSharpKernel.cs @@ -59,7 +59,7 @@ public CSharpKernel() : this(DefaultKernelName) { } - public CSharpKernel(string name) : base(name, "C#", "10.0") + public CSharpKernel(string name) : base(name, "C#", "11.0") { _workspace = new InteractiveWorkspace(); @@ -67,7 +67,7 @@ public CSharpKernel(string name) : base(name, "C#", "10.0") //...so we wait for RunAsync to read Directory.GetCurrentDirectory() the first time. _scriptOptions = ScriptOptions.Default - .WithLanguageVersion(LanguageVersion.Latest) + .WithLanguageVersion(LanguageVersion.CSharp11) .AddImports( "System", "System.Text", diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/CSharpProjectKernel.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/CSharpProjectKernel.cs index 71e68003c3..3f1a142abf 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/CSharpProjectKernel.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/CSharpProjectKernel.cs @@ -55,7 +55,7 @@ public static void RegisterEventsAndCommands() } } - public CSharpProjectKernel(string name = "csharp") : base(name, "C#") + public CSharpProjectKernel(string name = "csharp") : base(name, "C#", languageVersion:"11.0") { } @@ -297,7 +297,7 @@ private static async Task CreateConsoleWorkspacePackage() { var packageBuilder = new PackageBuilder("console"); packageBuilder.CreateUsingDotnet("console"); - packageBuilder.TrySetLanguageVersion("8.0"); + packageBuilder.TrySetLanguageVersion("11.0"); packageBuilder.AddPackageReference("Newtonsoft.Json", "13.0.1"); var package = packageBuilder.GetPackage() as Package; await package!.CreateWorkspaceForRunAsync(); diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/RoslynWorkspaceServer.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/RoslynWorkspaceServer.cs index dad799e55f..088aff024e 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/RoslynWorkspaceServer.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Servers/Roslyn/RoslynWorkspaceServer.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Recommendations; using Microsoft.DotNet.Interactive.Utility; From 2c6b8efe74d9c299d5ebf2a3e0901796f36c17cc Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Mon, 21 Nov 2022 22:13:50 +0000 Subject: [PATCH 02/14] add test to check cs11 support --- .../CSharpKernelTests.cs | 85 ++++++++++++------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/src/Microsoft.DotNet.Interactive.CSharp.Tests/CSharpKernelTests.cs b/src/Microsoft.DotNet.Interactive.CSharp.Tests/CSharpKernelTests.cs index a8199c2305..c4d30e454a 100644 --- a/src/Microsoft.DotNet.Interactive.CSharp.Tests/CSharpKernelTests.cs +++ b/src/Microsoft.DotNet.Interactive.CSharp.Tests/CSharpKernelTests.cs @@ -1,60 +1,81 @@ // 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; using Xunit.Abstractions; -namespace Microsoft.DotNet.Interactive.CSharp.Tests +namespace Microsoft.DotNet.Interactive.CSharp.Tests; + +public class CSharpKernelTests : LanguageKernelTestBase { - public class CSharpKernelTests : LanguageKernelTestBase + public CSharpKernelTests(ITestOutputHelper output) : base(output) { - public CSharpKernelTests(ITestOutputHelper output) : base(output) - { - } + } - [Fact] - public async Task Script_state_is_available_within_middleware_pipeline() + [Fact] + public async Task Script_state_is_available_within_middleware_pipeline() + { + var variableCountBeforeEvaluation = 0; + var variableCountAfterEvaluation = 0; + + using var kernel = new CSharpKernel(); + + kernel.AddMiddleware(async (command, context, next) => { - var variableCountBeforeEvaluation = 0; - var variableCountAfterEvaluation = 0; + var k = context.HandlingKernel as CSharpKernel; + + await next(command, context); - using var kernel = new CSharpKernel(); + variableCountAfterEvaluation = k.ScriptState.Variables.Length; + }); - kernel.AddMiddleware(async (command, context, next) => - { - var k = context.HandlingKernel as CSharpKernel; + await kernel.SendAsync(new SubmitCode("var x = 1;")); - await next(command, context); + variableCountBeforeEvaluation.Should().Be(0); + variableCountAfterEvaluation.Should().Be(1); + } - variableCountAfterEvaluation = k.ScriptState.Variables.Length; - }); + [Fact] + public async Task GetValueInfos_only_returns_non_shadowed_values() + { + using var kernel = new CSharpKernel(); - await kernel.SendAsync(new SubmitCode("var x = 1;")); + await kernel.SendAsync(new SubmitCode("var x = 1;")); + await kernel.SendAsync(new SubmitCode("var x = \"two\";")); - variableCountBeforeEvaluation.Should().Be(0); - variableCountAfterEvaluation.Should().Be(1); - } + var (success, valueInfosProduced) = await kernel.TryRequestValueInfosAsync(); - [Fact] - public async Task GetValueInfos_only_returns_non_shadowed_values() - { - using var kernel = new CSharpKernel(); + success.Should().BeTrue(); - await kernel.SendAsync(new SubmitCode("var x = 1;")); - await kernel.SendAsync(new SubmitCode("var x = \"two\";")); + valueInfosProduced.ValueInfos + .Should() + .ContainSingle(v => v.Name == "x"); + } - var (success, valueInfosProduced) = await kernel.TryRequestValueInfosAsync(); + [Fact] + public async Task Supports_csharp_11() + { + using var kernel = new CSharpKernel(); + + var events = kernel.KernelEvents.ToSubscribedList(); - success.Should().BeTrue(); + await kernel.SendAsync(new SubmitCode(@"public static int CheckSwitch(int[] values) + => values switch + { + [1, 2, .., 10] => 1, + [1, 2] => 2, + [1, _] => 3, + [1, ..] => 4, + [..] => 50 + };")); - valueInfosProduced.ValueInfos - .Should() - .ContainSingle(v => v.Name == "x"); - } + events.OfType().Should().BeEmpty(); } } \ No newline at end of file From 023ad4b25a5e1159490130eaf926ad1d1deb935b Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Mon, 21 Nov 2022 23:31:37 +0000 Subject: [PATCH 03/14] fix signature --- eng/Signing.props | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/Signing.props b/eng/Signing.props index ec59a59fdf..6a498d3e6c 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -37,6 +37,7 @@ + From ae49deec75640a1975a6da145309883305823bc5 Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Mon, 21 Nov 2022 22:48:43 +0000 Subject: [PATCH 04/14] powershell upgraded --- Directory.Build.props | 2 +- .../Markdown/AnnotatedCodeBlockParser.cs | 2 +- .../Microsoft.DotNet.Interactive.CSharpProject.csproj | 2 +- .../Microsoft.DotNet.Interactive.Jupyter.csproj | 4 ++-- .../Microsoft.DotNet.Interactive.PowerShell.csproj | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 9909cecc02..2cb1d0340c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,7 +19,7 @@ 13.0.1 4.4.0 5.0.0 - 6.0.1 + 7.0.0 2.4.2 2.4.5 diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Markdown/AnnotatedCodeBlockParser.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Markdown/AnnotatedCodeBlockParser.cs index 84e07965bc..c789cbf9be 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Markdown/AnnotatedCodeBlockParser.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Markdown/AnnotatedCodeBlockParser.cs @@ -60,7 +60,7 @@ public override BlockState TryContinue( Block block) { var fence = (IFencedBlock) block; - var count = fence.FencedCharCount; + var count = Math.Min(fence.OpeningFencedCharCount, fence.ClosingFencedCharCount); var matchChar = fence.FencedChar; var c = processor.CurrentChar; diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft.DotNet.Interactive.CSharpProject.csproj b/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft.DotNet.Interactive.CSharpProject.csproj index e82bfb979e..25e28a2ef9 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft.DotNet.Interactive.CSharpProject.csproj +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft.DotNet.Interactive.CSharpProject.csproj @@ -41,7 +41,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.Jupyter/Microsoft.DotNet.Interactive.Jupyter.csproj b/src/Microsoft.DotNet.Interactive.Jupyter/Microsoft.DotNet.Interactive.Jupyter.csproj index a586bbb55d..e47089f3ed 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter/Microsoft.DotNet.Interactive.Jupyter.csproj +++ b/src/Microsoft.DotNet.Interactive.Jupyter/Microsoft.DotNet.Interactive.Jupyter.csproj @@ -9,14 +9,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.DotNet.Interactive.PowerShell/Microsoft.DotNet.Interactive.PowerShell.csproj b/src/Microsoft.DotNet.Interactive.PowerShell/Microsoft.DotNet.Interactive.PowerShell.csproj index cb3647a0b1..ccf65347b5 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell/Microsoft.DotNet.Interactive.PowerShell.csproj +++ b/src/Microsoft.DotNet.Interactive.PowerShell/Microsoft.DotNet.Interactive.PowerShell.csproj @@ -49,7 +49,7 @@ - + From 4647cffd43bbe20eb23c90099e9708fe055d3795 Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Mon, 21 Nov 2022 22:58:13 +0000 Subject: [PATCH 05/14] fix parser --- .../Markdown/AnnotatedCodeBlockParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Markdown/AnnotatedCodeBlockParser.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/Markdown/AnnotatedCodeBlockParser.cs index c789cbf9be..8a781147f1 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Markdown/AnnotatedCodeBlockParser.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Markdown/AnnotatedCodeBlockParser.cs @@ -60,7 +60,7 @@ public override BlockState TryContinue( Block block) { var fence = (IFencedBlock) block; - var count = Math.Min(fence.OpeningFencedCharCount, fence.ClosingFencedCharCount); + var count = fence.OpeningFencedCharCount; var matchChar = fence.FencedChar; var c = processor.CurrentChar; From 9884902908d07c40628daee7d7b93434d69f74d8 Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Mon, 21 Nov 2022 23:33:49 +0000 Subject: [PATCH 06/14] fix exclusion --- eng/SignCheckExclusionsFile.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/SignCheckExclusionsFile.txt b/eng/SignCheckExclusionsFile.txt index 435bc6a2ba..93ad2b1dc9 100644 --- a/eng/SignCheckExclusionsFile.txt +++ b/eng/SignCheckExclusionsFile.txt @@ -2,5 +2,6 @@ Microsoft.Management.Infrastructure.dll;Microsoft.dotnet-interactive.*.nupkg; Microsoft.PowerShell.Host.psd1;Microsoft.dotnet-interactive.*.nupkg; Microsoft.PowerShell.Management.psd1;Microsoft.dotnet-interactive.*.nupkg; Microsoft.PowerShell.Security.psd1;Microsoft.dotnet-interactive.*.nupkg; +Security.types.ps1xml;Microsoft.dotnet-interactive.*.nupkg; Microsoft.PowerShell.Utility.psd1;Microsoft.dotnet-interactive.*.nupkg; dotnet-interactive.js From b04eff349d0d4dad6fc451314357c445ef81a458 Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Tue, 22 Nov 2022 12:09:00 +0000 Subject: [PATCH 07/14] update packages and entity framework --- ...DotNet.Interactive.AspNetCore.Tests.csproj | 2 +- ...ft.DotNet.Interactive.CSharpProject.csproj | 2 +- ...rosoft.DotNet.Interactive.Documents.csproj | 4 ++-- ...osoft.DotNet.Interactive.Formatting.csproj | 4 ++-- .../Microsoft.DotNet.Interactive.Http.csproj | 2 +- ...icrosoft.DotNet.Interactive.Jupyter.csproj | 4 ++-- .../Microsoft.DotNet.Interactive.Kql.csproj | 4 ++-- ...osoft.DotNet.Interactive.PowerShell.csproj | 2 +- .../ConnectMsSqlCommand.cs | 21 +++++++------------ ...rosoft.DotNet.Interactive.SqlServer.csproj | 10 ++++----- .../Microsoft.DotNet.Interactive.csproj | 6 +++--- .../dotnet-interactive.Tests.csproj | 2 +- .../dotnet-interactive.csproj | 2 +- 13 files changed, 29 insertions(+), 36 deletions(-) diff --git a/src/Microsoft.DotNet.Interactive.AspNetCore.Tests/Microsoft.DotNet.Interactive.AspNetCore.Tests.csproj b/src/Microsoft.DotNet.Interactive.AspNetCore.Tests/Microsoft.DotNet.Interactive.AspNetCore.Tests.csproj index d0dc157dd6..9326a5ab56 100644 --- a/src/Microsoft.DotNet.Interactive.AspNetCore.Tests/Microsoft.DotNet.Interactive.AspNetCore.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.AspNetCore.Tests/Microsoft.DotNet.Interactive.AspNetCore.Tests.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft.DotNet.Interactive.CSharpProject.csproj b/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft.DotNet.Interactive.CSharpProject.csproj index 25e28a2ef9..62f8a58049 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft.DotNet.Interactive.CSharpProject.csproj +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/Microsoft.DotNet.Interactive.CSharpProject.csproj @@ -52,7 +52,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.Documents/Microsoft.DotNet.Interactive.Documents.csproj b/src/Microsoft.DotNet.Interactive.Documents/Microsoft.DotNet.Interactive.Documents.csproj index dd0319495e..5b4e16e448 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/Microsoft.DotNet.Interactive.Documents.csproj +++ b/src/Microsoft.DotNet.Interactive.Documents/Microsoft.DotNet.Interactive.Documents.csproj @@ -15,9 +15,9 @@ - + - + diff --git a/src/Microsoft.DotNet.Interactive.Formatting/Microsoft.DotNet.Interactive.Formatting.csproj b/src/Microsoft.DotNet.Interactive.Formatting/Microsoft.DotNet.Interactive.Formatting.csproj index 3e81849376..0f64e304e7 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting/Microsoft.DotNet.Interactive.Formatting.csproj +++ b/src/Microsoft.DotNet.Interactive.Formatting/Microsoft.DotNet.Interactive.Formatting.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/src/Microsoft.DotNet.Interactive.Http/Microsoft.DotNet.Interactive.Http.csproj b/src/Microsoft.DotNet.Interactive.Http/Microsoft.DotNet.Interactive.Http.csproj index 4b9f7e8abb..9400a3b129 100644 --- a/src/Microsoft.DotNet.Interactive.Http/Microsoft.DotNet.Interactive.Http.csproj +++ b/src/Microsoft.DotNet.Interactive.Http/Microsoft.DotNet.Interactive.Http.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.Jupyter/Microsoft.DotNet.Interactive.Jupyter.csproj b/src/Microsoft.DotNet.Interactive.Jupyter/Microsoft.DotNet.Interactive.Jupyter.csproj index e47089f3ed..443d6f7b7b 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter/Microsoft.DotNet.Interactive.Jupyter.csproj +++ b/src/Microsoft.DotNet.Interactive.Jupyter/Microsoft.DotNet.Interactive.Jupyter.csproj @@ -10,7 +10,7 @@ - + all @@ -18,7 +18,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.Kql/Microsoft.DotNet.Interactive.Kql.csproj b/src/Microsoft.DotNet.Interactive.Kql/Microsoft.DotNet.Interactive.Kql.csproj index 1930db1b44..4f2da6ac45 100644 --- a/src/Microsoft.DotNet.Interactive.Kql/Microsoft.DotNet.Interactive.Kql.csproj +++ b/src/Microsoft.DotNet.Interactive.Kql/Microsoft.DotNet.Interactive.Kql.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/src/Microsoft.DotNet.Interactive.PowerShell/Microsoft.DotNet.Interactive.PowerShell.csproj b/src/Microsoft.DotNet.Interactive.PowerShell/Microsoft.DotNet.Interactive.PowerShell.csproj index ccf65347b5..bd414f0cb6 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell/Microsoft.DotNet.Interactive.PowerShell.csproj +++ b/src/Microsoft.DotNet.Interactive.PowerShell/Microsoft.DotNet.Interactive.PowerShell.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.SqlServer/ConnectMsSqlCommand.cs b/src/Microsoft.DotNet.Interactive.SqlServer/ConnectMsSqlCommand.cs index 9ab36a3827..0e5bf7735f 100644 --- a/src/Microsoft.DotNet.Interactive.SqlServer/ConnectMsSqlCommand.cs +++ b/src/Microsoft.DotNet.Interactive.SqlServer/ConnectMsSqlCommand.cs @@ -1,6 +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.Collections.Generic; using System.CommandLine; using System.CommandLine.Invocation; using System.Linq; @@ -72,11 +73,11 @@ private async Task InitializeDbContextAsync(string kernelName, MsSqlKernelConnec context.DisplayAs($"Scaffolding a `DbContext` and initializing an instance of it called `{kernelName}` in the C# kernel.", "text/markdown"); var submission1 = @$" -#r ""nuget: Microsoft.EntityFrameworkCore.Design, 6.0.10"" -#r ""nuget: Microsoft.EntityFrameworkCore.SqlServer, 6.0.10"" +#r ""nuget: Microsoft.EntityFrameworkCore.Design, 7.0.0"" +#r ""nuget: Microsoft.EntityFrameworkCore.SqlServer, 7.0.0"" #r ""nuget: Humanizer.Core, 2.14.1"" #r ""nuget: Humanizer, 2.14.1"" -#r ""nuget: Microsoft.Identity.Client, 4.35.1"" +#r ""nuget: Microsoft.Identity.Client, 4.48.1"" using System; using System.Reflection; @@ -113,19 +114,11 @@ private async Task InitializeDbContextAsync(string kernelName, MsSqlKernelConnec foreach (var file in new[] {{ model.ContextFile.Code }}.Concat(model.AdditionalFiles.Select(f => f.Code))) {{ + var namespaceToFind = ""namespace {kernelName};""; + var headerSize = file.LastIndexOf(namespaceToFind) + namespaceToFind.Length; var fileCode = file // remove namespaces, which don't compile in Roslyn scripting - .Replace(""namespace {kernelName}"", """") - - // remove the namespaces, which have been hoisted to the top of the code submission - .Replace(""using System;"", """") - .Replace(""using System.Collections.Generic;"", """") - .Replace(""using Microsoft.EntityFrameworkCore;"", """") - .Replace(""using Microsoft.EntityFrameworkCore.Metadata;"", """") - - // trim out the wrapping braces - .Trim() - .Trim( new[] {{ '{{', '}}' }} ); + .Substring(headerSize).Trim(); code += fileCode; }} diff --git a/src/Microsoft.DotNet.Interactive.SqlServer/Microsoft.DotNet.Interactive.SqlServer.csproj b/src/Microsoft.DotNet.Interactive.SqlServer/Microsoft.DotNet.Interactive.SqlServer.csproj index 6bf18cb577..4b6cf2304d 100644 --- a/src/Microsoft.DotNet.Interactive.SqlServer/Microsoft.DotNet.Interactive.SqlServer.csproj +++ b/src/Microsoft.DotNet.Interactive.SqlServer/Microsoft.DotNet.Interactive.SqlServer.csproj @@ -21,15 +21,15 @@ - + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.DotNet.Interactive/Microsoft.DotNet.Interactive.csproj b/src/Microsoft.DotNet.Interactive/Microsoft.DotNet.Interactive.csproj index c1e21c96c3..ff8879a80c 100644 --- a/src/Microsoft.DotNet.Interactive/Microsoft.DotNet.Interactive.csproj +++ b/src/Microsoft.DotNet.Interactive/Microsoft.DotNet.Interactive.csproj @@ -30,8 +30,8 @@ - - + + all @@ -40,7 +40,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/dotnet-interactive.Tests/dotnet-interactive.Tests.csproj b/src/dotnet-interactive.Tests/dotnet-interactive.Tests.csproj index 4436b981d6..89d6766113 100644 --- a/src/dotnet-interactive.Tests/dotnet-interactive.Tests.csproj +++ b/src/dotnet-interactive.Tests/dotnet-interactive.Tests.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/dotnet-interactive/dotnet-interactive.csproj b/src/dotnet-interactive/dotnet-interactive.csproj index ed03971f6b..219241b452 100644 --- a/src/dotnet-interactive/dotnet-interactive.csproj +++ b/src/dotnet-interactive/dotnet-interactive.csproj @@ -68,7 +68,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From c98265d84fb716ce7dcf40db9ee350c6cc60c16b Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Tue, 22 Nov 2022 13:40:02 +0000 Subject: [PATCH 08/14] upgrade packages --- eng/perf-tests/perf-tests.csproj | 2 +- ...Microsoft.DotNet.Interactive.ApiCompatibility.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.AspNetCore.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.Browser.Tests.csproj | 4 ++-- .../Microsoft.DotNet.Interactive.Browser.csproj | 2 +- .../Microsoft.DotNet.Interactive.CSharp.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.CSharpProject.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.Documents.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.ExtensionLab.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.FSharp.Tests.fsproj | 2 +- .../Microsoft.DotNet.Interactive.Formatting.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.Journey.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.Jupyter.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.Kql.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.Mermaid.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.PowerShell.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.SqlServer.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.Telemetry.Tests.csproj | 2 +- .../Microsoft.DotNet.Interactive.Tests.csproj | 4 ++-- src/dotnet-interactive.Tests/dotnet-interactive.Tests.csproj | 2 +- 20 files changed, 22 insertions(+), 22 deletions(-) diff --git a/eng/perf-tests/perf-tests.csproj b/eng/perf-tests/perf-tests.csproj index fecc39490f..e157ebfc6d 100644 --- a/eng/perf-tests/perf-tests.csproj +++ b/eng/perf-tests/perf-tests.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/Microsoft.DotNet.Interactive.ApiCompatibility.Tests.csproj b/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/Microsoft.DotNet.Interactive.ApiCompatibility.Tests.csproj index 6032515f76..2ae140762b 100644 --- a/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/Microsoft.DotNet.Interactive.ApiCompatibility.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/Microsoft.DotNet.Interactive.ApiCompatibility.Tests.csproj @@ -21,7 +21,7 @@ - + all diff --git a/src/Microsoft.DotNet.Interactive.AspNetCore.Tests/Microsoft.DotNet.Interactive.AspNetCore.Tests.csproj b/src/Microsoft.DotNet.Interactive.AspNetCore.Tests/Microsoft.DotNet.Interactive.AspNetCore.Tests.csproj index 9326a5ab56..6df45832ca 100644 --- a/src/Microsoft.DotNet.Interactive.AspNetCore.Tests/Microsoft.DotNet.Interactive.AspNetCore.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.AspNetCore.Tests/Microsoft.DotNet.Interactive.AspNetCore.Tests.csproj @@ -19,7 +19,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.DotNet.Interactive.Browser.Tests/Microsoft.DotNet.Interactive.Browser.Tests.csproj b/src/Microsoft.DotNet.Interactive.Browser.Tests/Microsoft.DotNet.Interactive.Browser.Tests.csproj index 68c5cf845c..afe65e829a 100644 --- a/src/Microsoft.DotNet.Interactive.Browser.Tests/Microsoft.DotNet.Interactive.Browser.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.Browser.Tests/Microsoft.DotNet.Interactive.Browser.Tests.csproj @@ -18,7 +18,7 @@ - + all @@ -34,7 +34,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/src/Microsoft.DotNet.Interactive.Browser/Microsoft.DotNet.Interactive.Browser.csproj b/src/Microsoft.DotNet.Interactive.Browser/Microsoft.DotNet.Interactive.Browser.csproj index cf07cd0f1c..a3c8b12657 100644 --- a/src/Microsoft.DotNet.Interactive.Browser/Microsoft.DotNet.Interactive.Browser.csproj +++ b/src/Microsoft.DotNet.Interactive.Browser/Microsoft.DotNet.Interactive.Browser.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Microsoft.DotNet.Interactive.CSharp.Tests/Microsoft.DotNet.Interactive.CSharp.Tests.csproj b/src/Microsoft.DotNet.Interactive.CSharp.Tests/Microsoft.DotNet.Interactive.CSharp.Tests.csproj index 0fe34abbc1..31b6003c36 100644 --- a/src/Microsoft.DotNet.Interactive.CSharp.Tests/Microsoft.DotNet.Interactive.CSharp.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.CSharp.Tests/Microsoft.DotNet.Interactive.CSharp.Tests.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/Microsoft.DotNet.Interactive.CSharpProject.Tests.csproj b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/Microsoft.DotNet.Interactive.CSharpProject.Tests.csproj index 73bba873cc..73fd7ae6d6 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/Microsoft.DotNet.Interactive.CSharpProject.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.CSharpProject.Tests/Microsoft.DotNet.Interactive.CSharpProject.Tests.csproj @@ -31,7 +31,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.Documents.Tests/Microsoft.DotNet.Interactive.Documents.Tests.csproj b/src/Microsoft.DotNet.Interactive.Documents.Tests/Microsoft.DotNet.Interactive.Documents.Tests.csproj index bcf13b4c0c..477b28b09c 100644 --- a/src/Microsoft.DotNet.Interactive.Documents.Tests/Microsoft.DotNet.Interactive.Documents.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.Documents.Tests/Microsoft.DotNet.Interactive.Documents.Tests.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.ExtensionLab.Tests/Microsoft.DotNet.Interactive.ExtensionLab.Tests.csproj b/src/Microsoft.DotNet.Interactive.ExtensionLab.Tests/Microsoft.DotNet.Interactive.ExtensionLab.Tests.csproj index 92b8653b5d..8d81bbb001 100644 --- a/src/Microsoft.DotNet.Interactive.ExtensionLab.Tests/Microsoft.DotNet.Interactive.ExtensionLab.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.ExtensionLab.Tests/Microsoft.DotNet.Interactive.ExtensionLab.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.FSharp.Tests/Microsoft.DotNet.Interactive.FSharp.Tests.fsproj b/src/Microsoft.DotNet.Interactive.FSharp.Tests/Microsoft.DotNet.Interactive.FSharp.Tests.fsproj index 5dce1917be..570ba8b4e9 100644 --- a/src/Microsoft.DotNet.Interactive.FSharp.Tests/Microsoft.DotNet.Interactive.FSharp.Tests.fsproj +++ b/src/Microsoft.DotNet.Interactive.FSharp.Tests/Microsoft.DotNet.Interactive.FSharp.Tests.fsproj @@ -12,7 +12,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.Formatting.Tests/Microsoft.DotNet.Interactive.Formatting.Tests.csproj b/src/Microsoft.DotNet.Interactive.Formatting.Tests/Microsoft.DotNet.Interactive.Formatting.Tests.csproj index fad108bdfd..54a4a79872 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting.Tests/Microsoft.DotNet.Interactive.Formatting.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.Formatting.Tests/Microsoft.DotNet.Interactive.Formatting.Tests.csproj @@ -14,7 +14,7 @@ - + all diff --git a/src/Microsoft.DotNet.Interactive.Journey.Tests/Microsoft.DotNet.Interactive.Journey.Tests.csproj b/src/Microsoft.DotNet.Interactive.Journey.Tests/Microsoft.DotNet.Interactive.Journey.Tests.csproj index d67f3a04b2..eb26dea414 100644 --- a/src/Microsoft.DotNet.Interactive.Journey.Tests/Microsoft.DotNet.Interactive.Journey.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.Journey.Tests/Microsoft.DotNet.Interactive.Journey.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/Microsoft.DotNet.Interactive.Jupyter.Tests.csproj b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/Microsoft.DotNet.Interactive.Jupyter.Tests.csproj index d225031094..aa7c4cfdca 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/Microsoft.DotNet.Interactive.Jupyter.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/Microsoft.DotNet.Interactive.Jupyter.Tests.csproj @@ -22,7 +22,7 @@ - + all diff --git a/src/Microsoft.DotNet.Interactive.Kql.Tests/Microsoft.DotNet.Interactive.Kql.Tests.csproj b/src/Microsoft.DotNet.Interactive.Kql.Tests/Microsoft.DotNet.Interactive.Kql.Tests.csproj index 2a8131e476..95e93d3c82 100644 --- a/src/Microsoft.DotNet.Interactive.Kql.Tests/Microsoft.DotNet.Interactive.Kql.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.Kql.Tests/Microsoft.DotNet.Interactive.Kql.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.Mermaid.Tests/Microsoft.DotNet.Interactive.Mermaid.Tests.csproj b/src/Microsoft.DotNet.Interactive.Mermaid.Tests/Microsoft.DotNet.Interactive.Mermaid.Tests.csproj index 16b01aeba2..77f97eb239 100644 --- a/src/Microsoft.DotNet.Interactive.Mermaid.Tests/Microsoft.DotNet.Interactive.Mermaid.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.Mermaid.Tests/Microsoft.DotNet.Interactive.Mermaid.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.PowerShell.Tests/Microsoft.DotNet.Interactive.PowerShell.Tests.csproj b/src/Microsoft.DotNet.Interactive.PowerShell.Tests/Microsoft.DotNet.Interactive.PowerShell.Tests.csproj index 6d50665ee6..5c0f662168 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell.Tests/Microsoft.DotNet.Interactive.PowerShell.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.PowerShell.Tests/Microsoft.DotNet.Interactive.PowerShell.Tests.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.SqlServer.Tests/Microsoft.DotNet.Interactive.SqlServer.Tests.csproj b/src/Microsoft.DotNet.Interactive.SqlServer.Tests/Microsoft.DotNet.Interactive.SqlServer.Tests.csproj index 7a9d094b55..c71ff4b94d 100644 --- a/src/Microsoft.DotNet.Interactive.SqlServer.Tests/Microsoft.DotNet.Interactive.SqlServer.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.SqlServer.Tests/Microsoft.DotNet.Interactive.SqlServer.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Microsoft.DotNet.Interactive.Telemetry.Tests/Microsoft.DotNet.Interactive.Telemetry.Tests.csproj b/src/Microsoft.DotNet.Interactive.Telemetry.Tests/Microsoft.DotNet.Interactive.Telemetry.Tests.csproj index abd369707b..e5b2c49213 100644 --- a/src/Microsoft.DotNet.Interactive.Telemetry.Tests/Microsoft.DotNet.Interactive.Telemetry.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.Telemetry.Tests/Microsoft.DotNet.Interactive.Telemetry.Tests.csproj @@ -18,7 +18,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Microsoft.DotNet.Interactive.Tests/Microsoft.DotNet.Interactive.Tests.csproj b/src/Microsoft.DotNet.Interactive.Tests/Microsoft.DotNet.Interactive.Tests.csproj index 5df28a34dc..e9ba9f77e4 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/Microsoft.DotNet.Interactive.Tests.csproj +++ b/src/Microsoft.DotNet.Interactive.Tests/Microsoft.DotNet.Interactive.Tests.csproj @@ -40,10 +40,10 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/dotnet-interactive.Tests/dotnet-interactive.Tests.csproj b/src/dotnet-interactive.Tests/dotnet-interactive.Tests.csproj index 89d6766113..bae4dd99c8 100644 --- a/src/dotnet-interactive.Tests/dotnet-interactive.Tests.csproj +++ b/src/dotnet-interactive.Tests/dotnet-interactive.Tests.csproj @@ -25,7 +25,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 66761e02521656f561962a290272e1cd0d53ec15 Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Tue, 22 Nov 2022 18:32:12 +0000 Subject: [PATCH 09/14] default port range for jupyter --- src/Microsoft.DotNet.Interactive.Http/HttpPortRange.cs | 2 +- .../CommandLine/CommandLineParserTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.Interactive.Http/HttpPortRange.cs b/src/Microsoft.DotNet.Interactive.Http/HttpPortRange.cs index 4072b00e91..6daf9a9512 100644 --- a/src/Microsoft.DotNet.Interactive.Http/HttpPortRange.cs +++ b/src/Microsoft.DotNet.Interactive.Http/HttpPortRange.cs @@ -15,7 +15,7 @@ public HttpPortRange(int start, int end) public int End { get; } - public static HttpPortRange Default { get; } = new HttpPortRange(1000,3000); + public static HttpPortRange Default { get; } = new(2048,3000); public override string ToString() => $"{Start}-{End}"; } diff --git a/src/dotnet-interactive.Tests/CommandLine/CommandLineParserTests.cs b/src/dotnet-interactive.Tests/CommandLine/CommandLineParserTests.cs index b58ad3d925..dcd7a655e9 100644 --- a/src/dotnet-interactive.Tests/CommandLine/CommandLineParserTests.cs +++ b/src/dotnet-interactive.Tests/CommandLine/CommandLineParserTests.cs @@ -181,7 +181,7 @@ public void jupyter_command_help_shows_default_port_range() { _parser.Invoke("jupyter -h", _console); - _console.Out.ToString().Should().Contain("default: 1000-3000"); + _console.Out.ToString().Should().Contain("default: 2048-3000"); } [Fact] From 5ef80f866ba2b8e0ffc46835b562c355164e2dee Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Tue, 22 Nov 2022 18:15:00 +0000 Subject: [PATCH 10/14] fix issue when using type hint --- .../DollarProfileHelper.cs | 5 +- .../Host/PSKernelHost.cs | 261 +++-- .../Host/PSKernelHostRawUserInterface.cs | 231 ++-- .../Host/PSKernelHostUserInterfacePrompt.cs | 1009 ++++++++--------- .../PowerShellExtensions.cs | 162 ++- .../PowerShellKernel.cs | 2 +- .../Kernel.Static.cs | 5 +- 7 files changed, 834 insertions(+), 841 deletions(-) diff --git a/src/Microsoft.DotNet.Interactive.PowerShell/DollarProfileHelper.cs b/src/Microsoft.DotNet.Interactive.PowerShell/DollarProfileHelper.cs index 254eb8f27e..9a94630799 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell/DollarProfileHelper.cs +++ b/src/Microsoft.DotNet.Interactive.PowerShell/DollarProfileHelper.cs @@ -1,7 +1,6 @@ // 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.IO; namespace Microsoft.DotNet.Interactive.PowerShell @@ -26,7 +25,7 @@ private static string GetFullProfileFilePath(bool forCurrentUser) { if (!forCurrentUser) { - string pshome = Path.GetDirectoryName(typeof(PSObject).Assembly.Location); + var pshome = Path.GetDirectoryName(typeof(PSObject).Assembly.Location); return Path.Combine(pshome, _profileName); } @@ -35,7 +34,7 @@ private static string GetFullProfileFilePath(bool forCurrentUser) public static PSObject GetProfileValue() { - PSObject dollarProfile = new PSObject(CurrentUserCurrentHost); + var dollarProfile = new PSObject(CurrentUserCurrentHost); dollarProfile.Properties.Add(new PSNoteProperty("AllUsersCurrentHost", AllUsersCurrentHost)); dollarProfile.Properties.Add(new PSNoteProperty("CurrentUserCurrentHost", CurrentUserCurrentHost)); // TODO: Decide on whether or not we want to support running the AllHosts profiles diff --git a/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHost.cs b/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHost.cs index 310342b891..79af297744 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHost.cs +++ b/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHost.cs @@ -7,168 +7,167 @@ using System.Management.Automation.Host; using System.Management.Automation.Runspaces; -namespace Microsoft.DotNet.Interactive.PowerShell.Host +namespace Microsoft.DotNet.Interactive.PowerShell.Host; + +public class PSKernelHost : PSHost, IHostSupportsInteractiveSession { - public class PSKernelHost : PSHost, IHostSupportsInteractiveSession + private readonly PowerShellKernel _powerShell; + private const string HostName = ".NET Interactive Host"; + + private readonly Version _hostVersion; + private readonly Guid _instanceId; + private readonly PSKernelHostUserInterface _ui; + private readonly PSObject _consoleColorProxy; + + internal PSKernelHost(PowerShellKernel powerShell) { - private readonly PowerShellKernel _powerShell; - private const string HostName = ".NET Interactive Host"; + _powerShell = powerShell ?? throw new ArgumentNullException(nameof(powerShell)); + _hostVersion = new Version("1.0.0"); + _instanceId = Guid.NewGuid(); + _ui = new PSKernelHostUserInterface(_powerShell); + _consoleColorProxy = PSObject.AsPSObject(new ConsoleColorProxy(_ui)); + } - private readonly Version _hostVersion; - private readonly Guid _instanceId; - private readonly PSKernelHostUserInterface _ui; - private readonly PSObject _consoleColorProxy; + #region "PSHost Implementation" - internal PSKernelHost(PowerShellKernel powerShell) - { - _powerShell = powerShell ?? throw new ArgumentNullException(nameof(powerShell)); - _hostVersion = new Version("1.0.0"); - _instanceId = Guid.NewGuid(); - _ui = new PSKernelHostUserInterface(_powerShell); - _consoleColorProxy = PSObject.AsPSObject(new ConsoleColorProxy(_ui)); - } + public override string Name => HostName; - #region "PSHost Implementation" + public override Guid InstanceId => this._instanceId; - public override string Name => HostName; + public override Version Version => _hostVersion; - public override Guid InstanceId => this._instanceId; + public override PSHostUserInterface UI => _ui; - public override Version Version => _hostVersion; + public override PSObject PrivateData => _consoleColorProxy; - public override PSHostUserInterface UI => _ui; + public override CultureInfo CurrentCulture => CultureInfo.CurrentCulture; - public override PSObject PrivateData => _consoleColorProxy; + public override CultureInfo CurrentUICulture => CultureInfo.CurrentUICulture; - public override CultureInfo CurrentCulture => CultureInfo.CurrentCulture; + public override void EnterNestedPrompt() + { + throw new PSNotImplementedException("Nested prompt support is coming soon."); + } - public override CultureInfo CurrentUICulture => CultureInfo.CurrentUICulture; + public override void ExitNestedPrompt() + { + throw new PSNotImplementedException("Nested prompt support is coming soon."); + } - public override void EnterNestedPrompt() - { - throw new PSNotImplementedException("Nested prompt support is coming soon."); - } + public override void NotifyBeginApplication() + { + // Don't need to do anything for the PS kernel. + } - public override void ExitNestedPrompt() - { - throw new PSNotImplementedException("Nested prompt support is coming soon."); - } + public override void NotifyEndApplication() + { + // Don't need to do anything for the PS kernel. + } - public override void NotifyBeginApplication() - { - // Don't need to do anything for the PS kernel. - } + public override void SetShouldExit(int exitCode) + { + // Don't need to do anything for the PS kernel. + } + + #endregion - public override void NotifyEndApplication() - { - // Don't need to do anything for the PS kernel. - } + #region "IHostSupportsInteractiveSession Implementation" - public override void SetShouldExit(int exitCode) - { - // Don't need to do anything for the PS kernel. - } - - #endregion + public bool IsRunspacePushed => false; - #region "IHostSupportsInteractiveSession Implementation" + public Runspace Runspace => throw new PSNotImplementedException("IHostSupportsInteractiveSession support is coming soon."); - public bool IsRunspacePushed => false; + public void PopRunspace() + { + throw new PSNotImplementedException("IHostSupportsInteractiveSession support is coming soon."); + } - public Runspace Runspace => throw new PSNotImplementedException("IHostSupportsInteractiveSession support is coming soon."); + public void PushRunspace(Runspace runspace) + { + throw new PSNotImplementedException("IHostSupportsInteractiveSession support is coming soon."); + } - public void PopRunspace() - { - throw new PSNotImplementedException("IHostSupportsInteractiveSession support is coming soon."); - } + #endregion +} - public void PushRunspace(Runspace runspace) - { - throw new PSNotImplementedException("IHostSupportsInteractiveSession support is coming soon."); - } +internal class ConsoleColorProxy +{ + private readonly PSKernelHostUserInterface _ui; - #endregion + public ConsoleColorProxy(PSKernelHostUserInterface ui) + { + _ui = ui ?? throw new ArgumentNullException(nameof(ui)); } - internal class ConsoleColorProxy + public ConsoleColor FormatAccentColor { - private readonly PSKernelHostUserInterface _ui; + get => _ui.FormatAccentColor; + set => _ui.FormatAccentColor = value; + } - public ConsoleColorProxy(PSKernelHostUserInterface ui) - { - _ui = ui ?? throw new ArgumentNullException(nameof(ui)); - } + public ConsoleColor ErrorAccentColor + { + get => _ui.ErrorAccentColor; + set => _ui.ErrorAccentColor = value; + } - public ConsoleColor FormatAccentColor - { - get => _ui.FormatAccentColor; - set => _ui.FormatAccentColor = value; - } + public ConsoleColor ErrorForegroundColor + { + get => _ui.ErrorForegroundColor; + set => _ui.ErrorForegroundColor = value; + } - public ConsoleColor ErrorAccentColor - { - get => _ui.ErrorAccentColor; - set => _ui.ErrorAccentColor = value; - } + public ConsoleColor ErrorBackgroundColor + { + get => _ui.ErrorBackgroundColor; + set => _ui.ErrorBackgroundColor = value; + } - public ConsoleColor ErrorForegroundColor - { - get => _ui.ErrorForegroundColor; - set => _ui.ErrorForegroundColor = value; - } + public ConsoleColor WarningForegroundColor + { + get => _ui.WarningForegroundColor; + set => _ui.WarningForegroundColor = value; + } - public ConsoleColor ErrorBackgroundColor - { - get => _ui.ErrorBackgroundColor; - set => _ui.ErrorBackgroundColor = value; - } + public ConsoleColor WarningBackgroundColor + { + get => _ui.WarningBackgroundColor; + set => _ui.WarningBackgroundColor = value; + } - public ConsoleColor WarningForegroundColor - { - get => _ui.WarningForegroundColor; - set => _ui.WarningForegroundColor = value; - } + public ConsoleColor DebugForegroundColor + { + get => _ui.DebugForegroundColor; + set => _ui.DebugForegroundColor = value; + } - public ConsoleColor WarningBackgroundColor - { - get => _ui.WarningBackgroundColor; - set => _ui.WarningBackgroundColor = value; - } - - public ConsoleColor DebugForegroundColor - { - get => _ui.DebugForegroundColor; - set => _ui.DebugForegroundColor = value; - } - - public ConsoleColor DebugBackgroundColor - { - get => _ui.DebugBackgroundColor; - set => _ui.DebugBackgroundColor = value; - } - - public ConsoleColor VerboseForegroundColor - { - get => _ui.VerboseForegroundColor; - set => _ui.VerboseForegroundColor = value; - } - - public ConsoleColor VerboseBackgroundColor - { - get => _ui.VerboseBackgroundColor; - set => _ui.VerboseBackgroundColor = value; - } - - public ConsoleColor ProgressForegroundColor - { - get => _ui.ProgressForegroundColor; - set => _ui.ProgressForegroundColor = value; - } - - public ConsoleColor ProgressBackgroundColor - { - get => _ui.ProgressBackgroundColor; - set => _ui.ProgressBackgroundColor = value; - } + public ConsoleColor DebugBackgroundColor + { + get => _ui.DebugBackgroundColor; + set => _ui.DebugBackgroundColor = value; } -} + + public ConsoleColor VerboseForegroundColor + { + get => _ui.VerboseForegroundColor; + set => _ui.VerboseForegroundColor = value; + } + + public ConsoleColor VerboseBackgroundColor + { + get => _ui.VerboseBackgroundColor; + set => _ui.VerboseBackgroundColor = value; + } + + public ConsoleColor ProgressForegroundColor + { + get => _ui.ProgressForegroundColor; + set => _ui.ProgressForegroundColor = value; + } + + public ConsoleColor ProgressBackgroundColor + { + get => _ui.ProgressBackgroundColor; + set => _ui.ProgressBackgroundColor = value; + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHostRawUserInterface.cs b/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHostRawUserInterface.cs index 9778170f3e..f4881584c9 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHostRawUserInterface.cs +++ b/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHostRawUserInterface.cs @@ -4,150 +4,149 @@ using System; using System.Management.Automation.Host; -namespace Microsoft.DotNet.Interactive.PowerShell.Host +namespace Microsoft.DotNet.Interactive.PowerShell.Host; + +public class PSKernelHostRawUserInterface : PSHostRawUserInterface { - public class PSKernelHostRawUserInterface : PSHostRawUserInterface - { - private Size _bufferSize; - private ConsoleColor _foregroundColor, _backgroundColor; + private Size _bufferSize; + private ConsoleColor _foregroundColor, _backgroundColor; - internal PSKernelHostRawUserInterface() - { - _bufferSize = new Size(100, 50); - _foregroundColor = VTColorUtils.DefaultConsoleColor; - _backgroundColor = VTColorUtils.DefaultConsoleColor; - } + internal PSKernelHostRawUserInterface() + { + _bufferSize = new Size(100, 50); + _foregroundColor = VTColorUtils.DefaultConsoleColor; + _backgroundColor = VTColorUtils.DefaultConsoleColor; + } - public override ConsoleColor ForegroundColor - { - get => _foregroundColor; - set => _foregroundColor = value; - } + public override ConsoleColor ForegroundColor + { + get => _foregroundColor; + set => _foregroundColor = value; + } - public override ConsoleColor BackgroundColor - { - get => _backgroundColor; - set => _backgroundColor = value; - } + public override ConsoleColor BackgroundColor + { + get => _backgroundColor; + set => _backgroundColor = value; + } - public override Size BufferSize { get => _bufferSize; set => _bufferSize = value; } - public override Size WindowSize { get => _bufferSize; set => _bufferSize = value; } - public override Size MaxPhysicalWindowSize => _bufferSize; - public override Size MaxWindowSize => _bufferSize; + public override Size BufferSize { get => _bufferSize; set => _bufferSize = value; } + public override Size WindowSize { get => _bufferSize; set => _bufferSize = value; } + public override Size MaxPhysicalWindowSize => _bufferSize; + public override Size MaxWindowSize => _bufferSize; - #region "LengthInBufferCells" + #region "LengthInBufferCells" - public override int LengthInBufferCells(string source, int offset) + public override int LengthInBufferCells(string source, int offset) + { + if (source is null) { - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } - - if (offset < 0 || offset > source.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - int length = 0; - for (int i = offset; i < source.Length; i++) - { - length += LengthInBufferCells(source[i]); - } - - return length; + throw new ArgumentNullException(nameof(source)); } - public override int LengthInBufferCells(string source) + if (offset < 0 || offset > source.Length) { - return LengthInBufferCells(source, offset: 0); + throw new ArgumentOutOfRangeException(nameof(offset)); } - public override int LengthInBufferCells(char c) + var length = 0; + for (var i = offset; i < source.Length; i++) { - // The following is based on https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c - // which is derived from https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt - bool isWide = c >= 0x1100 && - (c <= 0x115f || /* Hangul Jamo init. consonants */ - c == 0x2329 || c == 0x232a || - ((uint)(c - 0x2e80) <= (0xa4cf - 0x2e80) && - c != 0x303f) || /* CJK ... Yi */ - ((uint)(c - 0xac00) <= (0xd7a3 - 0xac00)) || /* Hangul Syllables */ - ((uint)(c - 0xf900) <= (0xfaff - 0xf900)) || /* CJK Compatibility Ideographs */ - ((uint)(c - 0xfe10) <= (0xfe19 - 0xfe10)) || /* Vertical forms */ - ((uint)(c - 0xfe30) <= (0xfe6f - 0xfe30)) || /* CJK Compatibility Forms */ - ((uint)(c - 0xff00) <= (0xff60 - 0xff00)) || /* Fullwidth Forms */ - ((uint)(c - 0xffe0) <= (0xffe6 - 0xffe0))); - - // We can ignore these ranges because .Net strings use surrogate pairs - // for this range and we do not handle surrogage pairs. - // (c >= 0x20000 && c <= 0x2fffd) || - // (c >= 0x30000 && c <= 0x3fffd) - return 1 + (isWide ? 1 : 0); + length += LengthInBufferCells(source[i]); } - #endregion + return length; + } - #region "NotSupported Members" + public override int LengthInBufferCells(string source) + { + return LengthInBufferCells(source, offset: 0); + } - private const string NotSupportedFeatureMsg = "This method or property is not supported by the PowerShell kernel."; + public override int LengthInBufferCells(char c) + { + // The following is based on https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + // which is derived from https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt + bool isWide = c >= 0x1100 && + (c <= 0x115f || /* Hangul Jamo init. consonants */ + c == 0x2329 || c == 0x232a || + ((uint)(c - 0x2e80) <= (0xa4cf - 0x2e80) && + c != 0x303f) || /* CJK ... Yi */ + ((uint)(c - 0xac00) <= (0xd7a3 - 0xac00)) || /* Hangul Syllables */ + ((uint)(c - 0xf900) <= (0xfaff - 0xf900)) || /* CJK Compatibility Ideographs */ + ((uint)(c - 0xfe10) <= (0xfe19 - 0xfe10)) || /* Vertical forms */ + ((uint)(c - 0xfe30) <= (0xfe6f - 0xfe30)) || /* CJK Compatibility Forms */ + ((uint)(c - 0xff00) <= (0xff60 - 0xff00)) || /* Fullwidth Forms */ + ((uint)(c - 0xffe0) <= (0xffe6 - 0xffe0))); + + // We can ignore these ranges because .Net strings use surrogate pairs + // for this range and we do not handle surrogage pairs. + // (c >= 0x20000 && c <= 0x2fffd) || + // (c >= 0x30000 && c <= 0x3fffd) + return 1 + (isWide ? 1 : 0); + } - public override int CursorSize - { - get => throw new NotSupportedException(NotSupportedFeatureMsg); - set => throw new NotSupportedException(NotSupportedFeatureMsg); - } + #endregion - public override Coordinates CursorPosition - { - get => throw new NotSupportedException(NotSupportedFeatureMsg); - set => throw new NotSupportedException(NotSupportedFeatureMsg); - } + #region "NotSupported Members" - public override bool KeyAvailable => throw new NotSupportedException(NotSupportedFeatureMsg); + private const string NotSupportedFeatureMsg = "This method or property is not supported by the PowerShell kernel."; - public override Coordinates WindowPosition - { - get => throw new NotSupportedException(NotSupportedFeatureMsg); - set => throw new NotSupportedException(NotSupportedFeatureMsg); - } + public override int CursorSize + { + get => throw new NotSupportedException(NotSupportedFeatureMsg); + set => throw new NotSupportedException(NotSupportedFeatureMsg); + } - public override string WindowTitle - { - get => throw new NotSupportedException(NotSupportedFeatureMsg); - set => throw new NotSupportedException(NotSupportedFeatureMsg); - } + public override Coordinates CursorPosition + { + get => throw new NotSupportedException(NotSupportedFeatureMsg); + set => throw new NotSupportedException(NotSupportedFeatureMsg); + } - public override void FlushInputBuffer() - { - throw new NotSupportedException(NotSupportedFeatureMsg); - } + public override bool KeyAvailable => throw new NotSupportedException(NotSupportedFeatureMsg); - public override BufferCell[,] GetBufferContents(Rectangle rectangle) - { - throw new NotSupportedException(NotSupportedFeatureMsg); - } + public override Coordinates WindowPosition + { + get => throw new NotSupportedException(NotSupportedFeatureMsg); + set => throw new NotSupportedException(NotSupportedFeatureMsg); + } - public override KeyInfo ReadKey(ReadKeyOptions options) - { - throw new NotSupportedException(NotSupportedFeatureMsg); - } + public override string WindowTitle + { + get => throw new NotSupportedException(NotSupportedFeatureMsg); + set => throw new NotSupportedException(NotSupportedFeatureMsg); + } - public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) - { - throw new NotSupportedException(NotSupportedFeatureMsg); - } + public override void FlushInputBuffer() + { + throw new NotSupportedException(NotSupportedFeatureMsg); + } - public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) - { - throw new NotSupportedException(NotSupportedFeatureMsg); - } + public override BufferCell[,] GetBufferContents(Rectangle rectangle) + { + throw new NotSupportedException(NotSupportedFeatureMsg); + } - public override void SetBufferContents(Rectangle rectangle, BufferCell fill) - { - throw new NotSupportedException(NotSupportedFeatureMsg); - } + public override KeyInfo ReadKey(ReadKeyOptions options) + { + throw new NotSupportedException(NotSupportedFeatureMsg); + } - #endregion + public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill) + { + throw new NotSupportedException(NotSupportedFeatureMsg); } -} + + public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) + { + throw new NotSupportedException(NotSupportedFeatureMsg); + } + + public override void SetBufferContents(Rectangle rectangle, BufferCell fill) + { + throw new NotSupportedException(NotSupportedFeatureMsg); + } + + #endregion +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHostUserInterfacePrompt.cs b/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHostUserInterfacePrompt.cs index 279271d2b3..66039bfd71 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHostUserInterfacePrompt.cs +++ b/src/Microsoft.DotNet.Interactive.PowerShell/Host/PSKernelHostUserInterfacePrompt.cs @@ -12,692 +12,691 @@ using System.Security; using System.Text; -namespace Microsoft.DotNet.Interactive.PowerShell.Host +namespace Microsoft.DotNet.Interactive.PowerShell.Host; + +public partial class PSKernelHostUserInterface { - public partial class PSKernelHostUserInterface - { - // Note: the prompt handling code is from the PowerShell ConsoleHost implementation, - // with the necessary refactoring and updates for it to work with the PowerShell kernel. + // Note: the prompt handling code is from the PowerShell ConsoleHost implementation, + // with the necessary refactoring and updates for it to work with the PowerShell kernel. - private const char PromptCommandPrefix = '!'; + private const char PromptCommandPrefix = '!'; - /// - /// Guarantee a contrasting color for the prompt... - /// - private ConsoleColor PromptColor + /// + /// Guarantee a contrasting color for the prompt... + /// + private ConsoleColor PromptColor + { + get { - get + switch (RawUI.BackgroundColor) { - switch (RawUI.BackgroundColor) - { - case ConsoleColor.White: return ConsoleColor.Black; - case ConsoleColor.Cyan: return ConsoleColor.Black; - case ConsoleColor.DarkYellow: return ConsoleColor.Black; - case ConsoleColor.Yellow: return ConsoleColor.Black; - case ConsoleColor.Gray: return ConsoleColor.Black; - case ConsoleColor.Green: return ConsoleColor.Black; - default: return ConsoleColor.Magenta; - } + case ConsoleColor.White: return ConsoleColor.Black; + case ConsoleColor.Cyan: return ConsoleColor.Black; + case ConsoleColor.DarkYellow: return ConsoleColor.Black; + case ConsoleColor.Yellow: return ConsoleColor.Black; + case ConsoleColor.Gray: return ConsoleColor.Black; + case ConsoleColor.Green: return ConsoleColor.Black; + default: return ConsoleColor.Magenta; } } + } - /// - /// Guarantee a contrasting color for the default prompt that is slightly - /// different from the other prompt elements. - /// - private ConsoleColor DefaultPromptColor + /// + /// Guarantee a contrasting color for the default prompt that is slightly + /// different from the other prompt elements. + /// + private ConsoleColor DefaultPromptColor + { + get { - get - { - if (PromptColor == ConsoleColor.Magenta) - return ConsoleColor.Yellow; - else - return ConsoleColor.Blue; - } + if (PromptColor == ConsoleColor.Magenta) + return ConsoleColor.Yellow; + else + return ConsoleColor.Blue; + } + } + + public override Dictionary Prompt(string caption, string message, Collection descriptions) + { + string paramName = nameof(descriptions); + if (descriptions is null) + { + throw new ArgumentNullException(paramName); + } + + if (descriptions.Count < 1) + { + throw new ArgumentException( + $"The '{paramName}' collection must have at least one element.", + paramName); } - public override Dictionary Prompt(string caption, string message, Collection descriptions) + // we lock here so that multiple threads won't interleave the various reads and writes here. + lock (_instanceLock) { - string paramName = nameof(descriptions); - if (descriptions is null) + var results = new Dictionary(); + + if (!string.IsNullOrEmpty(caption)) { - throw new ArgumentNullException(paramName); + WriteLine(); + WriteLine(PromptColor, RawUI.BackgroundColor, caption); } - if (descriptions.Count < 1) + if (!string.IsNullOrEmpty(message)) { - throw new ArgumentException( - $"The '{paramName}' collection must have at least one element.", - paramName); + WriteLine(message); } - // we lock here so that multiple threads won't interleave the various reads and writes here. - lock (_instanceLock) + if (descriptions.Any(d => d is not null && !string.IsNullOrEmpty(d.HelpMessage))) { - var results = new Dictionary(); + WriteLine("(Type !? for Help.)"); + } - if (!string.IsNullOrEmpty(caption)) - { - WriteLine(); - WriteLine(PromptColor, RawUI.BackgroundColor, caption); - } + for (var index = 0; index < descriptions.Count; index ++) + { + var fieldDescription = descriptions[index] + ?? throw new ArgumentException($"'{paramName}[{index}]' cannot be null.", paramName); - if (!string.IsNullOrEmpty(message)) - { - WriteLine(message); - } + PSObject inputPSObject = null; + var fieldName = fieldDescription.Name; - if (descriptions.Any(d => d is not null && !string.IsNullOrEmpty(d.HelpMessage))) + if (!LanguagePrimitives.TryConvertTo(fieldDescription.ParameterAssemblyFullName, out Type fieldType) && + !LanguagePrimitives.TryConvertTo(fieldDescription.ParameterTypeFullName, out fieldType)) { - WriteLine("(Type !? for Help.)"); + fieldType = typeof(string); } - for (int index = 0; index < descriptions.Count; index ++) + if (typeof(IList).IsAssignableFrom(fieldType)) { - FieldDescription fieldDescription = descriptions[index] - ?? throw new ArgumentException($"'{paramName}[{index}]' cannot be null.", paramName); - - PSObject inputPSObject = null; - string fieldName = fieldDescription.Name; - - if (!LanguagePrimitives.TryConvertTo(fieldDescription.ParameterAssemblyFullName, out Type fieldType) && - !LanguagePrimitives.TryConvertTo(fieldDescription.ParameterTypeFullName, out fieldType)) - { - fieldType = typeof(string); - } + var inputList = new List(); + var elementType = fieldType.IsArray ? fieldType.GetElementType() : typeof(object); - if (typeof(IList).IsAssignableFrom(fieldType)) + var listInputPrompt = new StringBuilder(fieldName); + while (true) { - var inputList = new List(); - Type elementType = fieldType.IsArray ? fieldType.GetElementType() : typeof(object); + listInputPrompt.Append($"[{inputList.Count}]: "); + string inputString = PromptForSingleItem( + elementType, + fieldDescription, + listInputPrompt.ToString(), + isListInput: true, + out bool listInputDone, + out object convertedObj); - var listInputPrompt = new StringBuilder(fieldName); - while (true) + if (listInputDone) { - listInputPrompt.Append($"[{inputList.Count}]: "); - string inputString = PromptForSingleItem( - elementType, - fieldDescription, - listInputPrompt.ToString(), - isListInput: true, - out bool listInputDone, - out object convertedObj); - - if (listInputDone) - { - break; - } - - inputList.Add(convertedObj); - // Remove the indices from the prompt - listInputPrompt.Length = fieldName.Length; + break; } - inputPSObject = LanguagePrimitives.TryConvertTo(inputList, fieldType, out object tryConvertResult) - ? PSObject.AsPSObject(tryConvertResult) - : PSObject.AsPSObject(inputList); + inputList.Add(convertedObj); + // Remove the indices from the prompt + listInputPrompt.Length = fieldName.Length; } - else - { - // field is not a list - string inputPrompt = $"{fieldName}: "; - PromptForSingleItem( - fieldType, - fieldDescription, - inputPrompt, - isListInput: false, - out bool _, - out object convertedObj); + inputPSObject = LanguagePrimitives.TryConvertTo(inputList, fieldType, out object tryConvertResult) + ? PSObject.AsPSObject(tryConvertResult) + : PSObject.AsPSObject(inputList); + } + else + { + // field is not a list + string inputPrompt = $"{fieldName}: "; - inputPSObject = PSObject.AsPSObject(convertedObj); - } + PromptForSingleItem( + fieldType, + fieldDescription, + inputPrompt, + isListInput: false, + out bool _, + out object convertedObj); - results.Add(fieldName, inputPSObject); + inputPSObject = PSObject.AsPSObject(convertedObj); } - return results; + results.Add(fieldName, inputPSObject); } + + return results; } + } - private string PromptForSingleItem( - Type fieldType, - FieldDescription fieldDescription, - string fieldPrompt, - bool isListInput, - out bool listInputDone, - out object convertedObj) + private string PromptForSingleItem( + Type fieldType, + FieldDescription fieldDescription, + string fieldPrompt, + bool isListInput, + out bool listInputDone, + out object convertedObj) + { + listInputDone = false; + convertedObj = null; + + if (fieldType == typeof(SecureString)) { - listInputDone = false; - convertedObj = null; + SecureString secureString = ReadPassword(fieldPrompt).GetSecureStringPassword(); + convertedObj = secureString; - if (fieldType == typeof(SecureString)) + if (isListInput && secureString is not null && secureString.Length == 0) { - SecureString secureString = ReadPassword(fieldPrompt).GetSecureStringPassword(); - convertedObj = secureString; - - if (isListInput && secureString is not null && secureString.Length == 0) - { - listInputDone = true; - } + listInputDone = true; } - else if (fieldType == typeof(PSCredential)) + } + else if (fieldType == typeof(PSCredential)) + { + WriteLine(fieldPrompt); + PSCredential credential = PromptForCredential( + caption: null, // caption already written + message: null, // message already written + userName: null, + targetName: string.Empty); + + convertedObj = credential; + if (isListInput && credential is not null && credential.Password.Length == 0) { - WriteLine(fieldPrompt); - PSCredential credential = PromptForCredential( - caption: null, // caption already written - message: null, // message already written - userName: null, - targetName: string.Empty); - - convertedObj = credential; - if (isListInput && credential is not null && credential.Password.Length == 0) - { - listInputDone = true; - } + listInputDone = true; } - else + } + else + { + string inputString = null; + do { - string inputString = null; - do - { - inputString = PromptReadInput( - fieldDescription, - fieldPrompt, - isListInput, - out listInputDone); - } - while (!listInputDone && !PromptTryConvertTo(fieldType, inputString, out convertedObj)); - return inputString; + inputString = PromptReadInput( + fieldDescription, + fieldPrompt, + isListInput, + out listInputDone); } - - return null; + while (!listInputDone && !PromptTryConvertTo(fieldType, inputString, out convertedObj)); + return inputString; } - private string PromptReadInput( - FieldDescription fieldDescription, - string fieldPrompt, - bool isListInput, - out bool listInputDone) - { - listInputDone = false; - string processedInputString = null; + return null; + } - while (true) + private string PromptReadInput( + FieldDescription fieldDescription, + string fieldPrompt, + bool isListInput, + out bool listInputDone) + { + listInputDone = false; + string processedInputString = null; + + while (true) + { + string rawInputString = ReadInput(fieldPrompt); + if (!string.IsNullOrEmpty(fieldDescription.Label) && rawInputString.StartsWith(PromptCommandPrefix)) { - string rawInputString = ReadInput(fieldPrompt); - if (!string.IsNullOrEmpty(fieldDescription.Label) && rawInputString.StartsWith(PromptCommandPrefix)) - { - processedInputString = PromptCommandMode(rawInputString, fieldDescription, out bool inputDone); + processedInputString = PromptCommandMode(rawInputString, fieldDescription, out bool inputDone); - if (inputDone) - { - break; - } - } - else + if (inputDone) { - if (isListInput && rawInputString.Length == 0) - { - listInputDone = true; - } - - processedInputString = rawInputString; break; } } - - return processedInputString; - } - - private string PromptCommandMode(string input, FieldDescription desc, out bool inputDone) - { - string command = input.Substring(1); - inputDone = true; - - if (command.StartsWith(PromptCommandPrefix)) - { - return command; - } - - if (command.Length == 1) + else { - if (command[0] == '?') - { - WriteLine(string.IsNullOrEmpty(desc.HelpMessage) - ? $"No help is available for {desc.Name}." - : desc.HelpMessage); - } - else + if (isListInput && rawInputString.Length == 0) { - WriteLine($"'{input}' cannot be recognized as a valid Prompt command."); + listInputDone = true; } - inputDone = false; - return null; + processedInputString = rawInputString; + break; } + } - if (string.Equals(command, "\"\"", StringComparison.OrdinalIgnoreCase)) - { - return string.Empty; - } - else if (string.Equals(command, "$null", StringComparison.OrdinalIgnoreCase)) + return processedInputString; + } + + private string PromptCommandMode(string input, FieldDescription desc, out bool inputDone) + { + string command = input.Substring(1); + inputDone = true; + + if (command.StartsWith(PromptCommandPrefix)) + { + return command; + } + + if (command.Length == 1) + { + if (command[0] == '?') { - return null; + WriteLine(string.IsNullOrEmpty(desc.HelpMessage) + ? $"No help is available for {desc.Name}." + : desc.HelpMessage); } else { WriteLine($"'{input}' cannot be recognized as a valid Prompt command."); - inputDone = false; - return null; } + + inputDone = false; + return null; } - private bool PromptTryConvertTo(Type fieldType, string inputString, out object convertedObj) + if (string.Equals(command, "\"\"", StringComparison.OrdinalIgnoreCase)) { - try - { - convertedObj = LanguagePrimitives.ConvertTo(inputString, fieldType, CultureInfo.InvariantCulture); - return true; - } - catch (PSInvalidCastException e) - { - WriteLine(e.InnerException?.Message ?? e.Message); - } - catch (Exception e) - { - WriteLine(e.Message); - } + return string.Empty; + } + else if (string.Equals(command, "$null", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + else + { + WriteLine($"'{input}' cannot be recognized as a valid Prompt command."); + inputDone = false; + return null; + } + } + + private bool PromptTryConvertTo(Type fieldType, string inputString, out object convertedObj) + { + try + { + convertedObj = LanguagePrimitives.ConvertTo(inputString, fieldType, CultureInfo.InvariantCulture); + return true; + } + catch (PSInvalidCastException e) + { + WriteLine(e.InnerException?.Message ?? e.Message); + } + catch (Exception e) + { + WriteLine(e.Message); + } + + convertedObj = inputString; + return false; + } + + public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) + { + var choicesParamName = nameof(choices); + var defaultChoiceParamName = nameof(defaultChoice); - convertedObj = inputString; - return false; + if (choices is null) + { + throw new ArgumentNullException(choicesParamName); + } + + if (choices.Count == 0) + { + throw new ArgumentException( + $"'{choicesParamName}' should have at least one element.", + choicesParamName); } - public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) + if (defaultChoice < -1 || defaultChoice >= choices.Count) { - string choicesParamName = nameof(choices); - string defaultChoiceParamName = nameof(defaultChoice); + throw new ArgumentOutOfRangeException( + defaultChoiceParamName, + defaultChoice, + $"'{defaultChoiceParamName}' must be a valid index into '{choicesParamName}' or -1 for no default choice."); + } + + // we lock here so that multiple threads won't interleave the various reads and writes here. - if (choices is null) + lock (_instanceLock) + { + if (!string.IsNullOrEmpty(caption)) { - throw new ArgumentNullException(choicesParamName); + WriteLine(); + WriteLine(PromptColor, RawUI.BackgroundColor, caption); } - if (choices.Count == 0) + if (!string.IsNullOrEmpty(message)) { - throw new ArgumentException( - $"'{choicesParamName}' should have at least one element.", - choicesParamName); + WriteLine(message); } - if (defaultChoice < -1 || defaultChoice >= choices.Count) + var result = defaultChoice; + var hotkeysAndPlainLabels = BuildHotkeysAndPlainLabels(choices); + + // Add the default choice key only if it is valid. + // The value '-1' is used to specify no default. + var defaultChoiceKeys = new Dictionary(); + if (defaultChoice >= 0) { - throw new ArgumentOutOfRangeException( - defaultChoiceParamName, - defaultChoice, - $"'{defaultChoiceParamName}' must be a valid index into '{choicesParamName}' or -1 for no default choice."); + defaultChoiceKeys.Add(defaultChoice, true); } - // we lock here so that multiple threads won't interleave the various reads and writes here. - - lock (_instanceLock) + do { - if (!string.IsNullOrEmpty(caption)) + WriteChoicePrompt( + hotkeysAndPlainLabels, + defaultChoiceKeys, + shouldEmulateForMultipleChoiceSelection: false); + + var response = ReadInput("Select: ").Trim(); + if (response.Length == 0) { - WriteLine(); - WriteLine(PromptColor, RawUI.BackgroundColor, caption); + // Just hit 'Enter', so pick the default if there is one. + if (defaultChoice >= 0) + { + break; + } + + continue; } - if (!string.IsNullOrEmpty(message)) + // Decide which choice they made. + if (response == "?") { - WriteLine(message); + // Show the help content. + ShowChoiceHelp(choices, hotkeysAndPlainLabels); + continue; } - int result = defaultChoice; - string[,] hotkeysAndPlainLabels = BuildHotkeysAndPlainLabels(choices); - - // Add the default choice key only if it is valid. - // The value '-1' is used to specify no default. - var defaultChoiceKeys = new Dictionary(); - if (defaultChoice >= 0) + result = DetermineChoicePicked(response, choices, hotkeysAndPlainLabels); + if (result >= 0) { - defaultChoiceKeys.Add(defaultChoice, true); + break; } - do - { - WriteChoicePrompt( - hotkeysAndPlainLabels, - defaultChoiceKeys, - shouldEmulateForMultipleChoiceSelection: false); + // The input matched none of the choices, so prompt again + } + while (true); - string response = ReadInput("Select: ").Trim(); - if (response.Length == 0) - { - // Just hit 'Enter', so pick the default if there is one. - if (defaultChoice >= 0) - { - break; - } + return result; + } + } - continue; - } + public Collection PromptForChoice(string caption, string message, Collection choices, IEnumerable defaultChoices) + { + var choicesParamName = nameof(choices); + var defaultChoicesParamName = nameof(defaultChoices); - // Decide which choice they made. - if (response == "?") - { - // Show the help content. - ShowChoiceHelp(choices, hotkeysAndPlainLabels); - continue; - } + if (choices is null) + { + throw new ArgumentNullException(choicesParamName); + } - result = DetermineChoicePicked(response, choices, hotkeysAndPlainLabels); - if (result >= 0) - { - break; - } + if (choices.Count == 0) + { + throw new ArgumentException( + $"'{choicesParamName}' should have at least one element.", + choicesParamName); + } - // The input matched none of the choices, so prompt again + var defaultChoiceKeys = new Dictionary(); + if (defaultChoices is not null) + { + foreach (int defaultChoice in defaultChoices) + { + if (defaultChoice < 0 || defaultChoice >= choices.Count) + { + throw new ArgumentOutOfRangeException( + defaultChoicesParamName, + defaultChoice, + string.Format( + "The element of '{0}' must be a valid index into '{1}'. '{2}' is not a valid index.", + defaultChoicesParamName, + choicesParamName, + defaultChoice)); } - while (true); - return result; + defaultChoiceKeys.TryAdd(defaultChoice, true); } } - public Collection PromptForChoice(string caption, string message, Collection choices, IEnumerable defaultChoices) + var result = new Collection(); + // Lock here so that multiple threads won't interleave the various reads and writes here. + lock (_instanceLock) { - string choicesParamName = nameof(choices); - string defaultChoicesParamName = nameof(defaultChoices); - - if (choices is null) + // Write caption on the console, if present. + if (!string.IsNullOrEmpty(caption)) { - throw new ArgumentNullException(choicesParamName); + // Should be a skin lookup + WriteLine(); + WriteLine(PromptColor, RawUI.BackgroundColor, caption); } - if (choices.Count == 0) + // Write message. + if (!string.IsNullOrEmpty(message)) { - throw new ArgumentException( - $"'{choicesParamName}' should have at least one element.", - choicesParamName); + WriteLine(message); } - var defaultChoiceKeys = new Dictionary(); - if (defaultChoices is not null) - { - foreach (int defaultChoice in defaultChoices) - { - if (defaultChoice < 0 || defaultChoice >= choices.Count) - { - throw new ArgumentOutOfRangeException( - defaultChoicesParamName, - defaultChoice, - string.Format( - "The element of '{0}' must be a valid index into '{1}'. '{2}' is not a valid index.", - defaultChoicesParamName, - choicesParamName, - defaultChoice)); - } + var hotkeysAndPlainLabels = BuildHotkeysAndPlainLabels(choices); - defaultChoiceKeys.TryAdd(defaultChoice, true); - } - } + WriteChoicePrompt( + hotkeysAndPlainLabels, + defaultChoiceKeys, + shouldEmulateForMultipleChoiceSelection: true); - var result = new Collection(); - // Lock here so that multiple threads won't interleave the various reads and writes here. - lock (_instanceLock) + if (defaultChoiceKeys.Count > 0) { - // Write caption on the console, if present. - if (!string.IsNullOrEmpty(caption)) - { - // Should be a skin lookup - WriteLine(); - WriteLine(PromptColor, RawUI.BackgroundColor, caption); - } - - // Write message. - if (!string.IsNullOrEmpty(message)) - { - WriteLine(message); - } - - string[,] hotkeysAndPlainLabels = BuildHotkeysAndPlainLabels(choices); + WriteLine(); + } - WriteChoicePrompt( - hotkeysAndPlainLabels, - defaultChoiceKeys, - shouldEmulateForMultipleChoiceSelection: true); + // Display ChoiceMessage like Choice[0],Choice[1] etc + var choicesSelected = 0; + do + { + // write the current prompt + var choiceMsg = $"Choice[{choicesSelected}]: "; + Write(PromptColor, RawUI.BackgroundColor, choiceMsg); - if (defaultChoiceKeys.Count > 0) - { - WriteLine(); - } + var response = ReadInput("Select: ").Trim(); - // Display ChoiceMessage like Choice[0],Choice[1] etc - int choicesSelected = 0; - do + // Just hit 'Enter'. + if (response.Length == 0) { - // write the current prompt - string choiceMsg = string.Format("Choice[{0}]: ", choicesSelected); - Write(PromptColor, RawUI.BackgroundColor, choiceMsg); - - string response = ReadInput("Select: ").Trim(); + // This may happen when + // 1. User wants to go with the defaults + // 2. User selected some choices and wanted those choices to be picked. - // Just hit 'Enter'. - if (response.Length == 0) + if (result.Count == 0 && defaultChoiceKeys.Keys.Count >= 0) { - // This may happen when - // 1. User wants to go with the defaults - // 2. User selected some choices and wanted those choices to be picked. - - if (result.Count == 0 && defaultChoiceKeys.Keys.Count >= 0) + // User did not pick up any choices. If there's a default, pick that one. + foreach (int defaultChoice in defaultChoiceKeys.Keys) { - // User did not pick up any choices. If there's a default, pick that one. - foreach (int defaultChoice in defaultChoiceKeys.Keys) - { - result.Add(defaultChoice); - } + result.Add(defaultChoice); } - - // allow for no choice selection. - break; } - // Decide which choice the user made. - if (response == "?") - { - // Show the help content. - ShowChoiceHelp(choices, hotkeysAndPlainLabels); - continue; - } + // allow for no choice selection. + break; + } - int choicePicked = DetermineChoicePicked(response, choices, hotkeysAndPlainLabels); - if (choicePicked >= 0) - { - result.Add(choicePicked); - choicesSelected++; - } - // prompt for multiple choices + // Decide which choice the user made. + if (response == "?") + { + // Show the help content. + ShowChoiceHelp(choices, hotkeysAndPlainLabels); + continue; } - while (true); - return result; + var choicePicked = DetermineChoicePicked(response, choices, hotkeysAndPlainLabels); + if (choicePicked >= 0) + { + result.Add(choicePicked); + choicesSelected++; + } + // prompt for multiple choices } + while (true); + + return result; } + } - internal static string[,] BuildHotkeysAndPlainLabels(Collection choices) - { - // we will allocate the result array - const char NullChar = (char)0; - var hotkeysAndPlainLabels = new string[2, choices.Count]; + internal static string[,] BuildHotkeysAndPlainLabels(Collection choices) + { + // we will allocate the result array + const char NullChar = (char)0; + var hotkeysAndPlainLabels = new string[2, choices.Count]; - for (int i = 0; i < choices.Count; ++i) - { - string label = choices[i].Label; + for (int i = 0; i < choices.Count; ++i) + { + var label = choices[i].Label; - char hotKeyChar = NullChar; - string hotKeyString = label; + var hotKeyChar = NullChar; + var hotKeyString = label; - int andIndex = label.IndexOf('&'); - if (andIndex >= 0) + var andIndex = label.IndexOf('&'); + if (andIndex >= 0) + { + if (andIndex + 1 < label.Length) { - if (andIndex + 1 < label.Length) + var hotKeyCandidate = label[andIndex + 1]; + if (!char.IsWhiteSpace(hotKeyCandidate)) { - char hotKeyCandidate = label[andIndex + 1]; - if (!char.IsWhiteSpace(hotKeyCandidate)) - { - hotKeyChar = char.ToUpper(hotKeyCandidate); - } + hotKeyChar = char.ToUpper(hotKeyCandidate); } - - hotKeyString = label.Remove(andIndex, 1).Trim(); } - // The question mark character is already taken for diplaying help. - if (hotKeyChar == '?') - { - throw new ArgumentException( - "Cannot process the hot key because a question mark ('?') cannot be used as a hot key.", - $"choices[{i}].Label"); - } + hotKeyString = label.Remove(andIndex, 1).Trim(); + } - hotkeysAndPlainLabels[0, i] = hotKeyChar == NullChar ? string.Empty : hotKeyChar.ToString(); - hotkeysAndPlainLabels[1, i] = hotKeyString; + // The question mark character is already taken for diplaying help. + if (hotKeyChar == '?') + { + throw new ArgumentException( + "Cannot process the hot key because a question mark ('?') cannot be used as a hot key.", + $"choices[{i}].Label"); } - return hotkeysAndPlainLabels; + hotkeysAndPlainLabels[0, i] = hotKeyChar == NullChar ? string.Empty : hotKeyChar.ToString(); + hotkeysAndPlainLabels[1, i] = hotKeyString; } - private static int DetermineChoicePicked(string response, Collection choices, string[,] hotkeysAndPlainLabels) + return hotkeysAndPlainLabels; + } + + private static int DetermineChoicePicked(string response, Collection choices, string[,] hotkeysAndPlainLabels) + { + // Check the full label first, as this is the least ambiguous. + for (var i = 0; i < choices.Count; i++) { - // Check the full label first, as this is the least ambiguous. - for (int i = 0; i < choices.Count; i++) + // pick the one that matches either the hot key or the full label + if (string.Equals(response, hotkeysAndPlainLabels[1, i], StringComparison.CurrentCultureIgnoreCase)) { - // pick the one that matches either the hot key or the full label - if (string.Equals(response, hotkeysAndPlainLabels[1, i], StringComparison.CurrentCultureIgnoreCase)) - { - return i; - } + return i; } + } - // Now check the hotkeys. - for (int i = 0; i < choices.Count; ++i) + // Now check the hotkeys. + for (var i = 0; i < choices.Count; ++i) + { + // Ignore labels with empty hotkeys + var hotKey = hotkeysAndPlainLabels[0, i]; + if (hotKey.Length == 0) { - // Ignore labels with empty hotkeys - var hotKey = hotkeysAndPlainLabels[0, i]; - if (hotKey.Length == 0) - { - continue; - } - - if (string.Equals(response, hotKey, StringComparison.CurrentCultureIgnoreCase)) - { - return i; - } + continue; } - return -1; + if (string.Equals(response, hotKey, StringComparison.CurrentCultureIgnoreCase)) + { + return i; + } } - private void ShowChoiceHelp(Collection choices, string[,] hotkeysAndPlainLabels) + return -1; + } + + private void ShowChoiceHelp(Collection choices, string[,] hotkeysAndPlainLabels) + { + for (var i = 0; i < choices.Count; ++i) { - for (int i = 0; i < choices.Count; ++i) + var key = hotkeysAndPlainLabels[0, i]; + if (key.Length == 0) { - string key = hotkeysAndPlainLabels[0, i]; - if (key.Length == 0) - { - // If there's no hotkey, use the label as the help. - key = hotkeysAndPlainLabels[1, i]; - } - - WriteLine(string.Format("{0} - {1}", key, choices[i].HelpMessage)); + // If there's no hotkey, use the label as the help. + key = hotkeysAndPlainLabels[1, i]; } + + WriteLine($"{key} - {choices[i].HelpMessage}"); } + } - private void WriteChoicePrompt( - string[,] hotkeysAndPlainLabels, - Dictionary defaultChoiceKeys, - bool shouldEmulateForMultipleChoiceSelection) - { - ConsoleColor fg = RawUI.ForegroundColor; - ConsoleColor bg = RawUI.BackgroundColor; + private void WriteChoicePrompt( + string[,] hotkeysAndPlainLabels, + Dictionary defaultChoiceKeys, + bool shouldEmulateForMultipleChoiceSelection) + { + var fg = RawUI.ForegroundColor; + var bg = RawUI.BackgroundColor; - string choiceTemplate = "[{0}] {1} "; - int lineLen = 0; - int choiceCount = hotkeysAndPlainLabels.GetLength(1); + var choiceTemplate = "[{0}] {1} "; + var lineLen = 0; + var choiceCount = hotkeysAndPlainLabels.GetLength(1); - for (int i = 0; i < choiceCount; ++i) + for (var i = 0; i < choiceCount; ++i) + { + var cfg = PromptColor; + if (defaultChoiceKeys.ContainsKey(i)) { - ConsoleColor cfg = PromptColor; - if (defaultChoiceKeys.ContainsKey(i)) - { - cfg = DefaultPromptColor; - } - - string choice = string.Format( - CultureInfo.InvariantCulture, - choiceTemplate, - hotkeysAndPlainLabels[0, i], - hotkeysAndPlainLabels[1, i]); - - WriteChoiceHelper(choice, cfg, bg, ref lineLen); - if (shouldEmulateForMultipleChoiceSelection) - { - WriteLine(); - } + cfg = DefaultPromptColor; } - WriteChoiceHelper("[?] Help", fg, bg, ref lineLen); + var choice = string.Format( + CultureInfo.InvariantCulture, + choiceTemplate, + hotkeysAndPlainLabels[0, i], + hotkeysAndPlainLabels[1, i]); + WriteChoiceHelper(choice, cfg, bg, ref lineLen); if (shouldEmulateForMultipleChoiceSelection) { WriteLine(); } + } - string defaultPrompt = string.Empty; - if (defaultChoiceKeys.Count > 0) + WriteChoiceHelper("[?] Help", fg, bg, ref lineLen); + + if (shouldEmulateForMultipleChoiceSelection) + { + WriteLine(); + } + + var defaultPrompt = string.Empty; + if (defaultChoiceKeys.Count > 0) + { + var prepend = string.Empty; + var defaultChoicesBuilder = new StringBuilder(); + foreach (var defaultChoice in defaultChoiceKeys.Keys) { - string prepend = string.Empty; - var defaultChoicesBuilder = new StringBuilder(); - foreach (int defaultChoice in defaultChoiceKeys.Keys) + var defaultStr = hotkeysAndPlainLabels[0, defaultChoice]; + if (string.IsNullOrEmpty(defaultStr)) { - string defaultStr = hotkeysAndPlainLabels[0, defaultChoice]; - if (string.IsNullOrEmpty(defaultStr)) - { - defaultStr = hotkeysAndPlainLabels[1, defaultChoice]; - } - - defaultChoicesBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", prepend, defaultStr); - prepend = ","; + defaultStr = hotkeysAndPlainLabels[1, defaultChoice]; } - string defaultChoices = defaultChoicesBuilder.ToString(); - defaultPrompt = defaultChoiceKeys.Count == 1 - ? string.Format("(default is '{0}')", defaultChoices) - : string.Format("(default choices are {0})", defaultChoices); + defaultChoicesBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}", prepend, defaultStr); + prepend = ","; } - WriteChoiceHelper(defaultPrompt, fg, bg, ref lineLen); + var defaultChoices = defaultChoicesBuilder.ToString(); + defaultPrompt = defaultChoiceKeys.Count == 1 + ? $"(default is '{defaultChoices}')" + : $"(default choices are {defaultChoices})"; } - private void WriteChoiceHelper(string text, ConsoleColor fg, ConsoleColor bg, ref int lineLen) - { - int lineLenMax = RawUI.WindowSize.Width - 1; - int textLen = RawUI.LengthInBufferCells(text); - bool trimEnd = false; + WriteChoiceHelper(defaultPrompt, fg, bg, ref lineLen); + } - if (lineLen + textLen > lineLenMax) - { - WriteLine(); - trimEnd = true; - lineLen = textLen; - } - else - { - lineLen += textLen; - } + private void WriteChoiceHelper(string text, ConsoleColor fg, ConsoleColor bg, ref int lineLen) + { + var lineLenMax = RawUI.WindowSize.Width - 1; + var textLen = RawUI.LengthInBufferCells(text); + var trimEnd = false; - Write(fg, bg, trimEnd ? text.TrimEnd() : text); + if (lineLen + textLen > lineLenMax) + { + WriteLine(); + trimEnd = true; + lineLen = textLen; + } + else + { + lineLen += textLen; } + + Write(fg, bg, trimEnd ? text.TrimEnd() : text); } } \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellExtensions.cs b/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellExtensions.cs index 2b82dfc267..40a5c4317e 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellExtensions.cs +++ b/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellExtensions.cs @@ -6,112 +6,110 @@ using System.Collections.ObjectModel; using System.Security; using System.Threading.Tasks; -using Microsoft.DotNet.Interactive.Events; -namespace Microsoft.DotNet.Interactive.PowerShell +namespace Microsoft.DotNet.Interactive.PowerShell; + +using System.Management.Automation; + +internal static class PowerShellExtensions { - using System.Management.Automation; + private static PSInvocationSettings _settings = new() { AddToHistory = true }; - internal static class PowerShellExtensions + public static void InvokeAndClearCommands(this PowerShell pwsh) { - private static PSInvocationSettings _settings = new PSInvocationSettings() { AddToHistory = true }; - - public static void InvokeAndClearCommands(this PowerShell pwsh) + try { - try - { - pwsh.Invoke(input: null, _settings); - } - finally - { - pwsh.Streams.ClearStreams(); - pwsh.Commands.Clear(); - } + pwsh.Invoke(input: null, _settings); } - - public static async Task InvokeAndClearCommandsAsync(this PowerShell pwsh) + finally { - try - { - await pwsh.InvokeAsync( - input: null, - settings: _settings, - callback: null, - state: null).ConfigureAwait(false); - } - finally - { - pwsh.Streams.ClearStreams(); - pwsh.Commands.Clear(); - } + pwsh.Streams.ClearStreams(); + pwsh.Commands.Clear(); } + } - public static void InvokeAndClearCommands(this PowerShell pwsh, IEnumerable input) + public static async Task InvokeAndClearCommandsAsync(this PowerShell pwsh) + { + try { - try - { - pwsh.Invoke(input, _settings); - } - finally - { - pwsh.Streams.ClearStreams(); - pwsh.Commands.Clear(); - } + await pwsh.InvokeAsync( + input: null, + settings: _settings, + callback: null, + state: null).ConfigureAwait(false); + } + finally + { + pwsh.Streams.ClearStreams(); + pwsh.Commands.Clear(); } + } - public static Collection InvokeAndClearCommands(this PowerShell pwsh) + public static void InvokeAndClearCommands(this PowerShell pwsh, IEnumerable input) + { + try { - try - { - var result = pwsh.Invoke(input: null, settings: _settings); - return result; - } - finally - { - pwsh.Streams.ClearStreams(); - pwsh.Commands.Clear(); - } + pwsh.Invoke(input, _settings); } + finally + { + pwsh.Streams.ClearStreams(); + pwsh.Commands.Clear(); + } + } - public static Collection InvokeAndClearCommands(this PowerShell pwsh, IEnumerable input) + public static Collection InvokeAndClearCommands(this PowerShell pwsh) + { + try { - try - { - var result = pwsh.Invoke(input, _settings); - return result; - } - finally - { - pwsh.Streams.ClearStreams(); - pwsh.Commands.Clear(); - } + var result = pwsh.Invoke(input: null, settings: _settings); + return result; + } + finally + { + pwsh.Streams.ClearStreams(); + pwsh.Commands.Clear(); } + } - internal static SecureString GetSecureStringPassword(this PasswordString pwdString) + public static Collection InvokeAndClearCommands(this PowerShell pwsh, IEnumerable input) + { + try { - var secure = new SecureString(); - foreach (char c in pwdString.GetClearTextPassword()) - { - secure.AppendChar(c); - } + var result = pwsh.Invoke(input, _settings); + return result; + } + finally + { + pwsh.Streams.ClearStreams(); + pwsh.Commands.Clear(); + } + } - return secure; + internal static SecureString GetSecureStringPassword(this PasswordString pwdString) + { + var secure = new SecureString(); + foreach (var c in pwdString.GetClearTextPassword()) + { + secure.AppendChar(c); } - internal static object Unwrap(this PSObject psObj) + return secure; + } + + internal static object Unwrap(this PSObject psObj) + { + var obj = psObj.BaseObject; + if (obj is PSCustomObject) { - object obj = psObj.BaseObject; - if (obj is PSCustomObject) + var table = new Dictionary(); + foreach (var p in psObj.Properties) { - Dictionary table = new Dictionary(); - foreach (var p in psObj.Properties) - { - table.Add(p.Name, p.Value); - } - obj = table; + table.Add(p.Name, p.Value); } - - return obj; + obj = table; } + + return obj; } -} +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellKernel.cs b/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellKernel.cs index b3dc7508d8..d3552f0899 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellKernel.cs +++ b/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellKernel.cs @@ -109,7 +109,7 @@ private PowerShell CreatePowerShell() var pwsh = PowerShell.Create(runspace); // Add Modules directory that contains the helper modules - string psJupyterModulePath = Path.Join( + var psJupyterModulePath = Path.Join( Path.GetDirectoryName(typeof(PowerShellKernel).Assembly.Location), "Modules"); diff --git a/src/Microsoft.DotNet.Interactive/Kernel.Static.cs b/src/Microsoft.DotNet.Interactive/Kernel.Static.cs index 944bb3b1da..deba27d7e1 100644 --- a/src/Microsoft.DotNet.Interactive/Kernel.Static.cs +++ b/src/Microsoft.DotNet.Interactive/Kernel.Static.cs @@ -51,9 +51,8 @@ public static async Task GetPasswordAsync(string prompt = "") private static async Task GetInputAsync(string prompt, bool isPassword, string typeHint = "text") { var command = new RequestInput( - prompt, - isPassword ? "password" : default, - inputTypeHint: typeHint); + prompt, + inputTypeHint: isPassword ? "password" : typeHint); var results = await Root.SendAsync(command, CancellationToken.None); From 883c52e77f88092a7de86ad53c095973ad9862b4 Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Tue, 22 Nov 2022 18:18:01 +0000 Subject: [PATCH 11/14] address xml comment --- src/Microsoft.DotNet.Interactive/Kernel.Static.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.DotNet.Interactive/Kernel.Static.cs b/src/Microsoft.DotNet.Interactive/Kernel.Static.cs index deba27d7e1..bda2c55866 100644 --- a/src/Microsoft.DotNet.Interactive/Kernel.Static.cs +++ b/src/Microsoft.DotNet.Interactive/Kernel.Static.cs @@ -37,6 +37,7 @@ public static DisplayedValue display( /// Gets input from the user. /// /// The prompt to show. + /// The type hint for the input, for example text or password. /// The user input value. public static async Task GetInputAsync(string prompt = "", string typeHint = "text") { From eee2c78c4d531740e8a0862884a79982f19347be Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Tue, 22 Nov 2022 20:35:43 -0700 Subject: [PATCH 12/14] add custom kernel picker (#2379) --- samples/stand-alone-kernels/perl/kernel.pl | 310 ++++++++ ...s.Document_api_is_not_changed.approved.txt | 6 +- ...nteractive_api_is_not_changed.approved.txt | 12 +- ...ts.jupyter_api_is_not_changed.approved.txt | 4 +- .../CSharpKernel.cs | 5 +- .../CSharpProjectKernel.cs | 4 +- .../CodeSubmissionFormatTests.cs | 41 +- .../DocumentFormatTestsBase.cs | 6 +- .../JupyterFormatTests.cs | 151 +++- ...without_the_content_changing.approved.json | 24 + .../KernelInfoCollectionTests.cs | 8 +- .../KernelInfoTests.cs | 4 +- ...tebookParserServerTests_ObjectInterface.cs | 94 ++- .../CodeSubmission.cs | 39 +- .../Jupyter/InputCellMetadata.cs | 6 +- .../InteractiveDocumentElementConverter.cs | 26 +- .../Jupyter/InteractiveDocumentExtensions.cs | 4 +- .../Jupyter/Notebook.cs | 1 + .../KernelInfo.cs | 8 +- .../KernelInfoCollection.cs | 16 +- .../ParserServer/KernelInfoConverter.cs | 16 +- .../ParserServer/NotebookParserServer.cs | 14 +- .../SQLiteKernel.cs | 3 +- .../FSharpKernel.fs | 6 +- .../PlainTextFormatterTests.cs | 8 +- .../LessonTests.cs | 8 +- .../KernelExtensions.cs | 2 +- .../NotebookLessonParser.cs | 12 +- .../CompleteRequestHandlerTests.cs | 26 +- .../CustomMetadataParsingTests.cs | 27 +- .../ExecuteRequestHandlerTests.cs | 46 +- .../IsCompleteRequestHandlerTests.cs | 26 +- ..._dotnet_interactive_metadata.approved.json | 6 + ..._polyglot_notebook_metadata.approved.json} | 12 +- .../JupyterMessageContractTests.cs | 37 +- .../CompleteRequestHandler.cs | 2 +- .../ExecuteRequestHandler.cs | 2 +- .../IsCompleteRequestHandler.cs | 2 +- .../JupyterRequestContextExtensions.cs | 18 +- .../Messaging/MetadataExtensions.cs | 1 + .../Protocol/LanguageInfo.cs | 6 +- .../MermaidKernel.cs | 4 +- .../PowerShellKernel.cs | 5 +- .../ToolsServiceKernel.cs | 3 +- ...been_broken.approved.SendEditableCode.json | 2 +- ...en_broken.approved.KernelInfoProduced.json | 1 + .../Connection/SerializationTests.cs | 6 +- .../KernelInfoTests.cs | 3 +- .../Utility/FakeKernel.cs | 6 +- .../Commands/KernelCommand.cs | 8 +- .../Commands/SendEditableCode.cs | 6 +- .../CompositeKernel.cs | 4 +- .../Connection/ProxyKernel.cs | 5 +- .../HtmlKernel.cs | 4 +- .../JavaScriptKernel.cs | 6 +- src/Microsoft.DotNet.Interactive/Kernel.cs | 30 +- .../KernelExtensions.cs | 22 +- .../KernelHost.cs | 11 +- .../KernelInfo.cs | 35 +- .../KeyValueStoreKernel.cs | 1 + .../KqlDiscoverabilityKernel.cs | 4 +- .../SqlDiscoverabilityKernel.cs | 4 +- .../src/commands.ts | 86 +-- .../src/constants.ts | 24 + .../src/documentSemanticTokenProvider.ts | 131 ++++ .../dynamicGrammarSemanticTokenProvider.ts | 456 +++++++++++ .../src/extension.ts | 106 +-- .../src/interactiveClient.ts | 10 +- .../src/interactiveNotebook.ts | 52 +- .../src/interfaces/vscode-like.ts | 7 +- .../src/ipynbUtilities.ts | 174 ----- .../src/kernelSelectorUtilities.ts | 61 ++ .../src/languageProvider.ts | 167 ++-- .../src/languageServices/completion.ts | 4 +- .../src/metadataUtilities.ts | 331 ++++++++ .../src/notebookCellStatusBarItemProvider.ts | 105 +++ .../src/notebookParserServer.ts | 8 +- .../src/variableExplorer.ts | 83 +- .../src/vscodeUtilities.ts | 54 +- ...ynamicGrammarSemanticTokenProvider.test.ts | 495 ++++++++++++ .../tests/grammar.test.ts | 351 --------- .../tests/ipynbMetadata.test.ts | 578 -------------- .../tests/kernelSelectorUtilities.test.ts | 62 ++ .../tests/metadataUtilities.test.ts | 713 ++++++++++++++++++ .../tests/misc.test.ts | 66 -- .../.vscode/settings.json | 1 + .../.vscodeignore | 1 + .../kql.tmGrammar.json} | 8 +- .../package-lock.json | 39 +- .../package.json | 701 +++++++++++------ .../src/notebookControllers.ts | 180 +++-- .../src/notebookSerializers.ts | 47 +- .../csharp.language-configuration.json | 93 --- .../fsharp.language-configuration.json | 148 ---- .../javascript.language-configuration.json | 105 --- .../syntaxes/kql.language-configuration.json | 76 -- ...magic-commands.language-configuration.json | 17 - .../powershell.language-configuration.json | 27 - ...e.dotnet-interactive.csharp.tmGrammar.json | 14 - ...e.dotnet-interactive.fsharp.tmGrammar.json | 14 - ...rce.dotnet-interactive.html.tmGrammar.json | 14 - ...tnet-interactive.javascript.tmGrammar.json | 14 - ...-interactive.magic-commands.tmGrammar.json | 71 -- ...dotnet-interactive.markdown.tmGrammar.json | 14 - ....dotnet-interactive.mermaid.tmGrammar.json | 11 - ...tnet-interactive.powershell.tmGrammar.json | 14 - ...urce.dotnet-interactive.sql.tmGrammar.json | 14 - .../source.dotnet-interactive.tmGrammar.json | 95 --- .../syntaxes/sql.language-configuration.json | 87 --- .../tools/buildSemanticTokenScopes.js | 139 ++++ .../.vscode/settings.json | 1 + .../kql.tmGrammar.json} | 10 +- .../package-lock.json | 39 +- src/dotnet-interactive-vscode/package.json | 703 +++++++++++------ .../src/notebookControllers.ts | 171 +++-- .../src/notebookSerializers.ts | 47 +- .../csharp.language-configuration.json | 93 --- .../fsharp.language-configuration.json | 148 ---- .../javascript.language-configuration.json | 105 --- .../syntaxes/kql.language-configuration.json | 76 -- ...magic-commands.language-configuration.json | 17 - .../powershell.language-configuration.json | 27 - ...e.dotnet-interactive.csharp.tmGrammar.json | 14 - ...e.dotnet-interactive.fsharp.tmGrammar.json | 14 - ...rce.dotnet-interactive.html.tmGrammar.json | 14 - ...tnet-interactive.javascript.tmGrammar.json | 14 - ...-interactive.magic-commands.tmGrammar.json | 71 -- ...dotnet-interactive.markdown.tmGrammar.json | 14 - ....dotnet-interactive.mermaid.tmGrammar.json | 11 - ...tnet-interactive.powershell.tmGrammar.json | 14 - ...urce.dotnet-interactive.sql.tmGrammar.json | 14 - .../source.dotnet-interactive.tmGrammar.json | 95 --- .../syntaxes/sql.language-configuration.json | 87 --- .../CommandLine/CommandLineParserTests.cs | 31 +- src/dotnet-interactive.Tests/HttpApiTests.cs | 2 +- .../StdioConnectionTests.cs | 5 +- .../CommandLine/CommandLineParser.cs | 18 +- .../CommandLine/StartupOptions.cs | 7 +- .../Connection/ConnectStdIO.cs | 32 +- .../Connection/StdIoKernelConnector.cs | 50 +- src/interface-generator/InterfaceGenerator.cs | 15 +- src/interface-generator/StaticContents.ts | 9 +- .../tests/kernel-client.test.ts | 7 +- .../src/connection.ts | 15 +- .../src/contracts.ts | 64 +- .../src/kernel.ts | 3 +- .../src/proxyKernel.ts | 4 +- .../src/webview/variableGrid.ts | 2 +- .../tests/frontEndHost.test.ts | 7 + .../tests/kernelHost.test.ts | 2 + .../tests/kernelInfo.test.ts | 7 + .../tests/proxykernel.test.ts | 4 +- 152 files changed, 5287 insertions(+), 4083 deletions(-) create mode 100644 samples/stand-alone-kernels/perl/kernel.pl create mode 100644 src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_dotnet_interactive_metadata.approved.json rename src/Microsoft.DotNet.Interactive.Jupyter.Tests/{JupyterMessageContractTests.Input_cell_honors_custom_metadata.approved.json => JupyterMessageContractTests.Input_cell_honors_custom_polyglot_notebook_metadata.approved.json} (70%) create mode 100644 src/dotnet-interactive-vscode-common/src/constants.ts create mode 100644 src/dotnet-interactive-vscode-common/src/documentSemanticTokenProvider.ts create mode 100644 src/dotnet-interactive-vscode-common/src/dynamicGrammarSemanticTokenProvider.ts delete mode 100644 src/dotnet-interactive-vscode-common/src/ipynbUtilities.ts create mode 100644 src/dotnet-interactive-vscode-common/src/kernelSelectorUtilities.ts create mode 100644 src/dotnet-interactive-vscode-common/src/metadataUtilities.ts create mode 100644 src/dotnet-interactive-vscode-common/src/notebookCellStatusBarItemProvider.ts create mode 100644 src/dotnet-interactive-vscode-common/tests/dynamicGrammarSemanticTokenProvider.test.ts delete mode 100644 src/dotnet-interactive-vscode-common/tests/grammar.test.ts delete mode 100644 src/dotnet-interactive-vscode-common/tests/ipynbMetadata.test.ts create mode 100644 src/dotnet-interactive-vscode-common/tests/kernelSelectorUtilities.test.ts create mode 100644 src/dotnet-interactive-vscode-common/tests/metadataUtilities.test.ts rename src/dotnet-interactive-vscode-insiders/{syntaxes/source.dotnet-interactive.kql.tmGrammar.json => grammars/kql.tmGrammar.json} (98%) delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/csharp.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/fsharp.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/javascript.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/kql.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/magic-commands.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/powershell.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.csharp.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.html.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.javascript.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.markdown.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.powershell.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.sql.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode-insiders/syntaxes/sql.language-configuration.json create mode 100644 src/dotnet-interactive-vscode-insiders/tools/buildSemanticTokenScopes.js rename src/dotnet-interactive-vscode/{syntaxes/source.dotnet-interactive.kql.tmGrammar.json => grammars/kql.tmGrammar.json} (98%) delete mode 100644 src/dotnet-interactive-vscode/syntaxes/csharp.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/fsharp.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/javascript.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/kql.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/magic-commands.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/powershell.language-configuration.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.csharp.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.html.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.javascript.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.markdown.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.powershell.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.sql.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.tmGrammar.json delete mode 100644 src/dotnet-interactive-vscode/syntaxes/sql.language-configuration.json diff --git a/samples/stand-alone-kernels/perl/kernel.pl b/samples/stand-alone-kernels/perl/kernel.pl new file mode 100644 index 0000000000..9b63303d80 --- /dev/null +++ b/samples/stand-alone-kernels/perl/kernel.pl @@ -0,0 +1,310 @@ +# 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. + +# This is designed to be a stand-alone, out-of-process kernel, primarily used to ensure proper proxy +# handling. It requires a local `perl.exe` interpreter be available and can be used by executing +# the following in a notebook: +# +# #!connect stdio --kernel-name perl --command perl.exe kernel.pl + +use JSON; +use Try::Tiny; + +$|++; # autoflush + +# run with `perl.exe kernel.pl test` to ensure the value serialization works as expected +if (length(@ARGV) > 0 && $ARGV[0] eq "test") { + test(); +} else { + main(@ARGV); +} + +sub test { + $scalarNumber = 4; + $scalarString = "four"; + @array = (1, 2, 3); + $arrayRef = [4, 5, 6]; + %hash = ("theAnswer" => 42, "pi" => 3.14159); + $hashRef = {"theAnswer" => 42, "pi" => 3.14159}; + + @names = ("scalarNumber", "scalarString", "array", "arrayRef", "hash", "hashRef"); + @mimeTypes = ("text/plain", "application/json"); + foreach my $mimeType (@mimeTypes) { + print "$mimeType:\n"; + foreach my $name (@names) { + print " $name: " . getStringRepresentationOfValueName($name, $mimeType) . "\n"; + } + } +} + +sub main { + my $kernelHost = "pid-$$"; + + # the line "#!connect stdio ..." auto-appends these arguments + for (my $i = 0; $i < $#_; $i++) { + my $arg = $_[$i]; + if ($arg eq "--kernel-host") { + $i++; + $kernelHost = $_[$i]; + } + } + + my $kernelUri = "kernel://$kernelHost/"; + + %suppressedValues = {}; + foreach my $valueName ( keys %main:: ) { + if (!$suppressedValues{$valueName}) { + $suppressedValues{$valueName} = 1; + } + } + + $kernelInfo = { + "localName" => "perl", + "languageName" => "perl", + "languageVersion" => "$^V", + "displayName" => "Perl $^V", + "uri" => $kernelUri, + "supportedKernelCommands" => [ + { "name" => "RequestKernelInfo" }, + { "name" => "RequestValue" }, + { "name" => "RequestValueInfos" }, + { "name" => "SendValue" }, + { "name" => "SubmitCode" }, + { "name" => "Quit" } + ], + "supportedDirectives" => [] + }; + + publish({ + "eventType" => "KernelReady", + "event" => {}, + "command" => undef, + "routingSlip" => [ + $kernelUri + ] + }); + + publish({ + "eventType" => "KernelInfoProduced", + "event" => { + "kernelInfo" => $kernelInfo, + }, + "command" => undef, + "routingSlip" => [ + $kernelUri + ] + }); + + while () { + chomp; + try { + $envelope = decode_json($_); + $envelopeRoutingSlip = $envelope->{"routingSlip"}; + push(@$envelopeRoutingSlip, $kernelUri . "?tag=arrived"); + $commandType = $envelope->{'commandType'}; + if ($commandType) { + $token = $envelope->{'token'}; + $command = $envelope->{'command'}; + $succeeded = false; + if ($commandType eq "Quit") { + # + # Quit + # + return; + } elsif ($commandType eq "RequestKernelInfo") { + # + # RequestKernelInfo + # + publish({ + "eventType" => "KernelInfoProduced", + "event" => { + "kernelInfo" => $kernelInfo + }, + "command" => $envelope, + "routingSlip" => [ + $kernelUri + ] + }); + $succeeded = true; + } elsif ($commandType eq "RequestValue") { + # + # RequestValue + # + $valueName = $command->{'name'}; + $mimeType = $command->{'mimeType'}; + $formattedValue = getStringRepresentationOfValueName($valueName, $mimeType); + publish({ + "eventType" => "ValueProduced", + "event" => { + "name" => $valueName, + "formattedValue" => { + "mimeType" => $mimeType, + "value" => $formattedValue + } + }, + "command" => $envelope, + "routingSlip" => [ + $kernelUri + ] + }); + $succeeded = true; + } elsif ($commandType eq "RequestValueInfos") { + # + # RequestValueInfos + # + my @valueInfos = (); + foreach my $valueName ( keys %main:: ) { + if (!$suppressedValues{$valueName}) { + push(@valueInfos, { "name" => "$valueName" }); + } + } + publish({ + "eventType" => "ValueInfosProduced", + "event" => { + "valueInfos" => \@valueInfos + }, + "command" => $envelope, + "routingSlip" => [ + $kernelUri + ] + }); + $succeeded = true; + } elsif ($commandType eq "SendValue") { + # + # SendValue + # + $formattedValue = $command->{'formattedValue'}; + # if `application/json` or `text/json` + if ($formattedValue->{'mimeType'} =~ m/\/json$/) { + $valueName = $command->{'name'}; + $jsonValue = $formattedValue->{'value'}; + $runtimeValue = decode_json($jsonValue); + $main::{$valueName} = $runtimeValue; + $succeeded = true; + } + } elsif ($commandType eq "SubmitCode") { + # + # SubmitCode + # + $code = $command->{'code'}; + $result = eval $code; + publish({ + "eventType" => "ReturnValueProduced", + "event" => { + "formattedValues" => [{ + "mimeType" => "text/plain", + "value" => "$result" + }], + }, + "command" => $envelope, + "routingSlip" => [ + $kernelUri + ] + }); + $succeeded = true; + } else { + $succeeded = false; + } + + push(@$envelopeRoutingSlip, $kernelUri); + if ($succeeded) { + publish({ + "eventType" => "CommandSucceeded", + "event" => {}, + "command" => $envelope, + "routingSlip" => [ + $kernelUri + ] + }); + } else { + publish({ + "eventType" => "CommandFailed", + "event" => { + "message" => "Unknown command type: $commandType" + }, + "command" => $envelope, + "routingSlip" => [ + $kernelUri + ] + }); + } + } + $eventType = $envelope->{'eventType'}; + if ($eventType) { + # TODO: respond to events + } + } catch { + print STDERR "error: $_\n"; + } + } +} + +sub publish { + print encode_json(\%{$_[0]}) . "\n"; +} + +sub getStringRepresentationOfValueName { + my $valueName = shift; + my $mimeType = shift; + my $rawValue = $main::{$valueName}; + my $formattedValue; + # if `application/json` or `text/json` + if ($mimeType =~ m/\/json$/) { + my @asArray = @{getArray($rawValue)}; + my %asHash = %{getHash($rawValue)}; + if (@asArray) { + $rawValue = \@asArray; + } + elsif (%asHash) { + $rawValue = \%asHash; + } + elsif ( length do { no warnings "numeric"; $$rawValue & '' }) { + $rawValue = $$rawValue + 0; + } + else { + $rawValue = $$rawValue; + } + + $formattedValue = encode_json($rawValue); + } + else { + # assume text/plain + my @asArray = @{getArray($rawValue)}; + my %asHash = %{getHash($rawValue)}; + if (@asArray) { + $formattedValue = "(" . join(", ", @asArray) . ")"; + } + elsif (%asHash) { + $formattedValue = "(" . join(", ", map { "$_ => $asHash{$_}" } keys %asHash) . ")"; + } + else { + $formattedValue = "" . $$rawValue; + } + } + + return $formattedValue; +} + +sub getArray { + my $rawValue = shift; + if (ref($$rawValue) eq "ARRAY") { + return \@$$rawValue; + } + elsif (@$rawValue) { + return \@$rawValue; + } + + return undef; +} + +sub getHash { + my $rawValue = shift; + if (ref($$rawValue) eq "HASH") { + return \%$$rawValue; + } + elsif (%$rawValue) { + return \%$rawValue; + } + + return undef; +} diff --git a/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.Document_api_is_not_changed.approved.txt b/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.Document_api_is_not_changed.approved.txt index 2c4e49d4d5..9acc4995b0 100644 --- a/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.Document_api_is_not_changed.approved.txt +++ b/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.Document_api_is_not_changed.approved.txt @@ -48,8 +48,9 @@ Microsoft.DotNet.Interactive.Documents public System.Collections.Generic.List Outputs { get;} public abstract class InteractiveDocumentOutputElement public class KernelInfo - .ctor(System.String name, System.Collections.Generic.IReadOnlyCollection aliases = null) + .ctor(System.String name, System.String languageName = null, System.Collections.Generic.IReadOnlyCollection aliases = null) public System.Collections.Generic.IReadOnlyCollection Aliases { get;} + public System.String LanguageName { get;} public System.String Name { get;} public System.String ToString() public class KernelInfoCollection, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.IEnumerable @@ -77,7 +78,8 @@ Microsoft.DotNet.Interactive.Documents public System.String Text { get;} Microsoft.DotNet.Interactive.Documents.Jupyter public class InputCellMetadata - .ctor(System.String language = null) + .ctor(System.String kernelName = null, System.String language = null) + public System.String KernelName { get;} public System.String Language { get;} public static class InteractiveDocumentExtensions public static Microsoft.DotNet.Interactive.Documents.InteractiveDocument WithJupyterMetadata(System.String language = C#) diff --git a/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.Interactive_api_is_not_changed.approved.txt b/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.Interactive_api_is_not_changed.approved.txt index 00c5973183..2faa532268 100644 --- a/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.Interactive_api_is_not_changed.approved.txt +++ b/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.Interactive_api_is_not_changed.approved.txt @@ -231,10 +231,12 @@ Microsoft.DotNet.Interactive public System.Threading.Tasks.Task ConnectProxyKernelOnDefaultConnectorAsync(System.String localName, System.Uri remoteKernelUri, System.String[] aliases = null) public System.Void Dispose() public class KernelInfo - .ctor(System.String localName, System.String languageName = null, System.String languageVersion = null, System.String[] aliases = null) + .ctor(System.String localName, System.String[] aliases = null) + .ctor(System.String localName, System.String languageName, System.String languageVersion, System.String[] aliases) public System.String[] Aliases { get; set;} - public System.String LanguageName { get;} - public System.String LanguageVersion { get;} + public System.String DisplayName { get; set;} + public System.String LanguageName { get; set;} + public System.String LanguageVersion { get; set;} public System.String LocalName { get;} public System.Uri RemoteUri { get; set;} public System.Collections.Generic.ICollection SupportedDirectives { get; set;} @@ -429,9 +431,9 @@ Microsoft.DotNet.Interactive.Commands public class RequestValueInfos : KernelCommand .ctor(System.String targetKernelName = null) public class SendEditableCode : KernelCommand - .ctor(System.String language, System.String code, System.String targetKernelName = vscode) + .ctor(System.String kernelName, System.String code, System.String targetKernelName = vscode) public System.String Code { get;} - public System.String Language { get;} + public System.String KernelName { get;} public class SendValue : KernelCommand .ctor(System.String name, System.Object value, Microsoft.DotNet.Interactive.FormattedValue formattedValue = null, System.String targetKernelName = null) public Microsoft.DotNet.Interactive.FormattedValue FormattedValue { get;} diff --git a/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.jupyter_api_is_not_changed.approved.txt b/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.jupyter_api_is_not_changed.approved.txt index 4b1902501b..d31532d22c 100644 --- a/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.jupyter_api_is_not_changed.approved.txt +++ b/src/Microsoft.DotNet.Interactive.ApiCompatibility.Tests/ApiCompatibilityTests.jupyter_api_is_not_changed.approved.txt @@ -48,7 +48,7 @@ Microsoft.DotNet.Interactive.Jupyter public System.Threading.Tasks.Task Done() public T GetRequestContent() public static class JupyterRequestContextExtensions - public static System.String GetLanguage() + public static System.String GetKernelName() public class JupyterRequestContextHandler .ctor(Microsoft.DotNet.Interactive.Kernel kernel) public System.Threading.Tasks.Task Handle(JupyterRequestContext context) @@ -169,7 +169,7 @@ Microsoft.DotNet.Interactive.Jupyter.Protocol public System.String Code { get; set;} public System.Int32 CursorPosition { get; set;} public class CSharpLanguageInfo : LanguageInfo - .ctor(System.String version = 10.0) + .ctor(System.String version = 11.0) public class DisplayData : PubSubMessage .ctor(System.String source = null, System.Collections.Generic.IReadOnlyDictionary data = null, System.Collections.Generic.IReadOnlyDictionary metaData = null, System.Collections.Generic.IReadOnlyDictionary transient = null) public System.Collections.Generic.IReadOnlyDictionary Data { get;} diff --git a/src/Microsoft.DotNet.Interactive.CSharp/CSharpKernel.cs b/src/Microsoft.DotNet.Interactive.CSharp/CSharpKernel.cs index fedf88fe44..cf7b4fc8f6 100644 --- a/src/Microsoft.DotNet.Interactive.CSharp/CSharpKernel.cs +++ b/src/Microsoft.DotNet.Interactive.CSharp/CSharpKernel.cs @@ -59,8 +59,11 @@ public CSharpKernel() : this(DefaultKernelName) { } - public CSharpKernel(string name) : base(name, "C#", "11.0") + public CSharpKernel(string name) : base(name) { + KernelInfo.LanguageName = "C#"; + KernelInfo.LanguageVersion = "11.0"; + KernelInfo.DisplayName = "C# Script"; _workspace = new InteractiveWorkspace(); //For the VSCode-Add-In Directory.GetCurrentDirectory() would here return something like: c:\Users\\AppData\Roaming\Code\User\globalStorage\ms-dotnettools.dotnet-interactive-vscode diff --git a/src/Microsoft.DotNet.Interactive.CSharpProject/CSharpProjectKernel.cs b/src/Microsoft.DotNet.Interactive.CSharpProject/CSharpProjectKernel.cs index 3f1a142abf..8bbd8ec58b 100644 --- a/src/Microsoft.DotNet.Interactive.CSharpProject/CSharpProjectKernel.cs +++ b/src/Microsoft.DotNet.Interactive.CSharpProject/CSharpProjectKernel.cs @@ -55,8 +55,10 @@ public static void RegisterEventsAndCommands() } } - public CSharpProjectKernel(string name = "csharp") : base(name, "C#", languageVersion:"11.0") + public CSharpProjectKernel(string name = "csharp") : base(name) { + KernelInfo.LanguageName = "C#"; + KernelInfo.LanguageVersion = "11.0"; } async Task IKernelCommandHandler.HandleAsync(OpenProject command, KernelInvocationContext context) diff --git a/src/Microsoft.DotNet.Interactive.Documents.Tests/CodeSubmissionFormatTests.cs b/src/Microsoft.DotNet.Interactive.Documents.Tests/CodeSubmissionFormatTests.cs index f5374e5f53..f9ec667517 100644 --- a/src/Microsoft.DotNet.Interactive.Documents.Tests/CodeSubmissionFormatTests.cs +++ b/src/Microsoft.DotNet.Interactive.Documents.Tests/CodeSubmissionFormatTests.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Assent; using FluentAssertions; +using FluentAssertions.Execution; using Microsoft.DotNet.Interactive.Tests.Utility; using Xunit; @@ -433,6 +434,44 @@ public void Kernel_languages_can_be_specified_in_metadata() }); } + [Fact] + public void dib_file_with_only_metadata_section_can_be_loaded() + { + var content = @"#!meta +{""theAnswer"":42}"; + var document = ParseDib(content); + document + .Metadata + .Should() + .ContainKey("theAnswer"); + } + + [Fact] + public void kernel_selector_can_immediately_follow_metadata_section() + { + var content = @"#!meta +{""theAnswer"":42} +#!csharp +var x = 1;"; + var document = ParseDib(content); + + using var _ = new AssertionScope(); + + // validate metadata + document + .Metadata + .Should() + .ContainKey("theAnswer"); + + // validate content + document + .Elements + .Single() + .Contents + .Should() + .Be("var x = 1;"); + } + [Fact] public void Metadata_section_is_not_added_as_a_document_element() { @@ -590,7 +629,7 @@ private async Task RoundTripDib(string notebookFile) var inputDoc = CodeSubmission.Parse(expectedContent); var resultContent = inputDoc.ToCodeSubmissionContent(); - + return resultContent; } diff --git a/src/Microsoft.DotNet.Interactive.Documents.Tests/DocumentFormatTestsBase.cs b/src/Microsoft.DotNet.Interactive.Documents.Tests/DocumentFormatTestsBase.cs index 9af031f01e..2bf3059f18 100644 --- a/src/Microsoft.DotNet.Interactive.Documents.Tests/DocumentFormatTestsBase.cs +++ b/src/Microsoft.DotNet.Interactive.Documents.Tests/DocumentFormatTestsBase.cs @@ -11,9 +11,9 @@ protected DocumentFormatTestsBase() { DefaultKernelInfos = new KernelInfoCollection { - new("csharp", new[] { "cs", "C#", "c#" }), - new("fsharp", new[] { "fs", "F#", "f#" }), - new("pwsh", new[] { "powershell" }), + new("csharp", "C#", new[] { "cs", "C#", "c#" }), + new("fsharp", "F#", new[] { "fs", "F#", "f#" }), + new("pwsh", "PowerShell", new[] { "powershell" }), }; DefaultKernelInfos.DefaultKernelName = "csharp"; } diff --git a/src/Microsoft.DotNet.Interactive.Documents.Tests/JupyterFormatTests.cs b/src/Microsoft.DotNet.Interactive.Documents.Tests/JupyterFormatTests.cs index 2e64ad219f..8ab2c2cabc 100644 --- a/src/Microsoft.DotNet.Interactive.Documents.Tests/JupyterFormatTests.cs +++ b/src/Microsoft.DotNet.Interactive.Documents.Tests/JupyterFormatTests.cs @@ -147,7 +147,7 @@ public void missing_metadata_defaults_to_csharp_kernel() } [Fact] - public void cell_metadata_can_specify_language_that_overrides_notebook() + public void cell_dotnet_metadata_can_specify_language_that_overrides_notebook() { var jupyter = new { @@ -197,6 +197,57 @@ public void cell_metadata_can_specify_language_that_overrides_notebook() .Be("fsharp"); } + [Fact] + public void cell_polyglot_metadata_can_specify_kernel_name_that_overrides_notebook() + { + var jupyter = new + { + cells = new object[] + { + new + { + cell_type = "code", + execution_count = 1, + metadata = new + { + polyglot_notebook = new + { + kernelName = "fsharp" + } + }, + source = new[] { "// this should be F#" } + } + }, + metadata = new + { + kernelspec = new + { + display_name = ".NET (C#)", + language = "C#", + name = ".net-csharp" + }, + language_info = new + { + file_extension = ".cs", + mimetype = "text/x-csharp", + name = "C#", + pygments_lexer = "C#", + version = "8.0" + } + }, + nbformat = 4, + nbformat_minor = 4 + }; + var notebook = SerializeAndParse(jupyter); + notebook.Elements + .Should() + .ContainSingle() + .Which + .KernelName + .Should() + .Be("fsharp"); + } + [Fact] public void cell_language_can_specify_language_when_there_is_no_notebook_default() { @@ -229,6 +280,42 @@ public void cell_language_can_specify_language_when_there_is_no_notebook_default .Be("fsharp"); } + [Fact] + public void cell_polyglot_kernel_name_overrides_dotnet_metadata_language() + { + var jupyter = new + { + cells = new object[] + { + new + { + cell_type = "code", + execution_count = 1, + metadata = new + { + dotnet_interactive = new + { + language = "not-fsharp" + }, + polyglot_notebook = new + { + kernelName = "fsharp" + } + }, + source = new[] { "// this should be F#" } + } + } + }; + var notebook = SerializeAndParse(jupyter); + notebook.Elements + .Should() + .ContainSingle() + .Which + .KernelName + .Should() + .Be("fsharp"); + } + [Fact] public void parsed_cells_do_not_contain_redundant_language_specifier() { @@ -643,7 +730,7 @@ public void cells_can_specify_source_as_a_single_string() } [Fact] - public void cell_with_metadata_but_not_language_can_be_parsed() + public void cell_with_dotnet_metadata_but_not_language_can_be_parsed() { var jupyter = new { @@ -686,6 +773,50 @@ public void cell_with_metadata_but_not_language_can_be_parsed() }); } + [Fact] + public void cell_with_polyglot_metadata_but_not_kernel_name_can_be_parsed() + { + var jupyter = new + { + cells = new object[] + { + new + { + cell_type = "code", + source = new[] + { + "// this is not really fsharp" + }, + metadata = new + { + polyglot_notebook = new + { + not_a_kernel = "fsharp" + } + } + } + } + }; + + var notebook = SerializeAndParse(jupyter); + + notebook.Elements + .Should() + .BeEquivalentToRespectingRuntimeTypes(new[] + { + new InteractiveDocumentElement("// this is not really fsharp", "csharp") + { + Metadata = new Dictionary + { + ["polyglot_notebook"] = new Dictionary + { + ["not_a_kernel"] = "fsharp" + } + } + } + }); + } + [Fact] public void code_cell_without_source_can_be_parsed() { @@ -1008,6 +1139,14 @@ public void serialized_notebook_has_appropriate_metadata() { new { name = "csharp" } } + }, + polyglot_notebook = new + { + defaultKernelName = "csharp", + items = new object[] + { + new { name = "csharp" } + } } }))); jupyter["nbformat"] @@ -1045,6 +1184,10 @@ public void serialized_code_cells_have_appropriate_shape() dotnet_interactive = new { language = "csharp" + }, + polyglot_notebook = new + { + kernelName = "csharp" } }, outputs = Array.Empty(), @@ -1095,6 +1238,10 @@ public void serialized_code_cells_with_non_default_jupyter_kernel_language_have_ dotnet_interactive = new { language = "fsharp" + }, + polyglot_notebook = new + { + kernelName = "fsharp" } }, source = new[] diff --git a/src/Microsoft.DotNet.Interactive.Documents.Tests/JupyterFormatTests.ipynb_from_VSCode_can_be_round_tripped_through_read_and_write_without_the_content_changing.approved.json b/src/Microsoft.DotNet.Interactive.Documents.Tests/JupyterFormatTests.ipynb_from_VSCode_can_be_round_tripped_through_read_and_write_without_the_content_changing.approved.json index 5fc4586ef4..06962771c2 100644 --- a/src/Microsoft.DotNet.Interactive.Documents.Tests/JupyterFormatTests.ipynb_from_VSCode_can_be_round_tripped_through_read_and_write_without_the_content_changing.approved.json +++ b/src/Microsoft.DotNet.Interactive.Documents.Tests/JupyterFormatTests.ipynb_from_VSCode_can_be_round_tripped_through_read_and_write_without_the_content_changing.approved.json @@ -18,6 +18,9 @@ }, "vscode": { "languageId": "dotnet-interactive.csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" } }, "outputs": [ @@ -42,6 +45,9 @@ }, "vscode": { "languageId": "dotnet-interactive.csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" } }, "outputs": [ @@ -66,6 +72,9 @@ }, "vscode": { "languageId": "dotnet-interactive.csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" } }, "outputs": [ @@ -94,6 +103,9 @@ }, "vscode": { "languageId": "dotnet-interactive.csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" } }, "outputs": [ @@ -120,6 +132,9 @@ }, "vscode": { "languageId": "dotnet-interactive.csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" } }, "outputs": [ @@ -150,6 +165,9 @@ }, "vscode": { "languageId": "dotnet-interactive.csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" } }, "outputs": [ @@ -176,6 +194,9 @@ }, "vscode": { "languageId": "dotnet-interactive.csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" } }, "outputs": [ @@ -217,6 +238,9 @@ }, "vscode": { "languageId": "dotnet-interactive.csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" } }, "outputs": [], diff --git a/src/Microsoft.DotNet.Interactive.Documents.Tests/KernelInfoCollectionTests.cs b/src/Microsoft.DotNet.Interactive.Documents.Tests/KernelInfoCollectionTests.cs index 7540dd2fad..2be6247a47 100644 --- a/src/Microsoft.DotNet.Interactive.Documents.Tests/KernelInfoCollectionTests.cs +++ b/src/Microsoft.DotNet.Interactive.Documents.Tests/KernelInfoCollectionTests.cs @@ -32,7 +32,7 @@ public void When_a_KernelInfo_with_a_conflicting_alias_is_added_then_it_throws_( collection.Add(new("one")); - collection.Invoking(c => c.Add(new("two", new[] { "one" }))) + collection.Invoking(c => c.Add(new("two", aliases: new[] { "one" }))) .Should() .Throw() .Which @@ -46,7 +46,7 @@ public void When_an_item_is_removed_then_Contains_no_longer_includes_its_name() { var collection = new KernelInfoCollection(); - var kernelInfo = new KernelInfo("a", new[] { "b" }); + var kernelInfo = new KernelInfo("a", aliases: new[] { "b" }); collection.Add(kernelInfo); @@ -60,7 +60,7 @@ public void When_an_item_is_removed_then_Contains_no_longer_includes_its_aliases { var collection = new KernelInfoCollection(); - var kernelInfo = new KernelInfo("a", new[] { "b" }); + var kernelInfo = new KernelInfo("a", aliases: new[] { "b" }); collection.Add(kernelInfo); @@ -68,4 +68,4 @@ public void When_an_item_is_removed_then_Contains_no_longer_includes_its_aliases collection.Contains("b").Should().BeFalse(); } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Interactive.Documents.Tests/KernelInfoTests.cs b/src/Microsoft.DotNet.Interactive.Documents.Tests/KernelInfoTests.cs index 6a3de62215..78f6409593 100644 --- a/src/Microsoft.DotNet.Interactive.Documents.Tests/KernelInfoTests.cs +++ b/src/Microsoft.DotNet.Interactive.Documents.Tests/KernelInfoTests.cs @@ -11,9 +11,9 @@ public class KernelInfoTests [Fact] public void Aliases_do_not_include_name() { - new KernelInfo("one", new[] { "two", "three" }) + new KernelInfo("one", aliases: new[] { "two", "three" }) .Aliases .Should() .BeEquivalentTo("two", "three"); } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Interactive.Documents.Tests/NotebookParserServerTests_ObjectInterface.cs b/src/Microsoft.DotNet.Interactive.Documents.Tests/NotebookParserServerTests_ObjectInterface.cs index 8c4419b3aa..49b6ce0eb2 100644 --- a/src/Microsoft.DotNet.Interactive.Documents.Tests/NotebookParserServerTests_ObjectInterface.cs +++ b/src/Microsoft.DotNet.Interactive.Documents.Tests/NotebookParserServerTests_ObjectInterface.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using FluentAssertions; using Microsoft.DotNet.Interactive.Documents.ParserServer; @@ -24,7 +25,7 @@ public void Notebook_parser_server_can_parse_file_based_on_document_type(Documen rawData: Encoding.UTF8.GetBytes(contents)); var response = NotebookParserServer.HandleRequest(request); - + response .Should() .BeOfType() @@ -39,6 +40,95 @@ public void Notebook_parser_server_can_parse_file_based_on_document_type(Documen .Be("var x = 1;"); } + [Fact] + public void Notebook_parser_server_can_parse_a_dib_file_with_well_known_kernel_metadata() + { + var dibContents = @" +#!meta +{ + ""kernelInfo"": { + ""defaultKernelName"": ""csharp"", + ""items"": [ + { + ""name"": ""csharp"", + ""languageName"": ""csharp"" + }, + { + ""name"": ""fsharp"", + ""languageName"": ""fsharp"" + } + ] + } +} + +#!csharp + +var x = 1; // this is C# + +#!fsharp + +let x = 1 (* this is F# *) +".Trim(); + var request = new NotebookParseRequest( + "the-id", + DocumentSerializationType.Dib, + defaultLanguage: "csharp", + rawData: Encoding.UTF8.GetBytes(dibContents)); + + var response = NotebookParserServer.HandleRequest(request); + + response + .Should() + .BeOfType() + .Which + .Document + .Elements + .Select(e => e.KernelName) + .Should() + .Equal(new[] { "csharp", "fsharp" }); + } + + [Fact] + public void Notebook_parser_server_can_parse_a_dib_file_with_not_well_known_kernel_metadata() + { + var dibContents = @" +#!meta +{ + ""kernelInfo"": { + ""defaultKernelName"": ""snake-language"", + ""items"": [ + { + ""name"": ""snake-language"", + ""languageName"": ""python"" + } + ] + } +} + +#!snake-language + +x = 1 # this is Python +".Trim(); + var request = new NotebookParseRequest( + "the-id", + DocumentSerializationType.Dib, + defaultLanguage: "csharp", + rawData: Encoding.UTF8.GetBytes(dibContents)); + + var response = NotebookParserServer.HandleRequest(request); + + response + .Should() + .BeOfType() + .Which + .Document + .Elements + .Single() + .KernelName + .Should() + .Be("snake-language"); + } + [Theory] [InlineData(DocumentSerializationType.Dib, "#!csharp")] [InlineData(DocumentSerializationType.Ipynb, @"""cell_type""")] @@ -90,4 +180,4 @@ public static string AsUtf8String(this byte[] data) { return Encoding.UTF8.GetString(data); } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Interactive.Documents/CodeSubmission.cs b/src/Microsoft.DotNet.Interactive.Documents/CodeSubmission.cs index c7da56d2b2..f20a4ddbf9 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/CodeSubmission.cs +++ b/src/Microsoft.DotNet.Interactive.Documents/CodeSubmission.cs @@ -28,15 +28,10 @@ public static InteractiveDocument Parse( var lines = content.SplitIntoLines(); var document = new InteractiveDocument(); - var currentLanguage = kernelInfo.DefaultKernelName ?? "csharp"; + var currentKernelName = kernelInfo.DefaultKernelName ?? "csharp"; var currentElementLines = new List(); - // not a kernel language, but still a valid cell splitter - if (!kernelInfo.Contains("markdown")) - { - kernelInfo = kernelInfo.Clone(); - kernelInfo.Add(new KernelInfo("markdown", new[] { "md" })); - } + kernelInfo = WithMarkdownKernel(kernelInfo); var foundMetadata = false; @@ -50,7 +45,7 @@ public static InteractiveDocument Parse( foundMetadata = true; var sb = new StringBuilder(); - while (!(line = lines[++i]).StartsWith("#!")) + while (i < lines.Length - 1 && !(line = lines[++i]).StartsWith(MagicCommandPrefix)) { sb.AppendLine(line); } @@ -61,21 +56,23 @@ public static InteractiveDocument Parse( if (InteractiveDocument.TryGetKernelInfoFromMetadata(metadata, out var kernelInfoFromMetadata)) { + kernelInfo = new(); // clear the old set kernelInfo.AddRange(kernelInfoFromMetadata); + kernelInfo = WithMarkdownKernel(kernelInfo); } } if (line.StartsWith(MagicCommandPrefix)) { - var cellLanguage = line.Substring(MagicCommandPrefix.Length); + var cellKernelName = line.Substring(MagicCommandPrefix.Length); - if (kernelInfo.TryGetByAlias(cellLanguage, out var name)) + if (kernelInfo.TryGetByAlias(cellKernelName, out var name)) { // recognized language, finalize the current element AddElement(); // start a new element - currentLanguage = name.Name; + currentKernelName = name.Name; currentElementLines.Clear(); } else @@ -96,7 +93,7 @@ public static InteractiveDocument Parse( // ensure there's at least one element available if (document.Elements.Count == 0) { - document.Elements.Add(CreateElement(currentLanguage, Array.Empty())); + document.Elements.Add(CreateElement(currentKernelName, Array.Empty())); } if (metadata is not null) @@ -122,14 +119,26 @@ void AddElement() if (currentElementLines.Count > 0) { - document.Elements.Add(CreateElement(currentLanguage, currentElementLines)); + document.Elements.Add(CreateElement(currentKernelName, currentElementLines)); } } - InteractiveDocumentElement CreateElement(string elementLanguage, IEnumerable elementLines) + InteractiveDocumentElement CreateElement(string kernelName, IEnumerable elementLines) + { + return new(string.Join("\n", elementLines), kernelName); + } + } + + private static KernelInfoCollection WithMarkdownKernel(KernelInfoCollection kernelInfo) + { + // not a kernel language, but still a valid cell splitter + if (!kernelInfo.Contains("markdown")) { - return new(string.Join("\n", elementLines), elementLanguage); + kernelInfo = kernelInfo.Clone(); + kernelInfo.Add(new KernelInfo("markdown", languageName: "markdown", aliases: new[] { "md" })); } + + return kernelInfo; } public static InteractiveDocument Read( diff --git a/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InputCellMetadata.cs b/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InputCellMetadata.cs index cd6d346ecc..4a595a7161 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InputCellMetadata.cs +++ b/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InputCellMetadata.cs @@ -7,10 +7,12 @@ namespace Microsoft.DotNet.Interactive.Documents.Jupyter; public class InputCellMetadata { - public InputCellMetadata(string? language = null) + public InputCellMetadata(string? kernelName = null, string ? language = null) { + KernelName = kernelName; Language = language; } + [JsonPropertyName("kernelName")] public string? KernelName { get; } [JsonPropertyName("language")] public string? Language { get; } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InteractiveDocumentElementConverter.cs b/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InteractiveDocumentElementConverter.cs index ff680c5025..f62180287b 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InteractiveDocumentElementConverter.cs +++ b/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InteractiveDocumentElementConverter.cs @@ -46,12 +46,11 @@ public override InteractiveDocumentElement Read( element.Metadata ??= new Dictionary(); element.Metadata.MergeWith(metadata); - if (element.Metadata?.TryGetValue("dotnet_interactive", out var dotnet_interactive) == true && - dotnet_interactive is IDictionary dotnet_interactive_dict && - dotnet_interactive_dict.TryGetValue("language", out var languageStuff) && - languageStuff is string language) + var kernelName = GetMetadataStringValue(element.Metadata, "polyglot_notebook", "kernelName") + ?? GetMetadataStringValue(element.Metadata, "dotnet_interactive", "language"); + if (kernelName is not null) { - element.KernelName = language; + element.KernelName = kernelName; } break; @@ -108,6 +107,19 @@ dotnet_interactive is IDictionary dotnet_interactive_dict && throw new JsonException($"Cannot deserialize {typeToConvert.Name}"); } + private static string? GetMetadataStringValue(IDictionary? dict, string name1, string name2) + { + if (dict?.TryGetValue(name1, out var dotnet_interactive) == true && + dotnet_interactive is IDictionary dotnet_interactive_dict && + dotnet_interactive_dict.TryGetValue(name2, out var languageStuff) && + languageStuff is string value) + { + return value; + } + + return null; + } + public override void Write(Utf8JsonWriter writer, InteractiveDocumentElement element, JsonSerializerOptions options) { writer.WriteStartObject(); @@ -150,6 +162,10 @@ public override void Write(Utf8JsonWriter writer, InteractiveDocumentElement ele element.Metadata.GetOrAdd("dotnet_interactive", _ => new Dictionary()) ["language"] = element.KernelName; + + element.Metadata.GetOrAdd("polyglot_notebook", + _ => new Dictionary()) + ["kernelName"] = element.KernelName; } writer.WritePropertyName("metadata"); diff --git a/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InteractiveDocumentExtensions.cs b/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InteractiveDocumentExtensions.cs index 2371dca1d1..a8e12064f1 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InteractiveDocumentExtensions.cs +++ b/src/Microsoft.DotNet.Interactive.Documents/Jupyter/InteractiveDocumentExtensions.cs @@ -40,7 +40,9 @@ public static InteractiveDocument WithJupyterMetadata( ["version"] = langVersion }); - var kernelInfos = document.Metadata.GetOrAdd("dotnet_interactive", _ => new KernelInfoCollection()); + // `polyglot_notebook` is the canonical metadata key name, but we're still writing `dotnet_interactive` for backwards compatibility + var kernelInfos = document.Metadata.GetOrAdd("polyglot_notebook", _ => new KernelInfoCollection()); + document.Metadata["dotnet_interactive"] = kernelInfos; kernelInfos.DefaultKernelName = kernelName; kernelInfos.Add(new(kernelName)); diff --git a/src/Microsoft.DotNet.Interactive.Documents/Jupyter/Notebook.cs b/src/Microsoft.DotNet.Interactive.Documents/Jupyter/Notebook.cs index 2c3b686f29..14f43d6374 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/Jupyter/Notebook.cs +++ b/src/Microsoft.DotNet.Interactive.Documents/Jupyter/Notebook.cs @@ -37,6 +37,7 @@ static Notebook() public static JsonSerializerOptions JsonSerializerOptions { get; } public const string MetadataNamespace = "dotnet_interactive"; + public const string PolyglotMetadataNamespace = "polyglot_notebook"; public static Encoding Encoding => new UTF8Encoding(false); diff --git a/src/Microsoft.DotNet.Interactive.Documents/KernelInfo.cs b/src/Microsoft.DotNet.Interactive.Documents/KernelInfo.cs index a1e1308954..27fb48a2ce 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/KernelInfo.cs +++ b/src/Microsoft.DotNet.Interactive.Documents/KernelInfo.cs @@ -12,11 +12,13 @@ namespace Microsoft.DotNet.Interactive.Documents; public class KernelInfo { public KernelInfo( - string name, + string name, + string? languageName = null, IReadOnlyCollection? aliases = null) { Validate(name); Name = name; + LanguageName = languageName; if (aliases is not null) { @@ -35,6 +37,8 @@ public KernelInfo( public string Name { get; } + public string? LanguageName { get; } + public IReadOnlyCollection Aliases { get; } public override string ToString() @@ -54,4 +58,4 @@ private static void Validate(string name) throw new ArgumentException("Kernel names or aliases cannot begin with \"#\""); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Interactive.Documents/KernelInfoCollection.cs b/src/Microsoft.DotNet.Interactive.Documents/KernelInfoCollection.cs index d1a1b4cae1..96659e77fc 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/KernelInfoCollection.cs +++ b/src/Microsoft.DotNet.Interactive.Documents/KernelInfoCollection.cs @@ -13,7 +13,7 @@ namespace Microsoft.DotNet.Interactive.Documents; [JsonConverter(typeof(KernelInfoCollectionConverter))] public class KernelInfoCollection : ICollection { - private readonly List _kernelNames = new(); + private readonly HashSet _kernelNames = new(); private readonly Dictionary _kernelInfoByNameOrAlias = new(); private string? _defaultKernelName; @@ -24,7 +24,7 @@ public class KernelInfoCollection : ICollection public string? DefaultKernelName { get => _defaultKernelName ?? (_kernelNames.Count == 1 - ? _kernelNames[0].Name + ? _kernelNames.Single().Name : null); set => _defaultKernelName = value; } @@ -33,13 +33,13 @@ public void Add(KernelInfo kernelInfo) { foreach (var alias in kernelInfo.Aliases.Append(kernelInfo.Name)) { - try - { - _kernelInfoByNameOrAlias.Add(alias, kernelInfo); + try + { + _kernelInfoByNameOrAlias.Add(alias, kernelInfo); } catch (ArgumentException argumentException) - { - throw new ArgumentException($"A {nameof(KernelInfo)} with name or alias '{alias}' is already present in the collection.", argumentException); + { + throw new ArgumentException($"A {nameof(KernelInfo)} with name or alias '{alias}' is already present in the collection.", argumentException); } } @@ -114,4 +114,4 @@ IEnumerator IEnumerable.GetEnumerator() { return _kernelNames.GetEnumerator(); } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Interactive.Documents/ParserServer/KernelInfoConverter.cs b/src/Microsoft.DotNet.Interactive.Documents/ParserServer/KernelInfoConverter.cs index b8d6c9668f..a5d31c0a72 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/ParserServer/KernelInfoConverter.cs +++ b/src/Microsoft.DotNet.Interactive.Documents/ParserServer/KernelInfoConverter.cs @@ -16,6 +16,7 @@ public override KernelInfo Read( EnsureStartObject(reader, typeToConvert); string? name = null; + string? languageName = null; string[]? aliases = null; while (reader.Read()) @@ -26,11 +27,12 @@ public override KernelInfo Read( { case "name": name = reader.ReadString(); - + break; + case "languageName": + languageName = reader.ReadString(); break; case "aliases": aliases = reader.ReadArray(options); - break; default: @@ -44,7 +46,7 @@ public override KernelInfo Read( } } - return new(name!, aliases); + return new(name!, languageName, aliases); } public override void Write(Utf8JsonWriter writer, KernelInfo value, JsonSerializerOptions options) @@ -54,6 +56,12 @@ public override void Write(Utf8JsonWriter writer, KernelInfo value, JsonSerializ writer.WritePropertyName("name"); writer.WriteStringValue(value.Name); + if (value.LanguageName is { }) + { + writer.WritePropertyName("languageName"); + writer.WriteStringValue(value.LanguageName); + } + if (value.Aliases.Count > 0) { writer.WritePropertyName("aliases"); @@ -69,4 +77,4 @@ public override void Write(Utf8JsonWriter writer, KernelInfo value, JsonSerializ writer.WriteEndObject(); } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Interactive.Documents/ParserServer/NotebookParserServer.cs b/src/Microsoft.DotNet.Interactive.Documents/ParserServer/NotebookParserServer.cs index 363a988342..37f128a3af 100644 --- a/src/Microsoft.DotNet.Interactive.Documents/ParserServer/NotebookParserServer.cs +++ b/src/Microsoft.DotNet.Interactive.Documents/ParserServer/NotebookParserServer.cs @@ -25,13 +25,13 @@ public NotebookParserServer(TextReader input, TextWriter output) public static KernelInfoCollection WellKnownKernelInfos = new() { - new("csharp", new[] { "c#", "C#", "cs" }), - new("fsharp", new[] { "f#", "F#", "fs" }), - new("pwsh", new[] { "powershell" }), - new("javascript", new[] { "js" }), - new("html"), - new("sql"), - new("kql"), + new("csharp", languageName: "C#", aliases: new[] { "c#", "cs" }), + new("fsharp", languageName: "F#", aliases: new[] { "f#", "fs" }), + new("pwsh", languageName: "PowerShell", aliases: new[] { "powershell" }), + new("javascript", languageName: "JavaScript", aliases: new[] { "js" }), + new("html", languageName: "HTML"), + new("sql", languageName: "SQL"), + new("kql", languageName: "KQL"), new("value"), }; diff --git a/src/Microsoft.DotNet.Interactive.ExtensionLab/SQLiteKernel.cs b/src/Microsoft.DotNet.Interactive.ExtensionLab/SQLiteKernel.cs index 08a876dca0..16a459f99e 100644 --- a/src/Microsoft.DotNet.Interactive.ExtensionLab/SQLiteKernel.cs +++ b/src/Microsoft.DotNet.Interactive.ExtensionLab/SQLiteKernel.cs @@ -20,8 +20,9 @@ public class SQLiteKernel : private readonly string _connectionString; private IEnumerable>> _tables; - public SQLiteKernel(string name, string connectionString) : base(name, "SQLite") + public SQLiteKernel(string name, string connectionString) : base(name) { + KernelInfo.LanguageName = "SQLite"; _connectionString = connectionString; } diff --git a/src/Microsoft.DotNet.Interactive.FSharp/FSharpKernel.fs b/src/Microsoft.DotNet.Interactive.FSharp/FSharpKernel.fs index 7d732039ae..529670772f 100644 --- a/src/Microsoft.DotNet.Interactive.FSharp/FSharpKernel.fs +++ b/src/Microsoft.DotNet.Interactive.FSharp/FSharpKernel.fs @@ -33,7 +33,11 @@ open FSharp.Compiler.Symbols type FSharpKernel () as this = - inherit Kernel("fsharp", "F#", "6.0") + inherit Kernel("fsharp") + + do this.KernelInfo.LanguageName <- "F#" + do this.KernelInfo.LanguageVersion <- "6.0" + do this.KernelInfo.DisplayName <- "F# Script" static let lockObj = Object(); diff --git a/src/Microsoft.DotNet.Interactive.Formatting.Tests/PlainTextFormatterTests.cs b/src/Microsoft.DotNet.Interactive.Formatting.Tests/PlainTextFormatterTests.cs index bded491a37..9aa84a713e 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting.Tests/PlainTextFormatterTests.cs +++ b/src/Microsoft.DotNet.Interactive.Formatting.Tests/PlainTextFormatterTests.cs @@ -368,11 +368,11 @@ public void Tuple_values_are_formatted_as_multi_line_when_not_all_scalar() var formatter = PlainTextFormatter.GetPreferredFormatterFor(tuple.GetType()); - var formatted = tuple.ToDisplayString(formatter); + var formatted = tuple.ToDisplayString(formatter).Replace("\r", ""); formatted.Should().Be(@" - 123 - Hello - - [ 1, 2, 3 ]"); + - [ 1, 2, 3 ]".Replace("\r", "")); } [Fact] @@ -382,11 +382,11 @@ public void ValueTuple_values_are_formatted_as_multi_line_when_not_all_scalar() var formatter = PlainTextFormatter.GetPreferredFormatterFor(tuple.GetType()); - var formatted = tuple.ToDisplayString(formatter); + var formatted = tuple.ToDisplayString(formatter).Replace("\r", ""); formatted.Should().Be(@" - 123 - Hello - - [ 1, 2, 3 ]"); + - [ 1, 2, 3 ]".Replace("\r", "")); } [Fact] diff --git a/src/Microsoft.DotNet.Interactive.Journey.Tests/LessonTests.cs b/src/Microsoft.DotNet.Interactive.Journey.Tests/LessonTests.cs index 67f1ff8a49..42cff2b5b9 100644 --- a/src/Microsoft.DotNet.Interactive.Journey.Tests/LessonTests.cs +++ b/src/Microsoft.DotNet.Interactive.Journey.Tests/LessonTests.cs @@ -247,12 +247,12 @@ public async Task teacher_can_run_challenge_environment_setup_code_when_starting [Fact] public async Task teacher_can_show_challenge_contents_when_starting_a_Lesson() { - var capturedSendEditableCode = new List<(string language, string code)>(); + var capturedSendEditableCode = new List<(string kernelName, string code)>(); using var kernel = await CreateKernel(LessonMode.StudentMode); var vscodeKernel = kernel.FindKernelByName("vscode"); vscodeKernel.RegisterCommandHandler((command, _) => { - capturedSendEditableCode.Add((command.Language, command.Code)); + capturedSendEditableCode.Add((command.KernelName, command.Code)); return Task.CompletedTask; }); using var events = kernel.KernelEvents.ToSubscribedList(); @@ -297,12 +297,12 @@ public async Task teacher_can_run_challenge_environment_setup_code_when_progress [Fact] public async Task teacher_can_show_challenge_contents_when_progressing_the_student_to_a_new_challenge() { - var capturedSendEditableCode = new List<(string language, string code)>(); + var capturedSendEditableCode = new List<(string kernelName, string code)>(); using var kernel = await CreateKernel(LessonMode.StudentMode); var vscodeKernel = kernel.FindKernelByName("vscode"); vscodeKernel.RegisterCommandHandler((command, _) => { - capturedSendEditableCode.Add((command.Language, command.Code)); + capturedSendEditableCode.Add((command.KernelName, command.Code)); return Task.CompletedTask; }); using var events = kernel.KernelEvents.ToSubscribedList(); diff --git a/src/Microsoft.DotNet.Interactive.Journey/KernelExtensions.cs b/src/Microsoft.DotNet.Interactive.Journey/KernelExtensions.cs index 211fd97516..46dadb7efc 100644 --- a/src/Microsoft.DotNet.Interactive.Journey/KernelExtensions.cs +++ b/src/Microsoft.DotNet.Interactive.Journey/KernelExtensions.cs @@ -201,7 +201,7 @@ private static SubmitCode CreateNewSubmitCodeWithCode(SubmitCode original) private static SendEditableCode CreateNewSendEditableCodeWithContent(SendEditableCode original) { - var command = new SendEditableCode(original.Language, original.Code); + var command = new SendEditableCode(original.KernelName, original.Code); return command; } diff --git a/src/Microsoft.DotNet.Interactive.Journey/NotebookLessonParser.cs b/src/Microsoft.DotNet.Interactive.Journey/NotebookLessonParser.cs index 2905529d5e..543913abd4 100644 --- a/src/Microsoft.DotNet.Interactive.Journey/NotebookLessonParser.cs +++ b/src/Microsoft.DotNet.Interactive.Journey/NotebookLessonParser.cs @@ -77,14 +77,14 @@ private static KernelInfoCollection GetKernelInfoFromKernel(CompositeKernel? ker foreach (var subkernel in kernel) { var info = subkernel.KernelInfo; - kernelInfos.Add(new(info.LocalName, info.Aliases)); + kernelInfos.Add(new(info.LocalName, info.LanguageName, info.Aliases)); } kernelInfos.DefaultKernelName = kernel.DefaultKernelName; if (kernelInfos.All(n => n.Name != "markdown")) { - kernelInfos.Add(new Documents.KernelInfo("markdown", new[] { "md" })); + kernelInfos.Add(new Documents.KernelInfo("markdown", "Markdown", new[] { "md" })); } return kernelInfos; @@ -93,10 +93,10 @@ private static KernelInfoCollection GetKernelInfoFromKernel(CompositeKernel? ker { var names = new KernelInfoCollection { - new("csharp", new[] { "cs", "C#", "c#" }), - new("fsharp", new[] { "fs", "F#", "f#" }), - new("pwsh", new[] { "powershell" }), - new("markdown", new[] { "md" }) + new("csharp", "C#", new[] { "cs", "c#" }), + new("fsharp", "F#", new[] { "fs", "f#" }), + new("pwsh", "PowerShell", new[] { "powershell" }), + new("markdown", "Markdown", new[] { "md" }) }; names.DefaultKernelName = "csharp"; return names; diff --git a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/CompleteRequestHandlerTests.cs b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/CompleteRequestHandlerTests.cs index a77cc2bd96..94d5183187 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/CompleteRequestHandlerTests.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/CompleteRequestHandlerTests.cs @@ -40,16 +40,32 @@ public async Task send_completeReply_on_CompleteRequest() } [Fact] - public void cell_language_can_be_pulled_from_metadata_when_present() + public void kernel_name_can_be_pulled_from_dotnet_interactive_metadata_when_present() { var metaData = new Dictionary { - { "dotnet_interactive", new InputCellMetadata("fsharp" ) } + // the value specified is `language`, but in reality this was the kernel name + { "dotnet_interactive", new InputCellMetadata(language: "fsharp") } }; var request = ZeroMQMessage.Create(new CompleteRequest("1+1"), metaData: metaData); var context = new JupyterRequestContext(JupyterMessageSender, request); - var language = context.GetLanguage(); - language + var kernel = context.GetKernelName(); + kernel + .Should() + .Be("fsharp"); + } + + [Fact] + public void kernel_name_can_be_pulled_from_polyglot_notebook_metadata_when_present() + { + var metaData = new Dictionary + { + { "polyglot_notebook", new InputCellMetadata(kernelName: "fsharp") } + }; + var request = ZeroMQMessage.Create(new CompleteRequest("1+1"), metaData: metaData); + var context = new JupyterRequestContext(JupyterMessageSender, request); + var kernel = context.GetKernelName(); + kernel .Should() .Be("fsharp"); } @@ -59,7 +75,7 @@ public void cell_language_defaults_to_null_when_it_cant_be_found() { var request = ZeroMQMessage.Create(new CompleteRequest("1+1")); var context = new JupyterRequestContext(JupyterMessageSender, request); - var language = context.GetLanguage(); + var language = context.GetKernelName(); language .Should() .BeNull(); diff --git a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/CustomMetadataParsingTests.cs b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/CustomMetadataParsingTests.cs index fbe990f4a6..3d0b274514 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/CustomMetadataParsingTests.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/CustomMetadataParsingTests.cs @@ -13,11 +13,12 @@ namespace Microsoft.DotNet.Interactive.Jupyter.Tests public class CustomMetadataParsingTests { [Fact] - public void Input_cell_metadata_can_be_parsed_with_all_fields() + public void Input_cell_metadata_can_be_parsed_from_dotnet_interactive_metadata() { var rawMetadata = new { - dotnet_interactive = new InputCellMetadata("fsharp") + // the value specified is `language`, but in reality this was the kernel name + dotnet_interactive = new InputCellMetadata(language: "fsharp") }; var rawMetadataJson = JsonSerializer.Serialize(rawMetadata); var metadata = MetadataExtensions.DeserializeMetadataFromJsonString(rawMetadataJson); @@ -25,7 +26,23 @@ public void Input_cell_metadata_can_be_parsed_with_all_fields() .ContainKey("dotnet_interactive") .WhoseValue .Should() - .BeEquivalentToRespectingRuntimeTypes(new InputCellMetadata("fsharp")); + .BeEquivalentToRespectingRuntimeTypes(new InputCellMetadata(language: "fsharp")); + } + + [Fact] + public void Input_cell_metadata_can_be_parsed_from_polyglot_notebook_metadata() + { + var rawMetadata = new + { + polyglot_notebook = new InputCellMetadata(kernelName: "fsharp") + }; + var rawMetadataJson = JsonSerializer.Serialize(rawMetadata); + var metadata = MetadataExtensions.DeserializeMetadataFromJsonString(rawMetadataJson); + metadata.Should() + .ContainKey("polyglot_notebook") + .WhoseValue + .Should() + .BeEquivalentToRespectingRuntimeTypes(new InputCellMetadata(kernelName: "fsharp")); } [Fact] @@ -41,7 +58,7 @@ public void Input_cell_metadata_can_be_parsed_with_no_fields() .ContainKey("dotnet_interactive") .WhoseValue .Should() - .BeEquivalentToRespectingRuntimeTypes(new InputCellMetadata() ); + .BeEquivalentToRespectingRuntimeTypes(new InputCellMetadata()); } [Fact] @@ -49,7 +66,7 @@ public void Input_cell_metadata_is_not_parsed_when_not_present() { var rawMetadata = new { - dotnet_interactive_but_not_the_right_shape = new InputCellMetadata("fsharp") + dotnet_interactive_but_not_the_right_shape = new InputCellMetadata(language: "fsharp") }; var rawMetadataJson = JsonSerializer.Serialize(rawMetadata); var metadata = MetadataExtensions.DeserializeMetadataFromJsonString(rawMetadataJson); diff --git a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/ExecuteRequestHandlerTests.cs b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/ExecuteRequestHandlerTests.cs index c1ea85696c..76cd17035d 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/ExecuteRequestHandlerTests.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/ExecuteRequestHandlerTests.cs @@ -459,27 +459,59 @@ public async Task Shows_not_supported_exception_when_stdin_not_allowed_and_passw } [Fact] - public void cell_language_can_be_pulled_from_metadata_when_present() + public void cell_kernel_name_can_be_pulled_from_dotnet_metadata_when_present() { var metaData = new Dictionary { - { "dotnet_interactive", new InputCellMetadata ("fsharp") } + // the value specified is `language`, but in reality this was the kernel name + { "dotnet_interactive", new InputCellMetadata(language: "fsharp") } }; var request = ZeroMQMessage.Create(new ExecuteRequest("1+1"), metaData: metaData); var context = new JupyterRequestContext(JupyterMessageSender, request); - var language = context.GetLanguage(); - language + var kernelName = context.GetKernelName(); + kernelName .Should() .Be("fsharp"); } [Fact] - public void cell_language_defaults_to_null_when_it_cant_be_found() + public void cell_kernel_name_can_be_pulled_from_polyglot_metadata_when_present() + { + var metaData = new Dictionary + { + { "polyglot_notebook", new InputCellMetadata(kernelName: "fsharp") } + }; + var request = ZeroMQMessage.Create(new ExecuteRequest("1+1"), metaData: metaData); + var context = new JupyterRequestContext(JupyterMessageSender, request); + var kernelName = context.GetKernelName(); + kernelName + .Should() + .Be("fsharp"); + } + + [Fact] + public void cell_kernel_name_in_polyglot_metadata_overrides_dotnet_metadata() + { + var metaData = new Dictionary + { + { "dotnet_interactive", new InputCellMetadata(language: "not-fsharp") }, + { "polyglot_notebook", new InputCellMetadata(kernelName: "fsharp") } + }; + var request = ZeroMQMessage.Create(new ExecuteRequest("1+1"), metaData: metaData); + var context = new JupyterRequestContext(JupyterMessageSender, request); + var kernelName = context.GetKernelName(); + kernelName + .Should() + .Be("fsharp"); + } + + [Fact] + public void cell_kernel_name_defaults_to_null_when_it_cant_be_found() { var request = ZeroMQMessage.Create(new ExecuteRequest("1+1")); var context = new JupyterRequestContext(JupyterMessageSender, request); - var language = context.GetLanguage(); - language + var kernelName = context.GetKernelName(); + kernelName .Should() .BeNull(); } diff --git a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/IsCompleteRequestHandlerTests.cs b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/IsCompleteRequestHandlerTests.cs index 4ae482a49f..d0f17ef11f 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/IsCompleteRequestHandlerTests.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/IsCompleteRequestHandlerTests.cs @@ -51,16 +51,32 @@ public async Task sends_isCompleteReply_with_incomplete_and_indent_if_the_code_i } [Fact] - public void cell_language_can_be_pulled_from_metadata_when_present() + public void cell_language_can_be_pulled_from_dotnet_interactive_metadata_when_present() { var metaData = new Dictionary { - { "dotnet_interactive", new InputCellMetadata( "fsharp") } + // the value specified is `language`, but in reality this was the kernel name + { "dotnet_interactive", new InputCellMetadata(language: "fsharp") } }; var request = ZeroMQMessage.Create(new IsCompleteRequest("1+1"), metaData: metaData); var context = new JupyterRequestContext(JupyterMessageSender, request); - var language = context.GetLanguage(); - language + var kernelName = context.GetKernelName(); + kernelName + .Should() + .Be("fsharp"); + } + + [Fact] + public void cell_language_can_be_pulled_from_polyglot_notebook_metadata_when_present() + { + var metaData = new Dictionary + { + { "polyglot_notebook", new InputCellMetadata(kernelName: "fsharp") } + }; + var request = ZeroMQMessage.Create(new IsCompleteRequest("1+1"), metaData: metaData); + var context = new JupyterRequestContext(JupyterMessageSender, request); + var kernelName = context.GetKernelName(); + kernelName .Should() .Be("fsharp"); } @@ -70,7 +86,7 @@ public void cell_language_defaults_to_null_when_it_cant_be_found() { var request = ZeroMQMessage.Create(new IsCompleteRequest("1+1")); var context = new JupyterRequestContext(JupyterMessageSender, request); - var language = context.GetLanguage(); + var language = context.GetKernelName(); language .Should() .BeNull(); diff --git a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_dotnet_interactive_metadata.approved.json b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_dotnet_interactive_metadata.approved.json new file mode 100644 index 0000000000..1d7b70a678 --- /dev/null +++ b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_dotnet_interactive_metadata.approved.json @@ -0,0 +1,6 @@ +data: more: True +data: fb425505d9bb2c7a3a40095212bf079165dd48a2f231c2938b418742e1f93c60 more: True +data: {"msg_id":"00000000-0000-0000-0000-000000000000","username":"dotnet_kernel","session":"test session","date":"0001-01-01T00:00:00Z","msg_type":"execute_result","version":"5.3"} more: True +data: {} more: True +data: {"dotnet_interactive":{"kernelName":null,"language":"fsharp"}} more: True +data: {"data":{"text/html":"some result","text/plain":"some result"},"metadata":{},"transient":{"display_id":"none"},"execution_count":12} more: False diff --git a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_metadata.approved.json b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_polyglot_notebook_metadata.approved.json similarity index 70% rename from src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_metadata.approved.json rename to src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_polyglot_notebook_metadata.approved.json index 76d8397643..1c841a7e68 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_metadata.approved.json +++ b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.Input_cell_honors_custom_polyglot_notebook_metadata.approved.json @@ -1,6 +1,6 @@ -data: more: True -data: cf637d3764524a6bb1a87763d06309eb4e5dc5ff16ccf7ca16ddd40e8e0c3e05 more: True -data: {"msg_id":"00000000-0000-0000-0000-000000000000","username":"dotnet_kernel","session":"test session","date":"0001-01-01T00:00:00Z","msg_type":"execute_result","version":"5.3"} more: True -data: {} more: True -data: {"dotnet_interactive":{"language":"fsharp"}} more: True -data: {"data":{"text/html":"some result","text/plain":"some result"},"metadata":{},"transient":{"display_id":"none"},"execution_count":12} more: False +data: more: True +data: 603e263e45519c834b3e93c592d33ecf45320b19a8a791aca22d33dfdda5f3e8 more: True +data: {"msg_id":"00000000-0000-0000-0000-000000000000","username":"dotnet_kernel","session":"test session","date":"0001-01-01T00:00:00Z","msg_type":"execute_result","version":"5.3"} more: True +data: {} more: True +data: {"polyglot_notebook":{"kernelName":"fsharp","language":null}} more: True +data: {"data":{"text/html":"some result","text/plain":"some result"},"metadata":{},"transient":{"display_id":"none"},"execution_count":12} more: False diff --git a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.cs b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.cs index 5bd611aa4d..60f0897377 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter.Tests/JupyterMessageContractTests.cs @@ -156,7 +156,7 @@ public void Update_data_contract_has_not_been_broken() } [Fact] - public void Input_cell_honors_custom_metadata() + public void Input_cell_honors_custom_dotnet_interactive_metadata() { var socket = new TextSocket(); var sender = new MessageSender(socket, new SignatureValidator("key", "HMACSHA256")); @@ -177,7 +177,40 @@ public void Input_cell_honors_custom_metadata() var metaData = new Dictionary { - { "dotnet_interactive", new InputCellMetadata("fsharp") } + { "dotnet_interactive", new InputCellMetadata(language: "fsharp") } + }; + + var replyMessage = new Message(header, content: executeResult, metaData: metaData); + + sender.Send(replyMessage); + + var encoded = socket.GetEncodedMessage(); + this.Assent(encoded, _configuration); + } + + [Fact] + public void Input_cell_honors_custom_polyglot_notebook_metadata() + { + var socket = new TextSocket(); + var sender = new MessageSender(socket, new SignatureValidator("key", "HMACSHA256")); + var transient = new Dictionary { { "display_id", "none" } }; + var output = "some result"; + var executeResult = new ExecuteResult( + 12, + transient: transient, + data: new Dictionary { + { "text/html", output }, + { "text/plain", output } + + }); + + var header = new Header(messageType: JupyterMessageContentTypes.ExecuteResult, messageId: Guid.Empty.ToString(), + version: "5.3", username: Constants.USERNAME, session: "test session", + date: DateTime.MinValue.ToString("yyyy-MM-ddTHH:mm:ssZ")); + + var metaData = new Dictionary + { + { "polyglot_notebook", new InputCellMetadata(kernelName: "fsharp") } }; var replyMessage = new Message(header, content: executeResult, metaData: metaData); diff --git a/src/Microsoft.DotNet.Interactive.Jupyter/CompleteRequestHandler.cs b/src/Microsoft.DotNet.Interactive.Jupyter/CompleteRequestHandler.cs index 48b7fee2cc..d1dd93d6b0 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter/CompleteRequestHandler.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter/CompleteRequestHandler.cs @@ -21,7 +21,7 @@ public CompleteRequestHandler(Kernel kernel, IScheduler scheduler = null) public async Task Handle(JupyterRequestContext context) { var completeRequest = GetJupyterRequest(context); - var targetKernelName = context.GetLanguage(); + var targetKernelName = context.GetKernelName(); var position = SourceUtilities.GetPositionFromCursorOffset(completeRequest.Code, completeRequest.CursorPosition); var command = new RequestCompletions(completeRequest.Code, position, targetKernelName); diff --git a/src/Microsoft.DotNet.Interactive.Jupyter/ExecuteRequestHandler.cs b/src/Microsoft.DotNet.Interactive.Jupyter/ExecuteRequestHandler.cs index b1f62c26e3..905837c735 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter/ExecuteRequestHandler.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter/ExecuteRequestHandler.cs @@ -31,7 +31,7 @@ public ExecuteRequestHandler(Kernel kernel, IScheduler scheduler = null) public async Task Handle(JupyterRequestContext context) { var executeRequest = GetJupyterRequest(context); - string targetKernelName = context.GetLanguage(); + var targetKernelName = context.GetKernelName(); _executionCount = executeRequest.Silent ? _executionCount : Interlocked.Increment(ref _executionCount); diff --git a/src/Microsoft.DotNet.Interactive.Jupyter/IsCompleteRequestHandler.cs b/src/Microsoft.DotNet.Interactive.Jupyter/IsCompleteRequestHandler.cs index c9be268bd5..d9be2ccd64 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter/IsCompleteRequestHandler.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter/IsCompleteRequestHandler.cs @@ -20,7 +20,7 @@ public IsCompleteRequestHandler(Kernel kernel, IScheduler scheduler = null) public async Task Handle(JupyterRequestContext context) { var isCompleteRequest = GetJupyterRequest(context); - var targetKernelName = context.GetLanguage(); + var targetKernelName = context.GetKernelName(); var command = new SubmitCode(isCompleteRequest.Code, targetKernelName, submissionType: SubmissionType.Diagnose); await SendAsync(context, command); diff --git a/src/Microsoft.DotNet.Interactive.Jupyter/JupyterRequestContextExtensions.cs b/src/Microsoft.DotNet.Interactive.Jupyter/JupyterRequestContextExtensions.cs index c1884dae92..fed8192b08 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter/JupyterRequestContextExtensions.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter/JupyterRequestContextExtensions.cs @@ -1,22 +1,28 @@ // 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 Microsoft.DotNet.Interactive.Documents.Jupyter; namespace Microsoft.DotNet.Interactive.Jupyter { public static class JupyterRequestContextExtensions { - public static string GetLanguage(this JupyterRequestContext context) + public static string GetKernelName(this JupyterRequestContext context) { - if (context.JupyterRequestMessageEnvelope.MetaData.TryGetValue(Notebook.MetadataNamespace, out var candidateMetadata) && - candidateMetadata is InputCellMetadata inputCellMetadata) + string kernelName = null; + if (context.JupyterRequestMessageEnvelope.MetaData.TryGetValue(Notebook.MetadataNamespace, out var candidateDotnetMetadata) && + candidateDotnetMetadata is InputCellMetadata dotnetMetadata) + { + kernelName = dotnetMetadata.Language; + } + + if (context.JupyterRequestMessageEnvelope.MetaData.TryGetValue(Notebook.PolyglotMetadataNamespace, out var candidatePolyglotMetadata) && + candidatePolyglotMetadata is InputCellMetadata polyglotMetadata) { - return inputCellMetadata.Language; + kernelName = polyglotMetadata.KernelName; } - return null; + return kernelName; } } } diff --git a/src/Microsoft.DotNet.Interactive.Jupyter/Messaging/MetadataExtensions.cs b/src/Microsoft.DotNet.Interactive.Jupyter/Messaging/MetadataExtensions.cs index c79b79612d..4722ec8432 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter/Messaging/MetadataExtensions.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter/Messaging/MetadataExtensions.cs @@ -19,6 +19,7 @@ public static Dictionary DeserializeMetadataFromJsonString(strin switch (property.Name) { case "dotnet_interactive": + case "polyglot_notebook": metadata[property.Name] = JsonSerializer.Deserialize(property.Value.GetRawText()); break; diff --git a/src/Microsoft.DotNet.Interactive.Jupyter/Protocol/LanguageInfo.cs b/src/Microsoft.DotNet.Interactive.Jupyter/Protocol/LanguageInfo.cs index 86a352c96a..f57791b8f5 100644 --- a/src/Microsoft.DotNet.Interactive.Jupyter/Protocol/LanguageInfo.cs +++ b/src/Microsoft.DotNet.Interactive.Jupyter/Protocol/LanguageInfo.cs @@ -64,7 +64,7 @@ public LanguageInfo(string name, string version, string mimeType, string fileExt public class CSharpLanguageInfo : LanguageInfo { - public CSharpLanguageInfo(string version = "10.0") : base("C#", version, "text/x-csharp", ".cs", pygmentsLexer: "csharp") + public CSharpLanguageInfo(string version = "11.0") : base("C#", version, "text/x-csharp", ".cs", pygmentsLexer: "csharp") { } } @@ -73,7 +73,6 @@ public class FSharpLanguageInfo : LanguageInfo { public FSharpLanguageInfo(string version = "6.0") : base("F#", version, "text/x-fsharp", ".fs", pygmentsLexer: "fsharp") { - } } @@ -81,7 +80,6 @@ public class PowerShellLanguageInfo : LanguageInfo { public PowerShellLanguageInfo(string version = "7.0") : base("PowerShell", version, "text/x-powershell", ".ps1", pygmentsLexer: "powershell") { - } } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Interactive.Mermaid/MermaidKernel.cs b/src/Microsoft.DotNet.Interactive.Mermaid/MermaidKernel.cs index 2df52b233f..b46de16249 100644 --- a/src/Microsoft.DotNet.Interactive.Mermaid/MermaidKernel.cs +++ b/src/Microsoft.DotNet.Interactive.Mermaid/MermaidKernel.cs @@ -11,8 +11,10 @@ public class MermaidKernel : Kernel, { private ChooseMermaidKernelDirective? _chooseKernelDirective; - public MermaidKernel() : base("mermaid", languageName:"Mermaid") + public MermaidKernel() : base("mermaid") { + KernelInfo.LanguageName = "Mermaid"; + KernelInfo.DisplayName = "Mermaid"; } Task IKernelCommandHandler.HandleAsync(SubmitCode command, KernelInvocationContext context) diff --git a/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellKernel.cs b/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellKernel.cs index d3552f0899..98900caffa 100644 --- a/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellKernel.cs +++ b/src/Microsoft.DotNet.Interactive.PowerShell/PowerShellKernel.cs @@ -40,6 +40,7 @@ public class PowerShellKernel : private const string PSModulePathEnvName = "PSModulePath"; internal const string DefaultKernelName = "pwsh"; + internal const string LanguageName = "PowerShell"; private static readonly CmdletInfo _outDefaultCommand; private static readonly PropertyInfo _writeStreamProperty; @@ -80,8 +81,10 @@ static PowerShellKernel() _addAccelerator = acceleratorType?.GetMethod("Add", new[] { typeof(string), typeof(Type) }); } - public PowerShellKernel() : base(DefaultKernelName, "PowerShell") + public PowerShellKernel() : base(DefaultKernelName) { + KernelInfo.LanguageName = LanguageName; + KernelInfo.DisplayName = LanguageName; _psHost = new PSKernelHost(this); _lazyPwsh = new Lazy(CreatePowerShell); _suppressedValueInfoNames = GetAllValueInfos().Select(v => v.Name).ToHashSet(); diff --git a/src/Microsoft.DotNet.Interactive.SqlServer/ToolsServiceKernel.cs b/src/Microsoft.DotNet.Interactive.SqlServer/ToolsServiceKernel.cs index cdb7f0c11e..783e4f6696 100644 --- a/src/Microsoft.DotNet.Interactive.SqlServer/ToolsServiceKernel.cs +++ b/src/Microsoft.DotNet.Interactive.SqlServer/ToolsServiceKernel.cs @@ -48,8 +48,9 @@ public abstract class ToolsServiceKernel : private readonly Dictionary _variables = new(StringComparer.Ordinal); - protected ToolsServiceKernel(string name, ToolsServiceClient client, string languageName) : base(name, languageName) + protected ToolsServiceKernel(string name, ToolsServiceClient client, string languageName) : base(name) { + KernelInfo.LanguageName = languageName; var filePath = Path.GetTempFileName(); TempFileUri = new Uri(filePath); diff --git a/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.Command_contract_has_not_been_broken.approved.SendEditableCode.json b/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.Command_contract_has_not_been_broken.approved.SendEditableCode.json index cf3e56e5bb..b8898a3b05 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.Command_contract_has_not_been_broken.approved.SendEditableCode.json +++ b/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.Command_contract_has_not_been_broken.approved.SendEditableCode.json @@ -3,7 +3,7 @@ "id": "command-id", "commandType": "SendEditableCode", "command": { - "language": "language", + "kernelName": "someKernelName", "code": "code", "targetKernelName": "vscode", "originUri": null, diff --git a/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.Event_contract_has_not_been_broken.approved.KernelInfoProduced.json b/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.Event_contract_has_not_been_broken.approved.KernelInfoProduced.json index 26f2526ea0..33757988cc 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.Event_contract_has_not_been_broken.approved.KernelInfoProduced.json +++ b/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.Event_contract_has_not_been_broken.approved.KernelInfoProduced.json @@ -6,6 +6,7 @@ ], "languageName": "JavaScript", "languageVersion": null, + "displayName": "JavaScript", "localName": "javascript", "uri": "kernel://vscode/javascript", "remoteUri": null, diff --git a/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.cs b/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.cs index c4b3bf3de0..15692e8695 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.cs +++ b/src/Microsoft.DotNet.Interactive.Tests/Connection/SerializationTests.cs @@ -182,7 +182,7 @@ IEnumerable commands() yield return new RequestSignatureHelp("sig-help-contents", new LinePosition(1, 2)); - yield return new SendEditableCode("language", "code"); + yield return new SendEditableCode("someKernelName", "code"); yield return new SubmitCode("123", "csharp", SubmissionType.Run); @@ -312,8 +312,10 @@ IEnumerable events() new LinePositionSpan(new LinePosition(1, 2), new LinePosition(3, 4))); yield return new KernelInfoProduced( - new KernelInfo("javascript", "JavaScript", aliases: new[] { "js" }) + new KernelInfo("javascript", aliases: new[] { "js" }) { + LanguageName = "JavaScript", + DisplayName = "JavaScript", Uri = new Uri("kernel://vscode/javascript"), SupportedDirectives = new[] { diff --git a/src/Microsoft.DotNet.Interactive.Tests/KernelInfoTests.cs b/src/Microsoft.DotNet.Interactive.Tests/KernelInfoTests.cs index daf8b5bb24..f7c96207e1 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/KernelInfoTests.cs +++ b/src/Microsoft.DotNet.Interactive.Tests/KernelInfoTests.cs @@ -144,7 +144,7 @@ public async Task proxyKernel_kernelInfo_is_updated_to_reflect_remote_kernelInfo using var remoteCompositeKernel = new CompositeKernel("REMOTE") { proxiedCsharpKernel, - new FakeKernel("fsharp", languageName: "fsharp") + new FakeKernel("fsharp", languageName: "fsharp", displayName: "Eff Sharp") }; ConnectHost.ConnectInProcessHost( @@ -177,6 +177,7 @@ await localCompositeKernel.SendAsync( .BeEquivalentTo(new { LanguageName = "fsharp", + DisplayName = "Eff Sharp", RemoteUri = remoteKernelUri }, c => c.ExcludingMissingMembers()); } diff --git a/src/Microsoft.DotNet.Interactive.Tests/Utility/FakeKernel.cs b/src/Microsoft.DotNet.Interactive.Tests/Utility/FakeKernel.cs index b38ab3f037..d3f5599bab 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/Utility/FakeKernel.cs +++ b/src/Microsoft.DotNet.Interactive.Tests/Utility/FakeKernel.cs @@ -12,8 +12,10 @@ public class FakeKernel : Kernel, IKernelCommandHandler { - public FakeKernel([CallerMemberName] string name = null, string languageName = null) : base(name, languageName ?? name) + public FakeKernel([CallerMemberName] string name = null, string languageName = null, string displayName = null) : base(name) { + KernelInfo.LanguageName = languageName ?? name; + KernelInfo.DisplayName = displayName ?? name; } public KernelCommandInvocation Handle { get; set; } @@ -24,4 +26,4 @@ Task IKernelCommandHandler.HandleAsync(SubmitCode command, KernelInv return Task.CompletedTask; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.Interactive/Commands/KernelCommand.cs b/src/Microsoft.DotNet.Interactive/Commands/KernelCommand.cs index aa222c1fad..032ea08ee1 100644 --- a/src/Microsoft.DotNet.Interactive/Commands/KernelCommand.cs +++ b/src/Microsoft.DotNet.Interactive/Commands/KernelCommand.cs @@ -14,7 +14,7 @@ namespace Microsoft.DotNet.Interactive.Commands; public abstract class KernelCommand { protected KernelCommand( - string targetKernelName = null, + string targetKernelName = null, KernelCommand parent = null) { Properties = new Dictionary(StringComparer.InvariantCultureIgnoreCase); @@ -26,7 +26,7 @@ protected KernelCommand( } } - [JsonIgnore] + [JsonIgnore] public KernelCommandInvocation Handler { get; set; } [JsonIgnore] @@ -44,12 +44,12 @@ protected KernelCommand( public Uri DestinationUri { get; set; } [JsonIgnore] - internal SchedulingScope SchedulingScope { get; set; } + internal SchedulingScope SchedulingScope { get; set; } [JsonIgnore] internal bool? ShouldPublishCompletionEvent { get; set; } - [JsonIgnore] + [JsonIgnore] public ParseResult KernelChooserParseResult { get; internal set; } [JsonIgnore] diff --git a/src/Microsoft.DotNet.Interactive/Commands/SendEditableCode.cs b/src/Microsoft.DotNet.Interactive/Commands/SendEditableCode.cs index bb59c7618b..f7be641319 100644 --- a/src/Microsoft.DotNet.Interactive/Commands/SendEditableCode.cs +++ b/src/Microsoft.DotNet.Interactive/Commands/SendEditableCode.cs @@ -5,13 +5,13 @@ namespace Microsoft.DotNet.Interactive.Commands { public class SendEditableCode : KernelCommand { - public string Language { get; } + public string KernelName { get; } public string Code { get; } - public SendEditableCode(string language, string code, string targetKernelName = "vscode") + public SendEditableCode(string kernelName, string code, string targetKernelName = "vscode") : base(targetKernelName) { - Language = language; + KernelName = kernelName; Code = code; } } diff --git a/src/Microsoft.DotNet.Interactive/CompositeKernel.cs b/src/Microsoft.DotNet.Interactive/CompositeKernel.cs index 0c20b661cb..dd88ceffa7 100644 --- a/src/Microsoft.DotNet.Interactive/CompositeKernel.cs +++ b/src/Microsoft.DotNet.Interactive/CompositeKernel.cs @@ -90,7 +90,7 @@ public void Add(Kernel kernel, IEnumerable aliases = null) RegisterForDisposal(kernel.KernelEvents.Subscribe(PublishEvent)); RegisterForDisposal(kernel); - + if (KernelInvocationContext.Current is { } current) { var kernelInfoProduced = new KernelInfoProduced(kernel.KernelInfo, current.Command); @@ -182,7 +182,7 @@ private protected override Kernel GetHandlingKernel( { } - else + else { targetKernelName = DefaultKernelName; diff --git a/src/Microsoft.DotNet.Interactive/Connection/ProxyKernel.cs b/src/Microsoft.DotNet.Interactive/Connection/ProxyKernel.cs index f9f8dd38b6..2501c8cb71 100644 --- a/src/Microsoft.DotNet.Interactive/Connection/ProxyKernel.cs +++ b/src/Microsoft.DotNet.Interactive/Connection/ProxyKernel.cs @@ -69,6 +69,7 @@ private void UpdateKernelInfoFromEvent(KernelInfoProduced kernelInfoProduced) _requiresRequestKernelInfoOnFirstCommand = false; KernelInfo.LanguageName = kernelInfoProduced.KernelInfo.LanguageName; KernelInfo.LanguageVersion = kernelInfoProduced.KernelInfo.LanguageVersion; + KernelInfo.DisplayName = kernelInfoProduced.KernelInfo.DisplayName; ((HashSet)KernelInfo.SupportedDirectives).UnionWith(kernelInfoProduced.KernelInfo.SupportedDirectives); ((HashSet)KernelInfo.SupportedKernelCommands).UnionWith(kernelInfoProduced.KernelInfo.SupportedKernelCommands); } @@ -117,7 +118,7 @@ private Task HandleByForwardingToRemoteAsync(KernelCommand command, KernelInvoca { if (!task.GetIsCompletedSuccessfully()) { - if (task.Exception is {} ex) + if (task.Exception is { } ex) { completionSource.TrySetException(ex); } @@ -127,7 +128,7 @@ private Task HandleByForwardingToRemoteAsync(KernelCommand command, KernelInvoca return completionSource.Task.ContinueWith(te => { command.TargetKernelName = targetKernelName; - + if (te.Result is CommandFailed cf) { context.Fail(command, cf.Exception, cf.Message); diff --git a/src/Microsoft.DotNet.Interactive/HtmlKernel.cs b/src/Microsoft.DotNet.Interactive/HtmlKernel.cs index 07b985eb38..619b5af23f 100644 --- a/src/Microsoft.DotNet.Interactive/HtmlKernel.cs +++ b/src/Microsoft.DotNet.Interactive/HtmlKernel.cs @@ -14,8 +14,10 @@ public class HtmlKernel : { public const string DefaultKernelName = "html"; - public HtmlKernel() : base(DefaultKernelName, "HTML") + public HtmlKernel() : base(DefaultKernelName) { + KernelInfo.LanguageName = "HTML"; + KernelInfo.DisplayName = "HTML"; } Task IKernelCommandHandler.HandleAsync(SubmitCode command, KernelInvocationContext context) diff --git a/src/Microsoft.DotNet.Interactive/JavaScriptKernel.cs b/src/Microsoft.DotNet.Interactive/JavaScriptKernel.cs index 9dbb75a168..88b152f2cf 100644 --- a/src/Microsoft.DotNet.Interactive/JavaScriptKernel.cs +++ b/src/Microsoft.DotNet.Interactive/JavaScriptKernel.cs @@ -2,11 +2,9 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Text.Json; using System.Threading.Tasks; using Microsoft.DotNet.Interactive.Commands; using Microsoft.DotNet.Interactive.Connection; -using Microsoft.DotNet.Interactive.Events; using Microsoft.DotNet.Interactive.ValueSharing; namespace Microsoft.DotNet.Interactive; @@ -18,10 +16,14 @@ public class JavaScriptKernel : { private readonly KernelClientBase _client; public const string DefaultKernelName = "javascript"; + private const string LanguageName = "javascript"; + private const string DisplayName = "JavaScript"; public JavaScriptKernel(KernelClientBase client = null) : base(DefaultKernelName) { _client = client; + KernelInfo.LanguageName = LanguageName; + KernelInfo.DisplayName = DisplayName; } Task IKernelCommandHandler.HandleAsync( diff --git a/src/Microsoft.DotNet.Interactive/Kernel.cs b/src/Microsoft.DotNet.Interactive/Kernel.cs index 7dd13cd2fb..329e67f8ca 100644 --- a/src/Microsoft.DotNet.Interactive/Kernel.cs +++ b/src/Microsoft.DotNet.Interactive/Kernel.cs @@ -25,6 +25,7 @@ using Disposable = System.Reactive.Disposables.Disposable; using Microsoft.DotNet.Interactive.Formatting; using System.Text.Json; +using Microsoft.CodeAnalysis; namespace Microsoft.DotNet.Interactive { @@ -47,10 +48,7 @@ public abstract partial class Kernel : private int _countOfLanguageServiceCommandsInFlight = 0; private readonly KernelInfo _kernelInfo; - protected Kernel( - string name, - string languageName = null, - string languageVersion = null) + protected Kernel(string name) { if (string.IsNullOrWhiteSpace(name)) { @@ -73,7 +71,7 @@ protected Kernel( GetType(), GetImplementedCommandHandlerTypesFor)); - _kernelInfo = InitializeKernelInfo(name, languageName, languageVersion); + _kernelInfo = InitializeKernelInfo(name); var counter = _kernelEvents.Subscribe(IncrementSubmissionCount); @@ -94,13 +92,24 @@ void IncrementSubmissionCount(KernelEvent e) } } - private KernelInfo InitializeKernelInfo(string name, string languageName, string languageVersion) + [Obsolete("This constructor has been deprecated. Please use the other constructor and directly set any remaining properties directly on the " + nameof(KernelInfo) + " property.")] + protected Kernel( + string name, + string languageName, + string languageVersion) + : this(name) + { + KernelInfo.LanguageName = languageName; + KernelInfo.LanguageVersion = languageVersion; + } + + private KernelInfo InitializeKernelInfo(string name) { var supportedKernelCommands = _supportedCommandTypes.Select(t => new KernelCommandInfo(t.Name)).ToArray(); var supportedDirectives = Directives.Select(d => new KernelDirectiveInfo(d.Name)).ToArray(); - return new KernelInfo(name, languageName, languageVersion) + return new KernelInfo(name, aliases: null) { SupportedKernelCommands = supportedKernelCommands, SupportedDirectives = supportedDirectives, @@ -174,7 +183,7 @@ private bool TrySplitCommand( } if (command.DestinationUri is { } && - handlingKernel.KernelInfo.Uri is { } && + handlingKernel.KernelInfo.Uri is { } && command.DestinationUri == handlingKernel.KernelInfo.Uri) { command.SchedulingScope = handlingKernel.SchedulingScope; @@ -563,9 +572,8 @@ protected internal KernelScheduler Scheduler { return true; } - + return inner.RoutingSlip.StartsWith(outer.RoutingSlip); - }); RegisterForDisposal(scheduler); SetScheduler(scheduler); @@ -669,7 +677,7 @@ protected internal void PublishEvent(KernelEvent kernelEvent) { kernelEvent.RoutingSlip.Stamp(KernelInfo.Uri); } - + _kernelEvents.OnNext(kernelEvent); } diff --git a/src/Microsoft.DotNet.Interactive/KernelExtensions.cs b/src/Microsoft.DotNet.Interactive/KernelExtensions.cs index 1fc67d086b..4e341ca1f1 100644 --- a/src/Microsoft.DotNet.Interactive/KernelExtensions.cs +++ b/src/Microsoft.DotNet.Interactive/KernelExtensions.cs @@ -66,10 +66,10 @@ public static IEnumerable FindKernels(this Kernel kernel, Func predicate(c) ? new[] { kernel }.Concat(c.ChildKernels.Where(predicate)) : c.ChildKernels.Where(predicate), + CompositeKernel c => predicate(c) ? new[] { kernel }.Concat(c.ChildKernels.Where(predicate)) : c.ChildKernels.Where(predicate), _ when predicate(kernel) => new[] { kernel }, _ => Enumerable.Empty() }; @@ -124,7 +124,7 @@ static KernelInfoCollection CreateKernelInfos(CompositeKernel kernel) kernelAliases.Add(alias[2..]); } - kernelInfos.Add(new Documents.KernelInfo(kernelChooser.Name[2..], kernelAliases)); + kernelInfos.Add(new Documents.KernelInfo(kernelChooser.Name[2..], aliases: kernelAliases)); } return kernelInfos; @@ -198,7 +198,7 @@ public static T UseValueSharing(this T kernel) where T : Kernel var sourceValueNameArg = new Argument( "name", "The name of the value to share. (This is also the default name value created in the destination kernel, unless --as is used to specify a different one.)"); - + sourceValueNameArg.AddCompletions(_ => { if (kernel.ParentKernel is { } composite) @@ -245,8 +245,8 @@ public static T UseValueSharing(this T kernel) where T : Kernel var mimeTypeOption = new Option("--mime-type", "Share the value as a string formatted to the specified MIME type.") .AddCompletions( - JsonFormatter.MimeType, - HtmlFormatter.MimeType, + JsonFormatter.MimeType, + HtmlFormatter.MimeType, PlainTextFormatter.MimeType); var asOption = new Option("--as", "The name to give the the value in the importing kernel."); @@ -270,9 +270,9 @@ public static T UseValueSharing(this T kernel) where T : Kernel if (kernel.FindKernelByName(from) is { } fromKernel) { await fromKernel.GetValueAndSendTo( - kernel, - valueName, - mimeType, + kernel, + valueName, + mimeType, importAsName); } else @@ -290,7 +290,7 @@ internal static async Task GetValueAndSendTo( this Kernel fromKernel, Kernel toKernel, string fromName, - string requestedMimeType, + string requestedMimeType, string toName) { var supportedRequestValue = fromKernel.SupportsCommandType(typeof(RequestValue)); @@ -325,7 +325,7 @@ await toKernel.SendAsync( } } } - + public static TKernel UseWho(this TKernel kernel) where TKernel : Kernel { diff --git a/src/Microsoft.DotNet.Interactive/KernelHost.cs b/src/Microsoft.DotNet.Interactive/KernelHost.cs index b5fa7cf48d..8d561f991c 100644 --- a/src/Microsoft.DotNet.Interactive/KernelHost.cs +++ b/src/Microsoft.DotNet.Interactive/KernelHost.cs @@ -10,7 +10,6 @@ using System.Reactive.Concurrency; using System.Threading; using System.Threading.Tasks; - using Microsoft.DotNet.Interactive.Commands; using Microsoft.DotNet.Interactive.Connection; using Microsoft.DotNet.Interactive.Events; @@ -75,8 +74,8 @@ public async Task ConnectAsync() _eventLoop = new EventLoopScheduler(a => new Thread(a) { Name = "KernelHost command dispatcher" - }); - + }); + _kernelEventSubscription = _kernel.KernelEvents.Subscribe(e => { if (e is ReturnValueProduced { Value: DisplayedValue }) @@ -101,11 +100,11 @@ public async Task ConnectAsync() var _ = _defaultSender.SendAsync(e, _cancellationTokenSource.Token); }); - _receiver.Subscribe( commandOrEvent => + _receiver.Subscribe(commandOrEvent => { if (commandOrEvent.IsParseError) { - var _= _defaultSender.SendAsync(commandOrEvent.Event, _cancellationTokenSource.Token); + var _ = _defaultSender.SendAsync(commandOrEvent.Event, _cancellationTokenSource.Token); } else if (commandOrEvent.Command is { }) { @@ -156,7 +155,7 @@ public void Dispose() { _eventLoop?.Dispose(); _kernelEventSubscription?.Dispose(); - + if (_cancellationTokenSource.Token.CanBeCanceled) { _cancellationTokenSource.Cancel(); diff --git a/src/Microsoft.DotNet.Interactive/KernelInfo.cs b/src/Microsoft.DotNet.Interactive/KernelInfo.cs index 4a91f16dcf..25ab9c4e35 100644 --- a/src/Microsoft.DotNet.Interactive/KernelInfo.cs +++ b/src/Microsoft.DotNet.Interactive/KernelInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; namespace Microsoft.DotNet.Interactive { @@ -13,11 +14,8 @@ public class KernelInfo private readonly HashSet _supportedKernelCommands = new(); private readonly HashSet _supportedDirectives = new(); - public KernelInfo( - string localName, - string? languageName = null, - string? languageVersion = null, - string[]? aliases = null) + [JsonConstructor] + public KernelInfo(string localName, string[]? aliases = null) { if (string.IsNullOrWhiteSpace(localName)) { @@ -30,8 +28,7 @@ public KernelInfo( } LocalName = localName; - LanguageName = languageName; - LanguageVersion = languageVersion; + DisplayName = localName; NameAndAliases = new HashSet { LocalName }; Uri = new Uri($"kernel://local/{LocalName}"); @@ -41,15 +38,35 @@ public KernelInfo( } } + [Obsolete("This constructor has been deprecated. Please use the other constructor and directly set any remaining properties.")] + public KernelInfo( + string localName, + string? languageName, + string? languageVersion, + string[]? aliases) + : this(localName, aliases) + { + LanguageName = languageName; + LanguageVersion = languageVersion; + } + + private string CreateDisplayName() + { + var displayName = $"{LanguageName} {LanguageVersion}".Trim(); + return displayName; + } + public string[] Aliases { get => NameAndAliases.Where(n => n != LocalName).ToArray(); init => NameAndAliases.UnionWith(value); } - public string? LanguageName { get; internal set; } + public string? LanguageName { get; set; } + + public string? LanguageVersion { get; set; } - public string? LanguageVersion { get; internal set; } + public string DisplayName { get; set; } public string LocalName { get; } diff --git a/src/Microsoft.DotNet.Interactive/KeyValueStoreKernel.cs b/src/Microsoft.DotNet.Interactive/KeyValueStoreKernel.cs index ef09a382aa..fd4ad709d5 100644 --- a/src/Microsoft.DotNet.Interactive/KeyValueStoreKernel.cs +++ b/src/Microsoft.DotNet.Interactive/KeyValueStoreKernel.cs @@ -29,6 +29,7 @@ public class KeyValueStoreKernel : public KeyValueStoreKernel(string name = DefaultKernelName) : base(name) { + KernelInfo.DisplayName = "Value Storage"; } Task IKernelCommandHandler.HandleAsync(RequestValueInfos command, KernelInvocationContext context) diff --git a/src/Microsoft.DotNet.Interactive/KqlDiscoverabilityKernel.cs b/src/Microsoft.DotNet.Interactive/KqlDiscoverabilityKernel.cs index 96eb011356..377a1aa2cc 100644 --- a/src/Microsoft.DotNet.Interactive/KqlDiscoverabilityKernel.cs +++ b/src/Microsoft.DotNet.Interactive/KqlDiscoverabilityKernel.cs @@ -18,12 +18,14 @@ public class KqlDiscoverabilityKernel : private readonly HashSet _kernelNameFilter; private const string DefaultKernelName = "kql"; - public KqlDiscoverabilityKernel() : base(DefaultKernelName, languageName: "KQL") + public KqlDiscoverabilityKernel() : base(DefaultKernelName) { _kernelNameFilter = new HashSet { "MsKqlKernel" }; + KernelInfo.LanguageName = "KQL"; + KernelInfo.DisplayName = "Kusto Query Language"; } Task IKernelCommandHandler.HandleAsync(SubmitCode command, KernelInvocationContext context) diff --git a/src/Microsoft.DotNet.Interactive/SqlDiscoverabilityKernel.cs b/src/Microsoft.DotNet.Interactive/SqlDiscoverabilityKernel.cs index 711db7a523..a74228cbe5 100644 --- a/src/Microsoft.DotNet.Interactive/SqlDiscoverabilityKernel.cs +++ b/src/Microsoft.DotNet.Interactive/SqlDiscoverabilityKernel.cs @@ -18,13 +18,15 @@ public class SqlDiscoverabilityKernel : private readonly HashSet _kernelNameFilter; public const string DefaultKernelName = "sql"; - public SqlDiscoverabilityKernel() : base(DefaultKernelName, languageName: "SQL") + public SqlDiscoverabilityKernel() : base(DefaultKernelName) { _kernelNameFilter = new HashSet { "MsSqlKernel", "SQLiteKernel" }; + KernelInfo.LanguageName = "SQL"; + KernelInfo.DisplayName = "SQL"; } Task IKernelCommandHandler.HandleAsync(SubmitCode command, KernelInvocationContext context) diff --git a/src/dotnet-interactive-vscode-common/src/commands.ts b/src/dotnet-interactive-vscode-common/src/commands.ts index 97ef2d697b..a19f1584d8 100644 --- a/src/dotnet-interactive-vscode-common/src/commands.ts +++ b/src/dotnet-interactive-vscode-common/src/commands.ts @@ -6,20 +6,21 @@ import * as path from 'path'; import { acquireDotnetInteractive } from './acquisition'; import { InstallInteractiveArgs, InteractiveLaunchOptions } from './interfaces'; import { ClientMapper } from './clientMapper'; -import { getEol, isInsidersBuild, toNotebookDocument } from './vscodeUtilities'; -import { DotNetPathManager, KernelIdForJupyter } from './extension'; +import { getEol, toNotebookDocument } from './vscodeUtilities'; +import { DotNetPathManager } from './extension'; import { computeToolInstallArguments, executeSafe, executeSafeAndLog, extensionToDocumentType, getVersionNumber } from './utilities'; import * as notebookControllers from '../notebookControllers'; -import * as ipynbUtilities from './ipynbUtilities'; +import * as metadataUtilities from './metadataUtilities'; import { ReportChannel } from './interfaces/vscode-like'; -import { jupyterViewType } from './interactiveNotebook'; import { NotebookParserServer } from './notebookParserServer'; import * as versionSpecificFunctions from '../versionSpecificFunctions'; import { PromiseCompletionSource } from './dotnet-interactive/promiseCompletionSource'; +import * as constants from './constants'; + export function registerAcquisitionCommands(context: vscode.ExtensionContext, diagnosticChannel: ReportChannel) { - const dotnetConfig = vscode.workspace.getConfiguration('dotnet-interactive'); + const dotnetConfig = vscode.workspace.getConfiguration(constants.DotnetConfigurationSectionName); const minDotNetInteractiveVersion = dotnetConfig.get('minimumInteractiveToolVersion'); const interactiveToolSource = dotnetConfig.get('interactiveToolSource'); @@ -154,6 +155,7 @@ export function registerKernelCommands(context: vscode.ExtensionContext, clientM }, (_progress, _token) => restartCompletionSource.promise); await vscode.commands.executeCommand('polyglot-notebook.stopCurrentNotebookKernel', notebook); + await vscode.commands.executeCommand('polyglot-notebook.resetNotebookKernelCollection', notebook); const _client = await clientMapper.getOrAddClient(notebook.uri); restartCompletionSource.resolve(); await vscode.commands.executeCommand('workbench.notebook.layout.webview.reset', notebook.uri); @@ -253,53 +255,51 @@ export function registerFileCommands(context: vscode.ExtensionContext, parserSer async function newNotebook(extension: string): Promise { const viewType = extension === '.dib' || extension === '.dotnet-interactive' - ? 'polyglot-notebook' - : jupyterViewType; + ? constants.NotebookViewType + : constants.JupyterViewType; // get language - const newNotebookCSharp = `C#`; - const newNotebookFSharp = `F#`; - const newNotebookPowerShell = `PowerShell`; - const notebookLanguage = await vscode.window.showQuickPick([newNotebookCSharp, newNotebookFSharp, newNotebookPowerShell], { title: 'Default Language' }); + const languagesAndKernelNames: { [key: string]: string } = { + 'C#': 'csharp', + 'F#': 'fsharp', + 'PowerShell': 'pwsh', + }; + + const newLanguageOptions: string[] = []; + for (const languageName in languagesAndKernelNames) { + newLanguageOptions.push(languageName); + } + + const notebookLanguage = await vscode.window.showQuickPick(newLanguageOptions, { title: 'Default Language' }); if (!notebookLanguage) { return; } - const ipynbLanguageName = ipynbUtilities.mapIpynbLanguageName(notebookLanguage); - const cellMetadata = { - custom: { - metadata: { - polyglot_notebook: { - language: ipynbLanguageName - } - } - } + const kernelName = languagesAndKernelNames[notebookLanguage]; + const notebookCellMetadata: metadataUtilities.NotebookCellMetadata = { + kernelName }; - const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '', `dotnet-interactive.${ipynbLanguageName}`); - cell.metadata = cellMetadata; - const documentMetadata = { - custom: { - metadata: { - kernelspec: { - display_name: `.NET (${notebookLanguage})`, - language: notebookLanguage, - name: `.net-${ipynbLanguageName}` - }, - language_info: { - name: notebookLanguage + const rawCellMetadata = metadataUtilities.getRawNotebookCellMetadataFromNotebookCellMetadata(notebookCellMetadata); + const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '', constants.CellLanguageIdentifier); + cell.metadata = rawCellMetadata; + const notebookDocumentMetadata: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: kernelName, + items: [ + { + name: kernelName, + aliases: [], + languageName: kernelName // it just happens that the kernel names we allow are also the language names } - } + ] } }; + + const createForIpynb = viewType === constants.JupyterViewType; + const rawNotebookMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata, createForIpynb); const content = new vscode.NotebookData([cell]); - content.metadata = documentMetadata; + content.metadata = rawNotebookMetadata; const notebook = await vscode.workspace.openNotebookDocument(viewType, content); - - // The document metadata isn't preserved from the previous call. This is addressed in the following issues: - // - https://github.com/microsoft/vscode-jupyter/issues/6187 - // - https://github.com/microsoft/vscode-jupyter/issues/5622 - // In the meantime, the metadata can be set again to ensure it's persisted. - const _succeeded = await versionSpecificFunctions.replaceNotebookMetadata(notebook.uri, documentMetadata); const _editor = await vscode.window.showNotebookDocument(notebook); } @@ -326,8 +326,8 @@ export function registerFileCommands(context: vscode.ExtensionContext, parserSer async function openNotebook(uri: vscode.Uri): Promise { const extension = path.extname(uri.toString()); const viewType = extension === '.dib' || extension === '.dotnet-interactive' - ? 'polyglot-notebook' - : jupyterViewType; + ? constants.NotebookViewType + : constants.JupyterViewType; await vscode.commands.executeCommand('vscode.openWith', uri, viewType); } @@ -360,7 +360,7 @@ export function registerFileCommands(context: vscode.ExtensionContext, parserSer export async function selectDotNetInteractiveKernelForJupyter(): Promise { const extension = 'ms-dotnettools.dotnet-interactive-vscode'; - const id = KernelIdForJupyter; + const id = constants.JupyterKernelId; await vscode.commands.executeCommand('notebook.selectKernel', { extension, id }); } diff --git a/src/dotnet-interactive-vscode-common/src/constants.ts b/src/dotnet-interactive-vscode-common/src/constants.ts new file mode 100644 index 0000000000..73faa3ae8b --- /dev/null +++ b/src/dotnet-interactive-vscode-common/src/constants.ts @@ -0,0 +1,24 @@ +// 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. + +// languages +export const CellLanguageIdentifier = 'polyglot-notebook'; + +// notebook controllers +export const NotebookControllerId = 'polyglot-notebook'; +export const LegacyNotebookControllerId = 'polyglot-notebook-legacy'; +export const JupyterNotebookControllerId = 'polyglot-notebook-for-jupyter'; + +// notebook kernel +export const JupyterKernelId = 'polyglot-notebook-for-jupyter'; + +// view types +export const NotebookViewType = 'polyglot-notebook'; +export const JupyterNotebookViewType = 'polyglot-notebook-jupyter'; +export const JupyterViewType = 'jupyter-notebook'; +export const LegacyNotebookViewType = 'polyglot-notebook-legacy'; + +// other +export const DotnetConfigurationSectionName = 'dotnet-interactive'; +export const PolyglotConfigurationSectionName = 'polyglot-notebook'; +export const InteractiveWindowControllerId = 'polyglot-notebook-window'; diff --git a/src/dotnet-interactive-vscode-common/src/documentSemanticTokenProvider.ts b/src/dotnet-interactive-vscode-common/src/documentSemanticTokenProvider.ts new file mode 100644 index 0000000000..4827cc1390 --- /dev/null +++ b/src/dotnet-interactive-vscode-common/src/documentSemanticTokenProvider.ts @@ -0,0 +1,131 @@ +// 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. + +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import * as metadataUtilities from './metadataUtilities'; +import { DynamicGrammarSemanticTokenProvider, VSCodeExtensionLike } from './dynamicGrammarSemanticTokenProvider'; +import * as constants from './constants'; +import * as versionSpecificFunctions from '../versionSpecificFunctions'; + +// https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide#standard-token-types-and-modifiers +const defaultTokenTypes = [ + 'namespace', // For identifiers that declare or reference a namespace, module, or package. + 'class', // For identifiers that declare or reference a class type. + 'enum', // For identifiers that declare or reference an enumeration type. + 'interface', // For identifiers that declare or reference an interface type. + 'struct', // For identifiers that declare or reference a struct type. + 'typeParameter', // For identifiers that declare or reference a type parameter. + 'type', // For identifiers that declare or reference a type that is not covered above. + 'parameter', // For identifiers that declare or reference a function or method parameters. + 'variable', // For identifiers that declare or reference a local or global variable. + 'property', // For identifiers that declare or reference a member property, member field, or member variable. + 'enumMember', // For identifiers that declare an enumeration property, constant, or member. + 'event', // For identifiers that declare an event property. + 'function', // For identifiers that declare a function. + 'method', // For identifiers that declare a member function or method. + 'macro', // For identifiers that declare a macro. + 'label', // For identifiers that declare a label. + 'comment', // For tokens that represent a comment. + 'string', // For tokens that represent a string literal. + 'keyword', // For tokens that represent a language keyword. + 'number', // For tokens that represent a number literal. + 'regexp', // For tokens that represent a regular expression literal. + 'operator', // For tokens that represent an operator. +]; + +// default semantic token modifiers +const defaultTokenModifiers = [ + 'declaration', // For declarations of symbols. + 'definition', // For definitions of symbols, for example, in header files. + 'readonly', // For readonly variables and member fields (constants). + 'static', // For class members (static members). + 'deprecated', // For symbols that should no longer be used. + 'abstract', // For types and member functions that are abstract. + 'async', // For functions that are marked async. + 'modification', // For variable references where the variable is assigned to. + 'documentation', // For occurrences of symbols in documentation. + 'defaultLibrary', // For symbols that are part of the standard library. +]; + +export const selector: vscode.DocumentSelector = [ + { language: constants.CellLanguageIdentifier }, +]; + +export class DocumentSemanticTokensProvider implements vscode.DocumentSemanticTokensProvider { + private _dynamicTokenProvider: DynamicGrammarSemanticTokenProvider; + private _onDidChangeSemanticTokensEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + private _semanticTokensLegend: vscode.SemanticTokensLegend; + + constructor(packageJSON: any) { + const extensionData = vscode.extensions.all.map(extension => extension); + this._dynamicTokenProvider = new DynamicGrammarSemanticTokenProvider(packageJSON, extensionData, path => fs.existsSync(path), path => fs.readFileSync(path, 'utf8')); + const tokenTypes = [...defaultTokenTypes, ...this._dynamicTokenProvider.semanticTokenTypes]; + this._semanticTokensLegend = new vscode.SemanticTokensLegend(tokenTypes, defaultTokenModifiers); + } + + get semanticTokensLegend(): vscode.SemanticTokensLegend { + return this._semanticTokensLegend; + } + + get dynamicTokenProvider(): DynamicGrammarSemanticTokenProvider { + return this._dynamicTokenProvider; + } + + async init(context: vscode.ExtensionContext): Promise { + await this._dynamicTokenProvider.init(); + + context.subscriptions.push(vscode.commands.registerCommand('polyglot-notebook.refreshSemanticTokens', () => { + this.refresh(); + })); + + context.subscriptions.push(vscode.commands.registerCommand('polyglot-notebook.resetNotebookKernelCollection', async (notebook?: vscode.NotebookDocument | undefined) => { + if (notebook) { + const isIpynb = metadataUtilities.isIpynbNotebook(notebook); + const bareMetadata = metadataUtilities.createDefaultNotebookDocumentMetadata(); + const rawBareMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(bareMetadata, isIpynb); + const _succeeded = await versionSpecificFunctions.replaceNotebookMetadata(notebook.uri, rawBareMetadata); + const kernelInfos = metadataUtilities.getKernelInfosFromNotebookDocument(notebook); + this.dynamicTokenProvider.rebuildNotebookGrammar(notebook.uri, kernelInfos, true); + } + })); + } + + onDidChangeSemanticTokens: vscode.Event = this._onDidChangeSemanticTokensEmitter.event; + + refresh() { + this._onDidChangeSemanticTokensEmitter.fire(); + } + + async provideDocumentSemanticTokens(document: vscode.TextDocument, _cancellationToken: vscode.CancellationToken): Promise { + const tokensBuilder = new vscode.SemanticTokensBuilder(this.semanticTokensLegend); + const notebookDocument = vscode.workspace.notebookDocuments.find(notebook => notebook.getCells().some(cell => cell.document === document)); + if (notebookDocument) { + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(notebookDocument); + const text = document.getText(); + const cell = notebookDocument.getCells().find(cell => cell.document === document); + if (cell) { + const cellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(cell); + const cellKernelName = cellMetadata.kernelName ?? notebookMetadata.kernelInfo.defaultKernelName; + const tokens = await this._dynamicTokenProvider.getTokens(notebookDocument.uri, cellKernelName, text); + for (const token of tokens) { + try { + tokensBuilder.push( + new vscode.Range( + new vscode.Position(token.line, token.startColumn), + new vscode.Position(token.line, token.endColumn) + ), + token.tokenType, + token.tokenModifiers); + } catch (e) { + const x = e; + } + } + } + } + + // TODO: agument with real semantic tokens? + + return tokensBuilder.build(); + } +} diff --git a/src/dotnet-interactive-vscode-common/src/dynamicGrammarSemanticTokenProvider.ts b/src/dotnet-interactive-vscode-common/src/dynamicGrammarSemanticTokenProvider.ts new file mode 100644 index 0000000000..dd9470b8c3 --- /dev/null +++ b/src/dotnet-interactive-vscode-common/src/dynamicGrammarSemanticTokenProvider.ts @@ -0,0 +1,456 @@ +// 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. + +import * as contracts from './dotnet-interactive/contracts'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as oniguruma from 'vscode-oniguruma'; +import * as vscodeLike from './interfaces/vscode-like'; +import * as vsctm from 'vscode-textmate'; + +import { Logger } from './dotnet-interactive/logger'; + +const customScopePrefix = 'polyglot-notebook'; + +export interface GrammarPair { + grammarContents: string; + extension: string; +} + +export interface LanguageInfo { + languageName: string; + scopeName: string; + grammar: GrammarPair | null; +}; + +export interface SemanticToken { + line: number; + startColumn: number; + endColumn: number; + tokenType: string; + tokenModifiers: string[]; +} + +export interface VSCodeExtensionLike { + id: string; + extensionPath: string; + packageJSON: any; +} + +const languageSelector: string = '#!'; +const magicCommandSelector: string = '#!'; + +// grammars shipped with this extension +const wellKnownLanguages: { languageName: string, grammarFileName: string, aliases: string[] }[] = [ + { languageName: 'kql', grammarFileName: 'kql.tmGrammar.json', aliases: [] }, +]; + +// aliases that we might want to manually specify +const wellKnownAliases: { languageName: string, aliases: string[] }[] = [ + { languageName: 'sql', aliases: ['SQLite', 'T-SQL', 'PL/SQL'] } +]; + +export class DynamicGrammarSemanticTokenProvider { + private _documentKernelInfos: Map> = new Map(); + private _documentGrammarRegistries: Map = new Map(); + private _textMateScopeToSemanticType: Map = new Map(); + private _languageNameInfoMap: Map = new Map(); + + constructor(packageJSON: any, extensionData: VSCodeExtensionLike[], private readonly fileExists: (path: string) => boolean, private readonly fileReader: (path: string) => string) { + this.buildInstalledLanguageInfosMap(extensionData); + this.buildSemanticTokensLegendAndCustomScopes(packageJSON); + } + + get semanticTokenTypes(): string[] { + return [...this._textMateScopeToSemanticType.values()]; + } + + private buildSemanticTokensLegendAndCustomScopes(packageJSON: any) { + if (Array.isArray(packageJSON?.contributes?.semanticTokenScopes)) { + for (const semanticTokenScope of packageJSON.contributes.semanticTokenScopes) { + if (semanticTokenScope?.scopes) { + for (const scopeName in semanticTokenScope.scopes) { + // build a mapping of custom scopes + for (const textMateScope of semanticTokenScope.scopes[scopeName]) { + this._textMateScopeToSemanticType.set(textMateScope, scopeName); + } + } + } + } + } + } + + async init(): Promise { + // prepare grammar parser + const nodeModulesDir = path.join(__dirname, '..', '..', '..', 'node_modules'); + const onigWasmPath = path.join(nodeModulesDir, 'vscode-oniguruma', 'release', 'onig.wasm'); + const wasmBin = fs.readFileSync(onigWasmPath).buffer; + await oniguruma.loadWASM(wasmBin); + } + + getLanguageNameFromKernelNameOrAlias(notebookDocument: vscodeLike.NotebookDocument, kernelNameOrAlias: string): string | undefined { + // get the notebook's kernel info map so we can look up the language + const kernelInfoMap = this._documentKernelInfos.get(notebookDocument.uri); + if (kernelInfoMap) { + // first do a direct kernel name lookup + const kernelInfo = kernelInfoMap.get(kernelNameOrAlias); + if (kernelInfo) { + return kernelInfo.languageName; + } + + // no direct match found, search through the aliases + for (const kernelInfo of kernelInfoMap.values()) { + if (kernelInfo.aliases.indexOf(kernelNameOrAlias) >= 0) { + return kernelInfo.languageName; + } + } + } + + // no match found + return undefined; + } + + async getTokens(notebookUri: vscodeLike.Uri, initialKernelName: string, code: string): Promise { + let registry = this._documentGrammarRegistries.get(notebookUri); + if (!registry) { + // no grammar registry for this notebook, nothing to provide + return []; + } + + try { + const grammar = await registry.loadGrammar(`source.${customScopePrefix}.${initialKernelName}`); + if (grammar) { + let ruleStack = vsctm.INITIAL; + const semanticTokens: SemanticToken[] = []; + const lines = code.split('\n'); + for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { + const line = lines[lineIndex]; + const parsedLineTokens = grammar.tokenizeLine(line, ruleStack); + for (let tokenIndex = 0; tokenIndex < parsedLineTokens.tokens.length; tokenIndex++) { + const token = parsedLineTokens.tokens[tokenIndex]; + const tokenText = line.substring(token.startIndex, token.endIndex); + const tokenType = this.scopeNameToTokenType(token.scopes, tokenText); + if (tokenType) { + const semanticToken = { + line: lineIndex, + startColumn: token.startIndex, + endColumn: token.endIndex, + tokenType: tokenType, + tokenModifiers: [], + }; + semanticTokens.push(semanticToken); + } + } + + ruleStack = parsedLineTokens.ruleStack; + } + + return semanticTokens; + } + } catch (e) { + const x = e; + } + + // if we got here we didn't recognize the given language + return []; + } + + /** + * Update the given notebook's grammar registry with the provided language infos. + * @param notebookUri The URI of the notebook for which we're updating the grammars. + * @param kernelInfos The kernel info objects used to build the custom grammar. + */ + rebuildNotebookGrammar(notebookUri: vscodeLike.Uri, kernelInfos: contracts.KernelInfo[], replaceExistingGrammar?: boolean | undefined) { + // ensure we have a collection of + let documentKernelInfos = this._documentKernelInfos.get(notebookUri); + if (!documentKernelInfos || replaceExistingGrammar) { + // no existing KernelInfo collection, or we're explicitly replacing an existing one + documentKernelInfos = new Map(); + } + + // update that collection with new/changed values + for (const kernelInfo of kernelInfos) { + documentKernelInfos.set(kernelInfo.localName, kernelInfo); + } + + // keep that collection for next time + this._documentKernelInfos.set(notebookUri, documentKernelInfos); + + // rebuild a new grammar with it + const updatedKernelInfos = [...documentKernelInfos.values()]; + const notebookGrammarRegistry = this.createGrammarRegistry(updatedKernelInfos); + this._documentGrammarRegistries.set(notebookUri, notebookGrammarRegistry); + } + + private buildInstalledLanguageInfosMap(extensionData: VSCodeExtensionLike[]) { + // crawl all extensions for languages and grammars + this._languageNameInfoMap.clear(); + const seenLanguages: Set = new Set(); + + // grammars shipped with this extension + const grammarDir = path.join(__dirname, '..', '..', '..', 'grammars'); + for (const wellKnown of wellKnownLanguages) { + const grammarPath = path.join(grammarDir, wellKnown.grammarFileName); + const languageInfo = this.createLanguageInfoFromGrammar(normalizeLanguageName(wellKnown.languageName), `source.${wellKnown.languageName}`, grammarPath); + for (const languageNameOrAlias of [wellKnown.languageName, ...wellKnown.aliases].map(normalizeLanguageName)) { + this._languageNameInfoMap.set(languageNameOrAlias, languageInfo); + seenLanguages.add(languageNameOrAlias); + } + } + + for (let extensionIndex = 0; extensionIndex < extensionData.length; extensionIndex++) { + const extension = extensionData[extensionIndex]; + + // gather all grammars + if (Array.isArray(extension.packageJSON?.contributes?.grammars)) { + for (let grammarIndex = 0; grammarIndex < extension.packageJSON.contributes.grammars.length; grammarIndex++) { + const grammar = extension.packageJSON.contributes.grammars[grammarIndex]; + if (typeof grammar?.scopeName === 'string' && + typeof grammar?.path === 'string') { + // ensure language is in the map + const languageName = normalizeLanguageName(typeof grammar.language === 'string' ? grammar.language : extension.packageJSON.name); + if (!seenLanguages.has(languageName)) { + const grammarPath = path.join(extension.extensionPath, grammar.path); + if (this.fileExists(grammarPath)) { + const languageInfo = this.createLanguageInfoFromGrammar(languageName, grammar.scopeName, grammarPath); + this._languageNameInfoMap.set(languageName, languageInfo); + seenLanguages.add(languageName); + } + } + } + } + } + + // set any aliases + if (Array.isArray(extension.packageJSON?.contributes?.languages)) { + for (let languageIndex = 0; languageIndex < extension.packageJSON.contributes.languages.length; languageIndex++) { + const language = extension.packageJSON.contributes.languages[languageIndex]; + const languageId = normalizeLanguageName(language.id); + const languageInfo = this._languageNameInfoMap.get(languageId); + if (languageInfo) { + const aliases = Array.isArray(language.aliases) ? language.aliases : []; + for (const alias of aliases.map(normalizeLanguageName)) { + this._languageNameInfoMap.set(alias, languageInfo); + } + } + } + } + } + + // set any extra aliases, but only if they're not already set + for (const wellKnownAlias of wellKnownAliases) { + const languageInfo = this._languageNameInfoMap.get(normalizeLanguageName(wellKnownAlias.languageName)); + if (languageInfo) { + for (const alias of wellKnownAlias.aliases.map(normalizeLanguageName)) { + if (!this._languageNameInfoMap.has(alias)) { + this._languageNameInfoMap.set(alias, languageInfo); + } + } + } + } + } + + private createLanguageInfoFromGrammar(languageName: string, scopeName: string, grammarPath: string): LanguageInfo { + const grammarContents = this.fileReader(grammarPath); + const grammarExtension = path.extname(grammarPath).substring(1); + const languageInfo = { languageName, scopeName, grammar: { grammarContents, extension: grammarExtension } }; + return languageInfo; + } + + private createGrammarRegistry(kernelInfos: contracts.KernelInfo[]): vsctm.Registry { + const scopeNameToGrammarMap: Map = new Map(); + const allKernelNamesSet: Set = new Set(); + for (const kernelInfo of kernelInfos) { + allKernelNamesSet.add(kernelInfo.localName); + for (const alias of kernelInfo.aliases) { + allKernelNamesSet.add(alias); + } + } + + // create root language + const allKernelNames = Array.from(allKernelNamesSet); + const endPattern = `(?=^${languageSelector}(${allKernelNames.join('|')})\\s+$)`; + const rootGrammar = { + scopeName: `source.${customScopePrefix}`, + patterns: kernelInfos.map(kernelInfo => { + const selectors: string[] = [kernelInfo.localName, ...kernelInfo.aliases]; + return { + begin: `^${languageSelector}(${selectors.join('|')})\\s+$`, + end: endPattern, + name: `language.switch.${languageNameFromKernelInfo(kernelInfo)}`, + patterns: [ + { + include: `source.${customScopePrefix}.${kernelInfo.localName}`, + } + ] + }; + }) + }; + const rootGrammarContents = JSON.stringify(rootGrammar); + scopeNameToGrammarMap.set(rootGrammar.scopeName, { grammarContents: rootGrammarContents, extension: 'json', }); + + // create magic command language + const magicCommandBegin = `^(${magicCommandSelector})(?!(${allKernelNames.join('|')}))`; + const magicCommandGrammar = { + scopeName: `source.${customScopePrefix}.magic-commands`, + patterns: [ + { + name: 'comment.line.magic-commands', + begin: magicCommandBegin, + end: '(?<=$)', + beginCaptures: { + '1': { + name: 'comment.line.magic-commands.hash-bang' + } + }, + patterns: [ + { + include: '#magic-command-name', + }, + { + include: '#strings', + }, + { + include: '#option', + }, + { + include: '#argument', + }, + ] + } + ], + repository: { + 'magic-command-name': { + patterns: [ + { + name: 'keyword.control.magic-commands', + match: `(?<=^${magicCommandSelector})[^\\s\\\"]+`, + } + ] + }, + 'option': { + patterns: [ + { + name: 'constant.language.magic-commands', + match: '(--?|/)[^\\s\\\"]+', + } + ] + }, + 'argument': { + patterns: [ + { + name: 'constant.numeric.magic-commands', + match: '[^\\s\\\"]+', + } + ] + }, + 'strings': { + patterns: [ + { + name: 'string.quoted.double.magic-commands', + begin: '\"', + end: '\"', + patterns: [ + { + name: 'constant.character.escape.magic-commands', + match: '\\.', + } + ] + } + ] + } + } + }; + const magicCommandGrammarContents = JSON.stringify(magicCommandGrammar, null, 2); + scopeNameToGrammarMap.set(magicCommandGrammar.scopeName, { grammarContents: magicCommandGrammarContents, extension: 'json', }); + + // create individual langauges + kernelInfos.forEach(kernelInfo => { + // ensure we have some kind of language name, even if it doesn't map to anything + const scopeName = `source.${customScopePrefix}.${kernelInfo.localName}`; + const patterns = [ + { + include: `source.${customScopePrefix}.magic-commands` + }, + { + include: `source.${customScopePrefix}` + } + ]; + + const languageInfo = this._languageNameInfoMap.get(languageNameFromKernelInfo(kernelInfo)); + if (languageInfo) { + patterns.push({ + include: languageInfo.scopeName + }); + } + + const languageGrammar = { + scopeName, + patterns + }; + const languageGrammarContents = JSON.stringify(languageGrammar); + + // set custom version of the language + scopeNameToGrammarMap.set(languageGrammar.scopeName, { grammarContents: languageGrammarContents, extension: 'json', }); + + // set the real language + if (languageInfo?.grammar) { + scopeNameToGrammarMap.set(languageInfo.scopeName, { grammarContents: languageInfo.grammar.grammarContents, extension: languageInfo.grammar.extension, }); + } + }); + + // prepare grammar scope loader + const registry = new vsctm.Registry({ + onigLib: Promise.resolve({ + createOnigScanner: (sources) => new oniguruma.OnigScanner(sources), + createOnigString: (str) => new oniguruma.OnigString(str) + }), + loadGrammar: (scopeName) => { + return new Promise((resolve, _reject) => { + const grammarContentPair = scopeNameToGrammarMap.get(scopeName); + if (grammarContentPair) { + const grammar = vsctm.parseRawGrammar(grammarContentPair.grammarContents, `${scopeName}.${grammarContentPair.extension}`); + resolve(grammar); + } else { + resolve(null); + } + }); + } + }); + + return registry; + } + + private scopeNameToTokenType(scopeNames: string[], tokenText: string): string | null { + const attemptedScopeNames: string[] = []; + for (let i = scopeNames.length - 1; i >= 0; i--) { + const scopeName = scopeNames[i]; + const scopeParts = scopeName.split('.'); + for (let j = scopeParts.length; j >= 1; j--) { + const rebuiltScopeName = scopeParts.slice(0, j).join('.'); + attemptedScopeNames.push(rebuiltScopeName); + const customScopeName = this._textMateScopeToSemanticType.get(rebuiltScopeName); + if (customScopeName) { + return customScopeName; + } + } + } + + if (scopeNames.length === 1 && scopeNames[0].startsWith(`source.${customScopePrefix}.`)) { + // suppress log for scopes like "source.polyglot-notebook.csharp" + } else { + Logger.default.info(`Unsupported scope mapping [${attemptedScopeNames.join(', ')}] for token text [${tokenText}]`); + } + + return null; + } +} + +function normalizeLanguageName(languageName: string): string { + return languageName.toLowerCase(); +} + +function languageNameFromKernelInfo(kernelInfo: contracts.KernelInfo): string { + // ensure we have some kind of language name, even if it doesn't map to anything + return normalizeLanguageName(kernelInfo.languageName ?? `unknown-language-from-kernel-${kernelInfo.localName}`); +} diff --git a/src/dotnet-interactive-vscode-common/src/extension.ts b/src/dotnet-interactive-vscode-common/src/extension.ts index 5c1ccc77ec..ccc539aa50 100644 --- a/src/dotnet-interactive-vscode-common/src/extension.ts +++ b/src/dotnet-interactive-vscode-common/src/extension.ts @@ -6,6 +6,7 @@ import * as helpService from './helpService'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; +import * as semanticTokens from './documentSemanticTokenProvider'; import * as vscode from 'vscode'; import * as vscodeLike from './interfaces/vscode-like'; import { ClientMapper } from './clientMapper'; @@ -13,12 +14,13 @@ import { MessageClient } from './messageClient'; import { StdioDotnetInteractiveChannel } from './stdioDotnetInteractiveChannel'; import { registerLanguageProviders } from './languageProvider'; +import { registerNotbookCellStatusBarItemProvider } from './notebookCellStatusBarItemProvider'; import { registerAcquisitionCommands, registerKernelCommands, registerFileCommands } from './commands'; -import { getNotebookSpecificLanguage, getSimpleLanguage, isDotnetInteractiveLanguage, isJupyterNotebookViewType, languageToCellKind } from './interactiveNotebook'; +import { languageToCellKind } from './interactiveNotebook'; import { InteractiveLaunchOptions, InstallInteractiveArgs } from './interfaces'; -import { createOutput, getDotNetVersionOrThrow, getWorkingDirectoryForNotebook, isVersionSufficient, processArguments } from './utilities'; +import { createOutput, debounce, getDotNetVersionOrThrow, getWorkingDirectoryForNotebook, isVersionSufficient, processArguments } from './utilities'; import { OutputChannelAdapter } from './OutputChannelAdapter'; import * as notebookControllers from '../notebookControllers'; @@ -26,8 +28,7 @@ import * as notebookSerializers from '../notebookSerializers'; import * as versionSpecificFunctions from '../versionSpecificFunctions'; import { ErrorOutputCreator } from './interactiveClient'; -import { isInsidersBuild } from './vscodeUtilities'; -import { getDotNetMetadata, withDotNetCellMetadata } from './ipynbUtilities'; +import * as vscodeUtilities from './vscodeUtilities'; import fetch from 'node-fetch'; import { CompositeKernel } from './dotnet-interactive/compositeKernel'; import { Logger, LogLevel } from './dotnet-interactive/logger'; @@ -36,8 +37,9 @@ import { NotebookParserServer } from './notebookParserServer'; import { registerVariableExplorer } from './variableExplorer'; import { KernelCommandAndEventChannel } from './DotnetInteractiveChannel'; import { ActiveNotebookTracker } from './activeNotebookTracker'; - -export const KernelIdForJupyter = 'polyglot-notebook-for-jupyter'; +import { onKernelInfoUpdates } from './dotnet-interactive'; +import * as metadataUtilities from './metadataUtilities'; +import * as constants from './constants'; export class CachedDotNetPathManager { private dotNetPath: string = 'dotnet'; // default to global tool if possible @@ -65,8 +67,8 @@ export const DotNetPathManager = new CachedDotNetPathManager(); export async function activate(context: vscode.ExtensionContext) { - const dotnetConfig = vscode.workspace.getConfiguration('dotnet-interactive'); - const polyglotConfig = vscode.workspace.getConfiguration('polyglot-notebook'); + const dotnetConfig = vscode.workspace.getConfiguration(constants.DotnetConfigurationSectionName); + const polyglotConfig = vscode.workspace.getConfiguration(constants.PolyglotConfigurationSectionName); const minDotNetSdkVersion = dotnetConfig.get('minimumDotNetSdkVersion') || '7.0'; const diagnosticsChannel = new OutputChannelAdapter(vscode.window.createOutputChannel('Polyglot Notebook : diagnostics')); const loggerChannel = new OutputChannelAdapter(vscode.window.createOutputChannel('Polyglot Notebook : logger')); @@ -108,9 +110,14 @@ export async function activate(context: vscode.ExtensionContext) { await helpServiceInstance.showHelpPageAndThrow(helpService.DotNetVersion); } + // grammars + const tokensProvider = new semanticTokens.DocumentSemanticTokensProvider(context.extension.packageJSON); + await tokensProvider.init(context); + context.subscriptions.push(vscode.languages.registerDocumentSemanticTokensProvider(semanticTokens.selector, tokensProvider, tokensProvider.semanticTokensLegend)); + async function kernelChannelCreator(notebookUri: vscodeLike.Uri): Promise { - const dotnetConfig = vscode.workspace.getConfiguration('dotnet-interactive'); - const polyglotConfig = vscode.workspace.getConfiguration('polyglot-notebook'); + const dotnetConfig = vscode.workspace.getConfiguration(constants.DotnetConfigurationSectionName); + const polyglotConfig = vscode.workspace.getConfiguration(constants.PolyglotConfigurationSectionName); const launchOptions = await getInteractiveLaunchOptions(); if (!launchOptions) { throw new Error(`Unable to get interactive launch options. Please see the '${diagnosticsChannel.getName()}' output window for details.`); @@ -142,7 +149,6 @@ export async function activate(context: vscode.ExtensionContext) { }); await channel.waitForReady(); - return channel; } @@ -184,18 +190,29 @@ export async function activate(context: vscode.ExtensionContext) { compositeKernel.registerCommandHandler({ commandType: contracts.SendEditableCodeType, handle: async commandInvocation => { const addCell = commandInvocation.commandEnvelope.command; - const language = addCell.language; + const kernelName = addCell.kernelName; const contents = addCell.code; + const kernel = compositeKernel.findKernelByName(kernelName); const notebookDocument = vscode.workspace.notebookDocuments.find(notebook => notebook.uri.toString() === notebookUri.toString()); - if (notebookDocument) { + if (kernel && notebookDocument) { + const language = tokensProvider.dynamicTokenProvider.getLanguageNameFromKernelNameOrAlias(notebookDocument, kernel.kernelInfo.localName); const range = new vscode.NotebookRange(notebookDocument.cellCount, notebookDocument.cellCount); const cellKind = languageToCellKind(language); - const notebookCellLanguage = getNotebookSpecificLanguage(language); - const newCell = new vscode.NotebookCellData(cellKind, contents, notebookCellLanguage); + const cellLanguage = cellKind === vscode.NotebookCellKind.Code ? constants.CellLanguageIdentifier : 'markdown'; + const newCell = new vscode.NotebookCellData(cellKind, contents, cellLanguage); + const notebookCellMetadata: metadataUtilities.NotebookCellMetadata = { + kernelName + }; + const rawCellMetadata = metadataUtilities.getRawNotebookCellMetadataFromNotebookCellMetadata(notebookCellMetadata); + newCell.metadata = rawCellMetadata; const succeeded = await versionSpecificFunctions.replaceNotebookCells(notebookDocument.uri, range, [newCell]); if (!succeeded) { throw new Error(`Unable to add cell to notebook '${notebookUri.toString()}'.`); } + + // when new cells are added, the previous cell's kernel name is copied forward, but in this case we want to force it back + const addedCell = notebookDocument.cellAt(notebookDocument.cellCount - 1); // the newly added cell is always the last one + await vscodeUtilities.setCellKernelName(addedCell, kernelName); } else { throw new Error(`Unable to get notebook document for URI '${notebookUri.toString()}'.`); } @@ -214,7 +231,7 @@ export async function activate(context: vscode.ExtensionContext) { registerKernelCommands(context, clientMapper); registerVariableExplorer(context, clientMapper); - const hostVersionSuffix = isInsidersBuild() ? 'Insiders' : 'Stable'; + const hostVersionSuffix = vscodeUtilities.isInsidersBuild() ? 'Insiders' : 'Stable'; diagnosticsChannel.appendLine(`Extension started for VS Code ${hostVersionSuffix}.`); const languageServiceDelay = polyglotConfig.get('languageServiceDelay') || 500; // fall back to something reasonable @@ -231,16 +248,38 @@ export async function activate(context: vscode.ExtensionContext) { // old startup time ~4800ms //////////////////////////////////////////////////////////////////////////////// - const serializerMap = registerWithVsCode(context, clientMapper, parserServer, clientMapperConfig.createErrorOutput, ...preloads); + const serializerMap = registerWithVsCode(context, clientMapper, parserServer, tokensProvider, clientMapperConfig.createErrorOutput, ...preloads); registerFileCommands(context, parserServer, clientMapper); context.subscriptions.push(vscode.workspace.onDidRenameFiles(e => handleFileRenames(e, clientMapper))); context.subscriptions.push(serializerLineAdapter); context.subscriptions.push(new ActiveNotebookTracker(context, clientMapper)); + // rebuild notebook grammar + const compositeKernelToNotebookUri: Map = new Map(); + clientMapper.onClientCreate((uri, client) => { + compositeKernelToNotebookUri.set(client.kernel, uri); + }); + onKernelInfoUpdates.push((compositeKernel) => { + const notebookUri = compositeKernelToNotebookUri.get(compositeKernel); + if (notebookUri) { + const kernelInfos = compositeKernel.childKernels.map(k => k.kernelInfo); + tokensProvider.dynamicTokenProvider.rebuildNotebookGrammar(notebookUri, kernelInfos); + debounce('refresh-tokens-after-grammar-update', 500, () => { + tokensProvider.refresh(); + }); + } + }); + + // build initial notebook grammar + context.subscriptions.push(vscode.workspace.onDidOpenNotebookDocument(async notebook => { + const kernelInfos = metadataUtilities.getKernelInfosFromNotebookDocument(notebook); + tokensProvider.dynamicTokenProvider.rebuildNotebookGrammar(notebook.uri, kernelInfos); + })); + // language registration - context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(async e => await updateNotebookCellLanguageInMetadata(e))); - context.subscriptions.push(registerLanguageProviders(clientMapper, languageServiceDelay)); + context.subscriptions.push(await registerLanguageProviders(clientMapper, languageServiceDelay)); + registerNotbookCellStatusBarItemProvider(context, clientMapper); vscode.window.registerUriHandler({ handleUri(uri: vscode.Uri): vscode.ProviderResult { @@ -313,13 +352,13 @@ function createErrorOutput(message: string, outputId?: string): vscodeLike.Noteb return cellOutput; } -function registerWithVsCode(context: vscode.ExtensionContext, clientMapper: ClientMapper, parserServer: NotebookParserServer, createErrorOutput: ErrorOutputCreator, ...preloadUris: vscode.Uri[]): Map { +function registerWithVsCode(context: vscode.ExtensionContext, clientMapper: ClientMapper, parserServer: NotebookParserServer, tokensProvider: semanticTokens.DocumentSemanticTokensProvider, createErrorOutput: ErrorOutputCreator, ...preloadUris: vscode.Uri[]): Map { const config = { clientMapper, preloadUris, createErrorOutput, }; - context.subscriptions.push(new notebookControllers.DotNetNotebookKernel(config)); + context.subscriptions.push(new notebookControllers.DotNetNotebookKernel(config, tokensProvider)); return notebookSerializers.createAndRegisterNotebookSerializers(context, parserServer); } @@ -364,10 +403,10 @@ async function openNotebookFromUrl(notebookUrl: string, notebookFormat: string | let viewType: string | undefined = undefined; switch (notebookFormat) { case 'dib': - viewType = 'polyglot-notebook'; + viewType = constants.NotebookViewType; break; case 'ipynb': - viewType = 'jupyter-notebook'; + viewType = constants.JupyterViewType; break; default: throw new Error(`Unsupported notebook format: ${notebookFormat}`); @@ -400,27 +439,6 @@ async function waitForSdkPackExtension(): Promise { } } -// keep the cell's language in metadata in sync with what VS Code thinks it is -async function updateNotebookCellLanguageInMetadata(candidateNotebookCellDocument: vscode.TextDocument) { - const notebook = vscode.workspace.notebookDocuments.find(notebook => notebook.getCells().some(cell => cell.document === candidateNotebookCellDocument)); - if (notebook && - isJupyterNotebookViewType(notebook.notebookType) && - isDotnetInteractiveLanguage(candidateNotebookCellDocument.languageId)) { - const cell = notebook.getCells().find(c => c.document === candidateNotebookCellDocument); - if (cell) { - const cellLanguage = cell.kind === vscode.NotebookCellKind.Code - ? getSimpleLanguage(candidateNotebookCellDocument.languageId) - : 'markdown'; - - const dotnetMetadata = getDotNetMetadata(cell.metadata); - if (dotnetMetadata.language !== cellLanguage) { - const newMetadata = withDotNetCellMetadata(cell.metadata, cellLanguage); - const _succeeded = await versionSpecificFunctions.replaceNotebookCellMetadata(notebook.uri, cell.index, newMetadata); - } - } - } -} - function handleFileRenames(e: vscode.FileRenameEvent, clientMapper: ClientMapper) { for (const fileRename of e.files) { clientMapper.reassociateClient(fileRename.oldUri, fileRename.newUri); diff --git a/src/dotnet-interactive-vscode-common/src/interactiveClient.ts b/src/dotnet-interactive-vscode-common/src/interactiveClient.ts index a2745f3164..a435478720 100644 --- a/src/dotnet-interactive-vscode-common/src/interactiveClient.ts +++ b/src/dotnet-interactive-vscode-common/src/interactiveClient.ts @@ -58,11 +58,13 @@ import { clearDebounce, createOutput } from './utilities'; import * as vscodeLike from './interfaces/vscode-like'; import { CompositeKernel } from './dotnet-interactive/compositeKernel'; +import { ProxyKernel } from './dotnet-interactive/proxyKernel'; import { Guid } from './dotnet-interactive/tokenGenerator'; import { KernelHost } from './dotnet-interactive/kernelHost'; import { KernelCommandAndEventChannel } from './DotnetInteractiveChannel'; import * as connection from './dotnet-interactive/connection'; import { DisposableSubscription } from './dotnet-interactive/disposables'; +import { Logger } from './dotnet-interactive/logger'; export interface ErrorOutputCreator { (message: string, outputId?: string): vscodeLike.NotebookCellOutput; @@ -254,14 +256,14 @@ export class InteractiveClient { } - completion(language: string, code: string, line: number, character: number, token?: string | undefined): Promise { + completion(kernelName: string, code: string, line: number, character: number, token?: string | undefined): Promise { let command: RequestCompletions = { code: code, linePosition: { line, character }, - targetKernelName: language + targetKernelName: kernelName }; return this.submitCommandAndGetResult(command, RequestCompletionsType, CompletionsProducedType, token); } @@ -290,10 +292,10 @@ export class InteractiveClient { return this.submitCommandAndGetResult(command, RequestSignatureHelpType, SignatureHelpProducedType, token); } - async getDiagnostics(language: string, code: string, token?: string | undefined): Promise> { + async getDiagnostics(kernelName: string, code: string, token?: string | undefined): Promise> { const command: RequestDiagnostics = { code, - targetKernelName: language + targetKernelName: kernelName }; const diagsProduced = await this.submitCommandAndGetResult(command, RequestDiagnosticsType, DiagnosticsProducedType, token); return diagsProduced.diagnostics; diff --git a/src/dotnet-interactive-vscode-common/src/interactiveNotebook.ts b/src/dotnet-interactive-vscode-common/src/interactiveNotebook.ts index 8700c9f03f..ca28753fc5 100644 --- a/src/dotnet-interactive-vscode-common/src/interactiveNotebook.ts +++ b/src/dotnet-interactive-vscode-common/src/interactiveNotebook.ts @@ -8,46 +8,10 @@ import { NotebookCellKind, NotebookDocumentBackup } from './interfaces/vscode-li import { ClientMapper } from './clientMapper'; import { Diagnostic } from './dotnet-interactive/contracts'; import { Uri } from 'vscode'; - -export const notebookCellLanguages: Array = [ - 'dotnet-interactive.csharp', - 'dotnet-interactive.fsharp', - 'dotnet-interactive.html', - 'dotnet-interactive.javascript', - 'dotnet-interactive.mermaid', - 'dotnet-interactive.pwsh', - 'dotnet-interactive.sql', - 'dotnet-interactive.kql', -]; - -export const defaultNotebookCellLanguage = notebookCellLanguages[0]; - -const notebookLanguagePrefix = 'dotnet-interactive.'; - -export function getSimpleLanguage(language: string): string { - if (language.startsWith(notebookLanguagePrefix)) { - return language.substr(notebookLanguagePrefix.length); - } - - return language; -} - -export function getNotebookSpecificLanguage(language?: string): string { - if (language && !language.startsWith(notebookLanguagePrefix) && language !== 'markdown') { - return notebookLanguagePrefix + language; - } - - return language ?? ""; -} - -export function isDotnetInteractiveLanguage(language: string): boolean { - return language.startsWith(notebookLanguagePrefix); -} - -export const jupyterViewType = 'jupyter-notebook'; +import * as constants from './constants'; export function isJupyterNotebookViewType(viewType: string): boolean { - return viewType === jupyterViewType; + return viewType === constants.JupyterViewType; } export function languageToCellKind(language?: string): NotebookCellKind { @@ -82,10 +46,14 @@ export function backupNotebook(rawData: Uint8Array, location: string): Promise) => void) { +export function notebookCellChanged(clientMapper: ClientMapper, documentUri: Uri, documentText: string, kernelName: string, diagnosticDelay: number, callback: (diagnostics: Array) => void) { debounce(`diagnostics-${documentUri.toString()}`, diagnosticDelay, async () => { - const client = await clientMapper.getOrAddClient(documentUri); - const diagnostics = await client.getDiagnostics(language, documentText); - callback(diagnostics); + let diagnostics: Diagnostic[] = []; + try { + const client = await clientMapper.getOrAddClient(documentUri); + diagnostics = await client.getDiagnostics(kernelName, documentText); + } finally { + callback(diagnostics); + } }); } diff --git a/src/dotnet-interactive-vscode-common/src/interfaces/vscode-like.ts b/src/dotnet-interactive-vscode-common/src/interfaces/vscode-like.ts index f683aa3ce8..34c4f5d2f3 100644 --- a/src/dotnet-interactive-vscode-common/src/interfaces/vscode-like.ts +++ b/src/dotnet-interactive-vscode-common/src/interfaces/vscode-like.ts @@ -37,14 +37,13 @@ export interface Uri { } export interface NotebookCell { - cellKind: NotebookCellKind; - document: Document; - readonly language: string; - //outputs: CellOutput[]; + readonly kind: NotebookCellKind; + metadata: { [key: string]: any }; } export interface NotebookDocument { readonly uri: Uri; + readonly metadata: { [key: string]: any }; } export interface NotebookCellData { diff --git a/src/dotnet-interactive-vscode-common/src/ipynbUtilities.ts b/src/dotnet-interactive-vscode-common/src/ipynbUtilities.ts deleted file mode 100644 index e78fa5ed99..0000000000 --- a/src/dotnet-interactive-vscode-common/src/ipynbUtilities.ts +++ /dev/null @@ -1,174 +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. - -import * as path from 'path'; -import { getNotebookSpecificLanguage, isDotnetInteractiveLanguage, notebookCellLanguages } from "./interactiveNotebook"; - -// the shape of this is meant to match the cell metadata from VS Code -interface CellMetadata { - custom?: { [key: string]: any } | undefined, -} - -export interface DotNetCellMetadata { - language: string | undefined, -} - -function isDotNetCellMetadata(arg: any): arg is DotNetCellMetadata { - return arg - && typeof arg.language === 'string'; -} - -export function getDotNetMetadata(metadata: any): DotNetCellMetadata { - if (metadata && - metadata.custom && - metadata.custom.metadata && - metadata.custom.metadata.dotnet_interactive && - isDotNetCellMetadata(metadata.custom.metadata.dotnet_interactive)) { - return metadata.custom.metadata.dotnet_interactive; - } - - return { - language: undefined, - }; -} - -export function withDotNetCellMetadata(metadata: { [key: string]: any, } | undefined, cellLanguage: string): { [key: string]: any, } { - const newMetadata = { ...metadata }; - newMetadata.custom = newMetadata.custom || {}; - newMetadata.custom.metadata = newMetadata.custom.metadata || {}; - newMetadata.custom.metadata.dotnet_interactive = newMetadata.custom.metadata.dotnet_interactive || {}; - newMetadata.custom.metadata.dotnet_interactive.language = cellLanguage; - return newMetadata; -} - -// the shape of this is meant to match the document metadata from VS Code -export interface DocumentMetadata { - custom?: { [key: string]: any } | undefined, -} - -export interface LanguageInfoMetadata { - name: string | undefined, -} - -function isLanguageInfoMetadata(arg: any): arg is LanguageInfoMetadata { - return arg - && typeof arg.name === 'string'; -} - -export function getLanguageInfoMetadata(metadata: any): LanguageInfoMetadata { - let languageMetadata: LanguageInfoMetadata = { - name: undefined, - }; - - if (metadata && - metadata.custom && - metadata.custom.metadata && - metadata.custom.metadata.language_info && - isLanguageInfoMetadata(metadata.custom.metadata.language_info)) { - languageMetadata = { ...metadata.custom.metadata.language_info }; - } - - languageMetadata.name = mapIpynbLanguageName(languageMetadata.name); - return languageMetadata; -} - -export function mapIpynbLanguageName(name: string | undefined): string | undefined { - if (name) { - // The .NET Interactive Jupyter kernel serializes the language names as "C#", "F#", and "PowerShell"; these - // need to be normalized to Polyglot Notebook kernel language names. - switch (name.toLowerCase()) { - case 'c#': - return 'csharp'; - case 'f#': - return 'fsharp'; - case 'powershell': - return 'pwsh'; - default: - return name; - } - } - - return undefined; -} - -export function getCellLanguage(cellText: string, cellMetadata: DotNetCellMetadata, documentMetadata: LanguageInfoMetadata, reportedCellLanguage: string): string { - const cellLines = cellText.split('\n').map(line => line.trim()); - let cellLanguageSpecifier: string | undefined = undefined; - if (cellLines.length > 0 && cellLines[0].startsWith('#!')) { - const cellLanguage = cellLines[0].substr(2); - const notebookSpecficLanguage = getNotebookSpecificLanguage(cellLanguage); - if (notebookCellLanguages.includes(notebookSpecficLanguage)) { - cellLanguageSpecifier = cellLanguage; - } - } - - let dotnetDocumentLanguage: string | undefined = undefined; - if (isDotnetInteractiveLanguage(reportedCellLanguage) || notebookCellLanguages.includes(getNotebookSpecificLanguage(reportedCellLanguage))) { - // reported language is either something like `dotnet-interactive.csharp` or it's `csharp` that can be turned into a known supported language - dotnetDocumentLanguage = getNotebookSpecificLanguage(reportedCellLanguage); - } - const dotnetCellLanguage = cellLanguageSpecifier || cellMetadata.language || dotnetDocumentLanguage || documentMetadata.name; - if (dotnetCellLanguage) { - return getNotebookSpecificLanguage(dotnetCellLanguage); - } - - return reportedCellLanguage; -} - -export interface KernelspecMetadata { - readonly display_name: string, - readonly language: string, - readonly name: string, -} - -export const requiredKernelspecData: KernelspecMetadata = { - display_name: '.NET (C#)', - language: 'C#', - name: '.net-csharp', -}; - -export const requiredLanguageInfoData = { - file_extension: '.cs', - mimetype: 'text/x-csharp', - name: 'C#', - pygments_lexer: 'csharp', - version: '9.0', -}; - -export function withDotNetKernelMetadata(metadata: { [key: string]: any } | undefined): any | undefined { - if (isDotnetKernel(metadata?.custom?.metadata?.kernelspec?.name)) { - return metadata; // don't change anything - } - - const result = { - ...metadata, - custom: { - ...metadata?.custom, - metadata: { - ...metadata?.custom?.metadata, - kernelspec: { - ...metadata?.custom?.metadata?.kernelspec, - ...requiredKernelspecData, - }, - language_info: requiredLanguageInfoData, - }, - } - }; - - return result; -} - -export function isIpynbFile(filePath: string): boolean { - return path.extname(filePath).toLowerCase() === '.ipynb'; -} - -function isDotnetKernel(kernelspecName: any): boolean { - return typeof kernelspecName === 'string' && kernelspecName.toLowerCase().startsWith('.net-'); -} - -export function isDotNetNotebookMetadata(notebookMetadata: any): boolean { - const kernelName = notebookMetadata?.custom?.metadata?.kernelspec?.name; - const languageInfo = notebookMetadata?.custom?.metadata?.language_info?.name; - const isDotnetLanguageInfo = typeof languageInfo === 'string' && isDotnetInteractiveLanguage(languageInfo); - return isDotnetKernel(kernelName) || isDotnetLanguageInfo; -} diff --git a/src/dotnet-interactive-vscode-common/src/kernelSelectorUtilities.ts b/src/dotnet-interactive-vscode-common/src/kernelSelectorUtilities.ts new file mode 100644 index 0000000000..e303449477 --- /dev/null +++ b/src/dotnet-interactive-vscode-common/src/kernelSelectorUtilities.ts @@ -0,0 +1,61 @@ +// 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. + +import { CompositeKernel } from './dotnet-interactive'; +import * as contracts from './dotnet-interactive/contracts'; +import * as metadataUtilities from './metadataUtilities'; +import * as vscodeLike from './interfaces/vscode-like'; + +export interface KernelSelectorOption { + kernelName: string; + displayValue: string; + languageName?: string; +} + +export function getKernelInfoDisplayValue(kernelInfo: contracts.KernelInfo): string { + return `${kernelInfo.localName} - ${kernelInfo.displayName}`; +} + +export function getKernelSelectorOptions(kernel: CompositeKernel, document: vscodeLike.NotebookDocument, requiredSupportedCommandType: contracts.KernelCommandType): KernelSelectorOption[] { + const kernelInfos: Map = new Map(); + + // create and collect all `KernelInfo`s from document metadata... + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); + for (const item of notebookMetadata.kernelInfo.items) { + const kernelInfo: contracts.KernelInfo = { + localName: item.name, + aliases: item.aliases, + languageName: item.languageName, + displayName: item.name, + uri: 'unused', + // a few lines down we filter kernels to only those that support the requisite type, so we artificially add that + // value here to ensure we show kernels that haven't been re-connected yet + supportedKernelCommands: [{ name: requiredSupportedCommandType }], + supportedDirectives: [] + }; + kernelInfos.set(item.name, kernelInfo); + } + + // ...overwrite with any "real" `KernelInfo` that we might actually have... + for (const childKernel of kernel.childKernels) { + kernelInfos.set(childKernel.name, childKernel.kernelInfo); + } + + // ...filter to only kernels that can handle `requiredSupportedCommandType`... + const filteredKernels = [...kernelInfos.values()].filter(k => k.supportedKernelCommands.findIndex(kci => kci.name === requiredSupportedCommandType) >= 0); + + // ...and pull out just the information necessary + const selectorOptions: KernelSelectorOption[] = filteredKernels.map(kernelInfo => { + const result: KernelSelectorOption = { + kernelName: kernelInfo.localName, + displayValue: getKernelInfoDisplayValue(kernelInfo) + }; + if (kernelInfo.languageName) { + result.languageName = kernelInfo.languageName; + } + + return result; + }); + + return selectorOptions; +} diff --git a/src/dotnet-interactive-vscode-common/src/languageProvider.ts b/src/dotnet-interactive-vscode-common/src/languageProvider.ts index 26934bab57..1b55526367 100644 --- a/src/dotnet-interactive-vscode-common/src/languageProvider.ts +++ b/src/dotnet-interactive-vscode-common/src/languageProvider.ts @@ -5,19 +5,20 @@ import * as vscode from 'vscode'; import { ClientMapper } from './clientMapper'; import { provideCompletion } from './languageServices/completion'; import { provideHover } from '././languageServices/hover'; -import { notebookCellLanguages, getSimpleLanguage, notebookCellChanged } from './interactiveNotebook'; -import { convertToRange, toVsCodeDiagnostic } from './vscodeUtilities'; +import { notebookCellChanged } from './interactiveNotebook'; +import { convertToRange, getCellKernelName, toVsCodeDiagnostic } from './vscodeUtilities'; import { getDiagnosticCollection } from './diagnostics'; import { provideSignatureHelp } from './languageServices/signatureHelp'; import * as versionSpecificFunctions from '../versionSpecificFunctions'; +import * as constants from './constants'; -function getNotebookUriFromCellDocument(cellDocument: vscode.TextDocument): vscode.Uri | undefined { +function getNotebookDcoumentFromCellDocument(cellDocument: vscode.TextDocument): vscode.NotebookDocument | undefined { const notebookDocument = vscode.workspace.notebookDocuments.find(notebook => notebook.getCells().some(cell => cell.document === cellDocument)); - if (notebookDocument) { - return notebookDocument.uri; - } + return notebookDocument; +} - return undefined; +function getCellFromCellDocument(notebookDocument: vscode.NotebookDocument, cellDocument: vscode.TextDocument): vscode.NotebookCell | undefined { + return notebookDocument.getCells().find(cell => cell.document === cellDocument); } export class CompletionItemProvider implements vscode.CompletionItemProvider { @@ -27,35 +28,39 @@ export class CompletionItemProvider implements vscode.CompletionItemProvider { } provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.CompletionContext): vscode.ProviderResult { - const documentUri = getNotebookUriFromCellDocument(document); - if (documentUri) { - const documentText = document.getText(); - const completionPromise = provideCompletion(this.clientMapper, getSimpleLanguage(document.languageId), documentUri, documentText, position, this.languageServiceDelay); - return ensureErrorsAreRejected(completionPromise, result => { - let range: vscode.Range | undefined = undefined; - if (result.linePositionSpan) { - range = new vscode.Range( - new vscode.Position(result.linePositionSpan.start.line, result.linePositionSpan.start.character), - new vscode.Position(result.linePositionSpan.end.line, result.linePositionSpan.end.character)); - } - const completionItems: Array = []; - for (const item of result.completions) { - const insertText: string | vscode.SnippetString = item.insertTextFormat === 'snippet' ? new vscode.SnippetString(item.insertText) : item.insertText; - const vscodeItem: vscode.CompletionItem = { - label: item.displayText, - documentation: item.documentation, - filterText: item.filterText, - insertText: insertText, - sortText: item.sortText, - range: range, - kind: this.mapCompletionItem(item.kind) - }; - completionItems.push(vscodeItem); - } - - const completionList = new vscode.CompletionList(completionItems, false); - return completionList; - }); + const notebookDocument = getNotebookDcoumentFromCellDocument(document); + if (notebookDocument) { + const cell = getCellFromCellDocument(notebookDocument, document); + if (cell) { + const kernelName = getCellKernelName(cell); + const documentText = document.getText(); + const completionPromise = provideCompletion(this.clientMapper, kernelName, notebookDocument.uri, documentText, position, this.languageServiceDelay); + return ensureErrorsAreRejected(completionPromise, result => { + let range: vscode.Range | undefined = undefined; + if (result.linePositionSpan) { + range = new vscode.Range( + new vscode.Position(result.linePositionSpan.start.line, result.linePositionSpan.start.character), + new vscode.Position(result.linePositionSpan.end.line, result.linePositionSpan.end.character)); + } + const completionItems: Array = []; + for (const item of result.completions) { + const insertText: string | vscode.SnippetString = item.insertTextFormat === 'snippet' ? new vscode.SnippetString(item.insertText) : item.insertText; + const vscodeItem: vscode.CompletionItem = { + label: item.displayText, + documentation: item.documentation, + filterText: item.filterText, + insertText: insertText, + sortText: item.sortText, + range: range, + kind: this.mapCompletionItem(item.kind) + }; + completionItems.push(vscodeItem); + } + + const completionList = new vscode.CompletionList(completionItems, false); + return completionList; + }); + } } } @@ -89,17 +94,21 @@ export class HoverProvider implements vscode.HoverProvider { provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.ProviderResult { - const documentUri = getNotebookUriFromCellDocument(document); - if (documentUri) { - const documentText = document.getText(); - const hoverPromise = provideHover(this.clientMapper, getSimpleLanguage(document.languageId), documentUri, documentText, position, this.languageServiceDelay); - return ensureErrorsAreRejected(hoverPromise, result => { - const contents = result.isMarkdown - ? new vscode.MarkdownString(result.contents) - : result.contents; - const hover = new vscode.Hover(contents, convertToRange(result.range)); - return hover; - }); + const notebookDocument = getNotebookDcoumentFromCellDocument(document); + if (notebookDocument) { + const cell = getCellFromCellDocument(notebookDocument, document); + if (cell) { + const kernelName = getCellKernelName(cell); + const documentText = document.getText(); + const hoverPromise = provideHover(this.clientMapper, kernelName, notebookDocument.uri, documentText, position, this.languageServiceDelay); + return ensureErrorsAreRejected(hoverPromise, result => { + const contents = result.isMarkdown + ? new vscode.MarkdownString(result.contents) + : result.contents; + const hover = new vscode.Hover(contents, convertToRange(result.range)); + return hover; + }); + } } } } @@ -111,26 +120,30 @@ export class SignatureHelpProvider implements vscode.SignatureHelpProvider { } provideSignatureHelp(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, context: vscode.SignatureHelpContext): vscode.ProviderResult { - const documentUri = getNotebookUriFromCellDocument(document); - if (documentUri) { - const documentText = document.getText(); - const sigHelpPromise = provideSignatureHelp(this.clientMapper, getSimpleLanguage(document.languageId), documentUri, documentText, position, this.languageServiceDelay); - return ensureErrorsAreRejected(sigHelpPromise, result => { - const signatures: Array = result.signatures.map(sig => { - const parameters: Array = sig.parameters.map(p => new vscode.ParameterInformation(p.label, p.documentation.value)); - let si = new vscode.SignatureInformation( - sig.label, - sig.documentation.value - ); - si.parameters = parameters; - return si; + const notebookDocument = getNotebookDcoumentFromCellDocument(document); + if (notebookDocument) { + const cell = getCellFromCellDocument(notebookDocument, document); + if (cell) { + const kernelName = getCellKernelName(cell); + const documentText = document.getText(); + const sigHelpPromise = provideSignatureHelp(this.clientMapper, kernelName, notebookDocument.uri, documentText, position, this.languageServiceDelay); + return ensureErrorsAreRejected(sigHelpPromise, result => { + const signatures: Array = result.signatures.map(sig => { + const parameters: Array = sig.parameters.map(p => new vscode.ParameterInformation(p.label, p.documentation.value)); + let si = new vscode.SignatureInformation( + sig.label, + sig.documentation.value + ); + si.parameters = parameters; + return si; + }); + let sh = new vscode.SignatureHelp(); + sh.signatures = signatures; + sh.activeSignature = result.activeSignatureIndex; + sh.activeParameter = result.activeParameterIndex; + return sh; }); - let sh = new vscode.SignatureHelp(); - sh.signatures = signatures; - sh.activeSignature = result.activeSignatureIndex; - sh.activeParameter = result.activeParameterIndex; - return sh; - }); + } } } } @@ -146,26 +159,24 @@ function ensureErrorsAreRejected(promise: Promise< }); } -export function registerLanguageProviders(clientMapper: ClientMapper, languageServiceDelay: number): vscode.Disposable { +export async function registerLanguageProviders(clientMapper: ClientMapper, languageServiceDelay: number): Promise { const disposables: Array = []; - let languages = [...notebookCellLanguages, "dotnet-interactive.magic-commands"]; + const languages = [constants.CellLanguageIdentifier]; disposables.push(vscode.languages.registerCompletionItemProvider(languages, new CompletionItemProvider(clientMapper, languageServiceDelay), ...CompletionItemProvider.triggerCharacters)); disposables.push(vscode.languages.registerHoverProvider(languages, new HoverProvider(clientMapper, languageServiceDelay))); disposables.push(vscode.languages.registerSignatureHelpProvider(languages, new SignatureHelpProvider(clientMapper, languageServiceDelay), ...SignatureHelpProvider.triggerCharacters)); disposables.push(vscode.workspace.onDidChangeTextDocument(e => { - if (vscode.languages.match(notebookCellLanguages, e.document) && - vscode.window.activeNotebookEditor) { - const cells = versionSpecificFunctions.getNotebookDocumentFromEditor(vscode.window.activeNotebookEditor).getCells(); + if (e.document.languageId === constants.CellLanguageIdentifier && vscode.window.activeNotebookEditor) { + const notebookDocument = versionSpecificFunctions.getNotebookDocumentFromEditor(vscode.window.activeNotebookEditor); + const cells = notebookDocument.getCells(); const cell = cells?.find(cell => cell.document === e.document); if (cell) { - const documentUri = getNotebookUriFromCellDocument(e.document); - if (documentUri) { - notebookCellChanged(clientMapper, documentUri, e.document.getText(), getSimpleLanguage(cell.document.languageId), languageServiceDelay, diagnostics => { - const collection = getDiagnosticCollection(e.document.uri); - collection.set(e.document.uri, diagnostics.map(toVsCodeDiagnostic)); - }); - } + const kernelName = getCellKernelName(cell); + notebookCellChanged(clientMapper, notebookDocument.uri, e.document.getText(), kernelName, languageServiceDelay, diagnostics => { + const collection = getDiagnosticCollection(e.document.uri); + collection.set(e.document.uri, diagnostics.map(toVsCodeDiagnostic)); + }); } } })); diff --git a/src/dotnet-interactive-vscode-common/src/languageServices/completion.ts b/src/dotnet-interactive-vscode-common/src/languageServices/completion.ts index 205e82cb15..3c06a08426 100644 --- a/src/dotnet-interactive-vscode-common/src/languageServices/completion.ts +++ b/src/dotnet-interactive-vscode-common/src/languageServices/completion.ts @@ -8,10 +8,10 @@ import { Uri } from '../interfaces/vscode-like'; import * as contracts from '../dotnet-interactive/contracts'; import { debounceAndReject } from '../utilities'; -export function provideCompletion(clientMapper: ClientMapper, language: string, documentUri: Uri, documentText: string, position: PositionLike, languageServiceDelay: number, token?: string | undefined): Promise { +export function provideCompletion(clientMapper: ClientMapper, kernelName: string, documentUri: Uri, documentText: string, position: PositionLike, languageServiceDelay: number, token?: string | undefined): Promise { return debounceAndReject(`completion-${documentUri.toString()}`, languageServiceDelay, async () => { const client = await clientMapper.getOrAddClient(documentUri); - const completion = await client.completion(language, documentText, position.line, position.character, token); + const completion = await client.completion(kernelName, documentText, position.line, position.character, token); return completion; }); } diff --git a/src/dotnet-interactive-vscode-common/src/metadataUtilities.ts b/src/dotnet-interactive-vscode-common/src/metadataUtilities.ts new file mode 100644 index 0000000000..3bf1776b60 --- /dev/null +++ b/src/dotnet-interactive-vscode-common/src/metadataUtilities.ts @@ -0,0 +1,331 @@ +// 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. + +import { CompositeKernel } from './dotnet-interactive'; +import * as contracts from './dotnet-interactive/contracts'; +import * as vscodeLike from './interfaces/vscode-like'; + +export interface NotebookDocumentMetadata { + kernelInfo: contracts.DocumentKernelInfoCollection; +} + +export interface NotebookCellMetadata { + kernelName?: string; +} + +export interface KernelspecMetadata { + display_name: string; + language: string; + name: string; +} + +export function isIpynbNotebook(notebookDocument: vscodeLike.NotebookDocument) { + return notebookDocument.uri.fsPath.toLowerCase().endsWith('.ipynb'); +} + +export function isDotNetNotebook(notebook: vscodeLike.NotebookDocument): boolean { + const notebookUriString = notebook.uri.toString(); + if (notebookUriString.endsWith('.dib') || notebook.uri.fsPath.endsWith('.dib')) { + return true; + } + + const kernelspecMetadata = getKernelspecMetadataFromIpynbNotebookDocument(notebook); + if (kernelspecMetadata.name.startsWith('.net-')) { + return true; + } + + // doesn't look like us + return false; +} + +export function getNotebookCellMetadataFromInteractiveDocumentElement(interactiveDocumentElement: contracts.InteractiveDocumentElement): NotebookCellMetadata { + const cellMetadata = createDefaultNotebookCellMetadata(); + + // first try to get the old `dotnet_interactive` value... + const dotnet_interactive = interactiveDocumentElement.metadata?.dotnet_interactive; + if (typeof dotnet_interactive === 'object') { + const language = dotnet_interactive.language; + if (typeof language === 'string') { + // this is a really unfortunate case where we were storing the kernel name, but calling it the language + cellMetadata.kernelName = language; + } + } + + // ...then try newer `polyglot_notebook` value + const polyglot_notebook = interactiveDocumentElement.metadata?.polyglot_notebook; + if (typeof polyglot_notebook === 'object') { + const kernelName = polyglot_notebook.kernelName; + if (typeof kernelName === 'string') { + cellMetadata.kernelName = kernelName; + } + } + + return cellMetadata; +} + +export function getNotebookCellMetadataFromNotebookCellElement(notebookCell: vscodeLike.NotebookCell): NotebookCellMetadata { + const cellMetadata = createDefaultNotebookCellMetadata(); + + const metadata = notebookCell.metadata?.custom?.metadata; + + if (typeof metadata === 'object') { + // first try to get the old `dotnet_interactive` value... + const dotnet_interactive = metadata.dotnet_interactive; + if (typeof dotnet_interactive === 'object') { + const language = dotnet_interactive.language; + if (typeof language === 'string') { + // this is a really unfortunate case where we were storing the kernel name, but calling it the language + cellMetadata.kernelName = language; + } + } + + // ...then try newer `polyglot_notebook` value + const polyglot_notebook = metadata.polyglot_notebook; + if (typeof polyglot_notebook === 'object') { + const kernelName = polyglot_notebook.kernelName; + if (typeof kernelName === 'string') { + cellMetadata.kernelName = kernelName; + } + } + } + + return cellMetadata; +} + +export function getNotebookDocumentMetadataFromInteractiveDocument(interactiveDocument: contracts.InteractiveDocument): NotebookDocumentMetadata { + const notebookMetadata = createDefaultNotebookDocumentMetadata(); + const kernelInfo = interactiveDocument.metadata.kernelInfo; + if (typeof kernelInfo === 'object') { + if (typeof kernelInfo.defaultKernelName === 'string') { + notebookMetadata.kernelInfo.defaultKernelName = kernelInfo.defaultKernelName; + } + + const items = kernelInfo.items; + if (Array.isArray(items) && items.every(item => typeof item === 'object')) { + notebookMetadata.kernelInfo.items = items; + } + } + + notebookMetadata.kernelInfo.items = notebookMetadata.kernelInfo.items.map(item => ensureProperShapeForDocumentKernelInfo(item)); + return notebookMetadata; +} + +export function getNotebookDocumentMetadataFromNotebookDocument(document: vscodeLike.NotebookDocument): NotebookDocumentMetadata { + const notebookMetadata = createDefaultNotebookDocumentMetadata(); + + // .dib files will have their metadata at the root; .ipynb files will have their metadata a little deeper + const polyglot_notebook = document.metadata.polyglot_notebook ?? document.metadata?.custom?.metadata?.polyglot_notebook; + if (typeof polyglot_notebook === 'object') { + const kernelInfo = polyglot_notebook.kernelInfo; + if (typeof kernelInfo === 'object') { + if (typeof kernelInfo.defaultKernelName === 'string') { + notebookMetadata.kernelInfo.defaultKernelName = kernelInfo.defaultKernelName; + } + + const items = kernelInfo.items; + if (Array.isArray(items) && items.every(item => typeof item === 'object')) { + notebookMetadata.kernelInfo.items = items; + } + } + } else { + const x = 1; + } + + notebookMetadata.kernelInfo.items = notebookMetadata.kernelInfo.items.map(item => ensureProperShapeForDocumentKernelInfo(item)); + return notebookMetadata; +} + +export function getNotebookDocumentMetadataFromCompositeKernel(kernel: CompositeKernel): NotebookDocumentMetadata { + const notebookMetadata = createDefaultNotebookDocumentMetadata(); + notebookMetadata.kernelInfo.defaultKernelName = kernel.defaultKernelName ?? notebookMetadata.kernelInfo.defaultKernelName; + notebookMetadata.kernelInfo.items = kernel.childKernels.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map(k => ({ name: k.name, aliases: k.kernelInfo.aliases, languageName: k.kernelInfo.languageName })); + + return notebookMetadata; +} + +function ensureProperShapeForDocumentKernelInfo(kernelInfo: contracts.DocumentKernelInfo) { + if (!kernelInfo.aliases) { + kernelInfo.aliases = []; + } + + return kernelInfo; +} + +export function getKernelspecMetadataFromIpynbNotebookDocument(notebook: vscodeLike.NotebookDocument): KernelspecMetadata { + // defaulting to empty values so we don't mis-represent the document + const kernelspecMetadata: KernelspecMetadata = { + display_name: '', + language: '', + name: '' + }; + + const custom = notebook.metadata.custom; + if (typeof custom === 'object') { + const metadata = custom.metadata; + if (typeof metadata === 'object') { + const kernelspec = metadata.kernelspec; + if (typeof kernelspec === 'object') { + const display_name = kernelspec.display_name; + if (typeof display_name === 'string') { + kernelspecMetadata.display_name = display_name; + } + + const language = kernelspec.language; + if (typeof language === 'string') { + kernelspecMetadata.language = language; + } + + const name = kernelspec.name; + if (typeof name === 'string') { + kernelspecMetadata.name = name; + } + } + } + } + + return kernelspecMetadata; +} + +export function getKernelInfosFromNotebookDocument(notebookDocument: vscodeLike.NotebookDocument): contracts.KernelInfo[] { + const notebookDocumentMetadata = getNotebookDocumentMetadataFromNotebookDocument(notebookDocument); + const kernelInfos: contracts.KernelInfo[] = notebookDocumentMetadata.kernelInfo.items.map(item => ({ + // these are the only important ones + localName: item.name, + aliases: item.aliases, + languageName: item.languageName, + // these are unused + uri: 'unused', + displayName: 'unused', + supportedKernelCommands: [], + supportedDirectives: [] + })); + return kernelInfos; +} + +export function getKernelspecMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata: NotebookDocumentMetadata): KernelspecMetadata { + // these options are hard-coded because this is exactly what we put on disk with `dotnet interactive jupyter install` + switch (notebookDocumentMetadata.kernelInfo.defaultKernelName) { + case 'fsharp': + return { + display_name: '.NET (F#)', + language: 'F#', + name: '.net-fsharp' + }; + case 'pwsh': + return { + display_name: '.NET (PowerShell)', + language: 'PowerShell', + name: '.net-pwsh' + }; + case 'csharp': + default: + return { + display_name: '.NET (C#)', + language: 'C#', + name: '.net-csharp' + }; + } +} + +export function createNewIpynbMetadataWithNotebookDocumentMetadata(existingMetadata: { [key: string]: any }, notebookDocumentMetadata: NotebookDocumentMetadata): { [key: string]: any } { + const resultMetadata: { [key: string]: any } = { ...existingMetadata }; + + // kernelspec + const kernelspec = getKernelspecMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata); + resultMetadata.custom = resultMetadata.custom ?? {}; + resultMetadata.custom.metadata = resultMetadata.custom.metadata ?? {}; + resultMetadata.custom.metadata.kernelspec = kernelspec; + resultMetadata.custom.metadata.polyglot_notebook = notebookDocumentMetadata; + return resultMetadata; +} + +export function getRawInteractiveDocumentElementMetadataFromNotebookCellMetadata(notebookCellMetadata: NotebookCellMetadata): { [key: string]: any } { + return notebookCellMetadata; +} + +export function getRawNotebookCellMetadataFromNotebookCellMetadata(notebookCellMetadata: NotebookCellMetadata): { [key: string]: any } { + return { + custom: { + metadata: { + // this is the canonical metadata + polyglot_notebook: notebookCellMetadata, + // this is to maintain backwards compatibility for a while + dotnet_interactive: { + language: notebookCellMetadata.kernelName + } + } + } + }; +} + +export function getRawInteractiveDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata: NotebookDocumentMetadata): { [key: string]: any } { + return notebookDocumentMetadata; +} + +export function getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata: NotebookDocumentMetadata, createForIpynb: boolean): { [key: string]: any } { + const rawMetadata: { [key: string]: any } = {}; + + if (createForIpynb) { + const kernelspec = getKernelspecMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata); + rawMetadata.custom = { + metadata: { + kernelspec, + polyglot_notebook: notebookDocumentMetadata + }, + }; + } else { + rawMetadata.polyglot_notebook = notebookDocumentMetadata; + } + + return rawMetadata; +} + +export function mergeNotebookCellMetadata(baseMetadata: NotebookCellMetadata, metadataWithNewValues: NotebookCellMetadata): NotebookCellMetadata { + const resultMetadata = { ...baseMetadata }; + if (metadataWithNewValues.kernelName) { + resultMetadata.kernelName = metadataWithNewValues.kernelName; + } + + return resultMetadata; +} + +export function mergeNotebookDocumentMetadata(baseMetadata: NotebookDocumentMetadata, metadataWithNewValues: NotebookDocumentMetadata): NotebookDocumentMetadata { + const resultMetadata = { ...baseMetadata }; + const kernelInfoItems: Map = new Map(); + for (const item of baseMetadata.kernelInfo.items) { + kernelInfoItems.set(item.name, item); + } + for (const item of metadataWithNewValues.kernelInfo.items) { + kernelInfoItems.set(item.name, item); + } + + resultMetadata.kernelInfo.items = [...kernelInfoItems.values()]; + return resultMetadata; +} + +export function mergeRawMetadata(baseMetadata: { [key: string]: any }, metadataWithNewValues: { [key: string]: any }): { [key: string]: any } { + const resultMetadata = { ...baseMetadata }; + for (const key in metadataWithNewValues) { + resultMetadata[key] = metadataWithNewValues[key]; + } + + return resultMetadata; +} + +export function createDefaultNotebookDocumentMetadata(): NotebookDocumentMetadata { + return { + kernelInfo: { + defaultKernelName: 'csharp', + items: [ + { + name: 'csharp', + aliases: [], + } + ], + } + };; +} + +function createDefaultNotebookCellMetadata(): NotebookCellMetadata { + return {}; +} diff --git a/src/dotnet-interactive-vscode-common/src/notebookCellStatusBarItemProvider.ts b/src/dotnet-interactive-vscode-common/src/notebookCellStatusBarItemProvider.ts new file mode 100644 index 0000000000..6f70c59900 --- /dev/null +++ b/src/dotnet-interactive-vscode-common/src/notebookCellStatusBarItemProvider.ts @@ -0,0 +1,105 @@ +// 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. + +import * as vscode from 'vscode'; +import * as contracts from './dotnet-interactive/contracts'; +import * as metadataUtilities from './metadataUtilities'; +import * as versionSpecificFunctions from '../versionSpecificFunctions'; +import { ClientMapper } from './clientMapper'; +import { isKernelEventEnvelope } from './dotnet-interactive'; +import * as kernelSelectorUtilities from './kernelSelectorUtilities'; +import * as constants from './constants'; +import * as vscodeUtilities from './vscodeUtilities'; + +const selectKernelCommandName = 'polyglot-notebook.selectCellKernel'; + +export function registerNotbookCellStatusBarItemProvider(context: vscode.ExtensionContext, clientMapper: ClientMapper) { + const cellItemProvider = new DotNetNotebookCellStatusBarItemProvider(clientMapper); + clientMapper.onClientCreate((_uri, client) => { + client.channel.receiver.subscribe({ + next: envelope => { + if (isKernelEventEnvelope(envelope) && envelope.eventType === contracts.KernelInfoProducedType) { + cellItemProvider.updateKernelDisplayNames(); + } + } + }); + }); + context.subscriptions.push(vscode.notebooks.registerNotebookCellStatusBarItemProvider(constants.NotebookViewType, cellItemProvider)); + context.subscriptions.push(vscode.notebooks.registerNotebookCellStatusBarItemProvider(constants.JupyterViewType, cellItemProvider)); // TODO: fix this + context.subscriptions.push(vscode.notebooks.registerNotebookCellStatusBarItemProvider(constants.LegacyNotebookViewType, cellItemProvider)); + context.subscriptions.push(vscode.commands.registerCommand(selectKernelCommandName, async (cell?: vscode.NotebookCell) => { + if (cell) { + const client = await clientMapper.tryGetClient(cell.notebook.uri); + if (client) { + const availableOptions = kernelSelectorUtilities.getKernelSelectorOptions(client.kernel, cell.notebook, contracts.SubmitCodeType); + const availableDisplayOptions = availableOptions.map(o => o.displayValue); + const selectedDisplayOption = await vscode.window.showQuickPick(availableDisplayOptions, { title: 'Select kernel' }); + if (selectedDisplayOption) { + const selectedValueIndex = availableDisplayOptions.indexOf(selectedDisplayOption); + if (selectedValueIndex >= 0) { + const selectedKernelData = availableOptions[selectedValueIndex]; + const codeCell = await vscodeUtilities.ensureCellKernelKind(cell, vscode.NotebookCellKind.Code); + const notebookCellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(cell); + if (notebookCellMetadata.kernelName !== selectedKernelData.kernelName) { + notebookCellMetadata.kernelName = selectedKernelData.kernelName; + const newRawMetadata = metadataUtilities.getRawNotebookCellMetadataFromNotebookCellMetadata(notebookCellMetadata); + const mergedMetadata = metadataUtilities.mergeRawMetadata(cell.metadata, newRawMetadata); + const _succeeded = await versionSpecificFunctions.replaceNotebookCellMetadata(codeCell.notebook.uri, codeCell.index, mergedMetadata); + await vscode.commands.executeCommand('polyglot-notebook.refreshSemanticTokens'); + } + } + } + } + } + })); +} + +function getNotebookDcoumentFromCellDocument(cellDocument: vscode.TextDocument): vscode.NotebookDocument | undefined { + const notebookDocument = vscode.workspace.notebookDocuments.find(notebook => notebook.getCells().some(cell => cell.document === cellDocument)); + return notebookDocument; +} + +class DotNetNotebookCellStatusBarItemProvider { + private _onDidChangeCellStatusBarItemsEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + + onDidChangeCellStatusBarItems: vscode.Event = this._onDidChangeCellStatusBarItemsEmitter.event; + + constructor(private readonly clientMapper: ClientMapper) { + } + + async provideCellStatusBarItems(cell: vscode.NotebookCell, token: vscode.CancellationToken): Promise { + if (!metadataUtilities.isDotNetNotebook(cell.notebook)) { + return []; + } + + let displayText: string; + if (cell.document.languageId === 'markdown') { + displayText = 'Markdown'; + } else { + const cellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(cell); + const cellKernelName = cellMetadata.kernelName ?? 'csharp'; + const notebookDocument = getNotebookDcoumentFromCellDocument(cell.document); + const client = await this.clientMapper.tryGetClient(notebookDocument!.uri); // don't force client creation + if (client) { + const matchingKernel = client.kernel.childKernels.find(k => k.kernelInfo.localName === cellKernelName); + displayText = matchingKernel ? kernelSelectorUtilities.getKernelInfoDisplayValue(matchingKernel.kernelInfo) : cellKernelName; + } + else { + displayText = cellKernelName; + } + } + + const item = new vscode.NotebookCellStatusBarItem(displayText, vscode.NotebookCellStatusBarAlignment.Right); + const command: vscode.Command = { + title: '', + command: selectKernelCommandName, + arguments: [], + }; + item.command = command; + return [item]; + } + + updateKernelDisplayNames() { + this._onDidChangeCellStatusBarItemsEmitter.fire(); + } +} diff --git a/src/dotnet-interactive-vscode-common/src/notebookParserServer.ts b/src/dotnet-interactive-vscode-common/src/notebookParserServer.ts index f33bcb2c19..0ccb17781f 100644 --- a/src/dotnet-interactive-vscode-common/src/notebookParserServer.ts +++ b/src/dotnet-interactive-vscode-common/src/notebookParserServer.ts @@ -2,10 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. import * as contracts from './dotnet-interactive/contracts'; -import { defaultNotebookCellLanguage } from './interactiveNotebook'; import { MessageClient } from './messageClient'; import { isNotebookParserServerResponse, isNotebookParserServerError, isNotebookParseResponse, isNotebookSerializeResponse } from './interfaces/utilities'; import { Eol } from './interfaces'; +import * as constants from './constants'; export class NotebookParserServer { private nextId: number = 1; @@ -18,7 +18,7 @@ export class NotebookParserServer { type: contracts.RequestType.Parse, id: this.getNextId(), serializationType, - defaultLanguage: defaultNotebookCellLanguage, + defaultLanguage: constants.CellLanguageIdentifier, rawData, }; @@ -32,7 +32,7 @@ export class NotebookParserServer { // ensure at least one cell notebookCells.push({ executionOrder: 0, - kernelName: defaultNotebookCellLanguage, + kernelName: 'csharp', contents: '', outputs: [], }); @@ -53,7 +53,7 @@ export class NotebookParserServer { type: contracts.RequestType.Serialize, id: this.getNextId(), serializationType, - defaultLanguage: defaultNotebookCellLanguage, + defaultLanguage: 'csharp', newLine: eol, document, }; diff --git a/src/dotnet-interactive-vscode-common/src/variableExplorer.ts b/src/dotnet-interactive-vscode-common/src/variableExplorer.ts index 5bede867ac..19e8766d63 100644 --- a/src/dotnet-interactive-vscode-common/src/variableExplorer.ts +++ b/src/dotnet-interactive-vscode-common/src/variableExplorer.ts @@ -5,22 +5,12 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { ClientMapper } from './clientMapper'; import * as contracts from './dotnet-interactive/contracts'; -import { getNotebookSpecificLanguage } from './interactiveNotebook'; import { VariableGridRow } from './dotnet-interactive/webview/variableGridInterfaces'; import * as utilities from './utilities'; import * as versionSpecificFunctions from '../versionSpecificFunctions'; import { DisposableSubscription } from './dotnet-interactive/disposables'; import { isKernelEventEnvelope } from './dotnet-interactive'; - -// creates a map of, e.g.: -// "dotnet-interactive.csharp" => "C#"" -const languageIdToAliasMap = new Map( - vscode.extensions.all.map(e => e.packageJSON?.contributes?.languages || []) - .filter(l => l) - .reduce((a, b) => a.concat(b), []) - .filter(l => typeof l.id === 'string' && (l.aliases?.length ?? 0) > 0 && typeof l.aliases[0] === 'string') - .map(l => <[string, string]>[l.id, l.aliases[0]]) -); +import * as kernelSelectorUtilities from './kernelSelectorUtilities'; function debounce(callback: () => void) { utilities.debounce('variable-explorer', 500, callback); @@ -28,40 +18,31 @@ function debounce(callback: () => void) { export function registerVariableExplorer(context: vscode.ExtensionContext, clientMapper: ClientMapper) { context.subscriptions.push(vscode.commands.registerCommand('polyglot-notebook.shareValueWith', async (variableInfo: { kernelName: string, valueName: string } | undefined) => { - if (variableInfo && vscode.window.activeNotebookEditor) { - const client = await clientMapper.tryGetClient(versionSpecificFunctions.getNotebookDocumentFromEditor(vscode.window.activeNotebookEditor).uri); + const activeNotebookEditor = vscode.window.activeNotebookEditor; + if (variableInfo && activeNotebookEditor) { + const notebookDocument = versionSpecificFunctions.getNotebookDocumentFromEditor(activeNotebookEditor); + const client = await clientMapper.tryGetClient(notebookDocument.uri); if (client) { - // creates a map of _only_ the available languages in this notebook, e.g.: - // "C#" => "dotnet-interactive.csharp" - const availableKernelDisplayNamesToLanguageNames = new Map(client.kernel.childKernels.map(k => { - const notebookLanguage = getNotebookSpecificLanguage(k.name); - let displayLanguage = notebookLanguage; - const displayLanguageCandidate = languageIdToAliasMap.get(notebookLanguage); - if (displayLanguageCandidate) { - displayLanguage = displayLanguageCandidate; - } - - return <[string, string]>[displayLanguage, k.name]; - })); + const kernelSelectorOptions = kernelSelectorUtilities.getKernelSelectorOptions(client.kernel, notebookDocument, contracts.SendValueType); + const kernelDisplayValues = kernelSelectorOptions.map(k => k.displayValue); + const selectedKernelDisplayName = await vscode.window.showQuickPick(kernelDisplayValues, { title: `Share value [${variableInfo.valueName}] from [${variableInfo.kernelName}] to ...` }); + if (selectedKernelDisplayName) { + const targetKernelIndex = kernelDisplayValues.indexOf(selectedKernelDisplayName); + if (targetKernelIndex >= 0) { + const targetKernelSelectorOption = kernelSelectorOptions[targetKernelIndex]; + // ends with newline to make adding code easier + const code = `#!share --from ${variableInfo.kernelName} ${variableInfo.valueName}\n`; + const command: contracts.SendEditableCode = { + kernelName: targetKernelSelectorOption.kernelName, + code, + }; + const commandEnvelope: contracts.KernelCommandEnvelope = { + commandType: contracts.SendEditableCodeType, + command, + }; - const kernelDisplayValues = [...availableKernelDisplayNamesToLanguageNames.keys()]; - const selectedKernelName = await vscode.window.showQuickPick(kernelDisplayValues, { title: `Share value [${variableInfo.valueName}] from [${variableInfo.kernelName}] to ...` }); - if (selectedKernelName) { - // translate back from display name (e.g., "C# (.NET Interactive)") to language name (e.g., "dotnet-interactive.csharp") - const targetKernelName = availableKernelDisplayNamesToLanguageNames.get(selectedKernelName)!; - // TODO: if not well-known kernel/language, add kernel selector, e.g., `#!sql-AdventureWorks` - // ends with newline to make adding code easier - const code = `#!share --from ${variableInfo.kernelName} ${variableInfo.valueName}\n`; - const command: contracts.SendEditableCode = { - language: targetKernelName, - code, - }; - const commandEnvelope: contracts.KernelCommandEnvelope = { - commandType: contracts.SendEditableCodeType, - command, - }; - - await client.kernel.rootKernel.send(commandEnvelope); + await client.kernel.rootKernel.send(commandEnvelope); + } } } } @@ -104,6 +85,7 @@ class WatchWindowTableViewProvider implements vscode.WebviewViewProvider { + Polyglot Notebook: Values - Share to + Share value to... @@ -252,21 +234,22 @@ class WatchWindowTableViewProvider implements vscode.WebviewViewProvider { this.currentNotebookSubscription = { dispose: () => sub.unsubscribe() }; - const kernelNames = Array.from(client.kernel.childKernels.filter(k => k.kernelInfo.supportedKernelCommands.find(ci => ci.name === contracts.RequestValueInfosType)).map(k => k.name)); + const kernels = Array.from(client.kernel.childKernels.filter(k => k.kernelInfo.supportedKernelCommands.find(ci => ci.name === contracts.RequestValueInfosType))); - for (const name of kernelNames) { + for (const kernel of kernels) { try { - const valueInfos = await client.requestValueInfos(name); + const valueInfos = await client.requestValueInfos(kernel.name); for (const valueInfo of valueInfos.valueInfos) { try { - const value = await client.requestValue(valueInfo.name, name); + const value = await client.requestValue(valueInfo.name, kernel.name); const valueName = value.name; const valueValue = value.formattedValue.value; - const commandUrl = `command:polyglot-notebook.shareValueWith?${encodeURIComponent(JSON.stringify({ valueName, kernelName: name }))}`; + const displayName = kernelSelectorUtilities.getKernelInfoDisplayValue(kernel.kernelInfo); + const commandUrl = `command:polyglot-notebook.shareValueWith?${encodeURIComponent(JSON.stringify({ valueName, kernelName: kernel.name }))}`; rows.push({ name: valueName, value: valueValue, - kernel: name, + kernel: displayName, link: commandUrl, }); } catch (e) { diff --git a/src/dotnet-interactive-vscode-common/src/vscodeUtilities.ts b/src/dotnet-interactive-vscode-common/src/vscodeUtilities.ts index 69bd0953e3..c29333228f 100644 --- a/src/dotnet-interactive-vscode-common/src/vscodeUtilities.ts +++ b/src/dotnet-interactive-vscode-common/src/vscodeUtilities.ts @@ -6,8 +6,10 @@ import * as vscode from 'vscode'; import { Eol, WindowsEol, NonWindowsEol } from "./interfaces"; import { Diagnostic, DiagnosticSeverity, LinePosition, LinePositionSpan, DisplayElement, ErrorElement, InteractiveDocumentOutputElement, InteractiveDocument, InteractiveDocumentElement } from './dotnet-interactive/contracts'; -import { getSimpleLanguage } from './interactiveNotebook'; +import * as metadataUtilities from './metadataUtilities'; import * as vscodeLike from './interfaces/vscode-like'; +import * as constants from './constants'; +import * as versionSpecificFunctions from '../versionSpecificFunctions'; export function isInsidersBuild(): boolean { return vscode.version.indexOf('-insider') >= 0; @@ -78,11 +80,59 @@ export function toNotebookDocument(document: vscode.NotebookDocument): Interacti }; } +export function getCellKernelName(cell: vscode.NotebookCell): string { + const cellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(cell); + return cellMetadata.kernelName ?? 'csharp'; +} + +export async function setCellKernelName(cell: vscode.NotebookCell, kernelName: string): Promise { + if (cell.index < 0) { + const x = cell; + } + const cellMetadata: metadataUtilities.NotebookCellMetadata = { + kernelName + }; + const rawCellMetadata = metadataUtilities.getRawNotebookCellMetadataFromNotebookCellMetadata(cellMetadata); + await versionSpecificFunctions.replaceNotebookCellMetadata(cell.notebook.uri, cell.index, rawCellMetadata); +} + +export async function ensureCellKernelKind(cell: vscode.NotebookCell, kind: vscode.NotebookCellKind): Promise { + if (cell.kind === kind) { + return cell; + } + + const newCellData: vscode.NotebookCellData = { + kind: kind, + languageId: kind === vscode.NotebookCellKind.Markup ? 'markdown' : constants.CellLanguageIdentifier, + value: cell.document.getText(), + metadata: cell.metadata, + }; + const cellIndex = cell.index; // this gets reset to -1 when the cell is replaced so we have to capture it here + await versionSpecificFunctions.replaceNotebookCells(cell.notebook.uri, new vscode.NotebookRange(cellIndex, cellIndex + 1), [newCellData]); + const cells = cell.notebook.getCells(); + return cells[cellIndex]; +} + +export async function ensureCellLanguage(cell: vscode.NotebookCell): Promise { + if (cell.kind === vscode.NotebookCellKind.Code) { + if (cell.document.languageId !== constants.CellLanguageIdentifier) { + const updatedCellData = new vscode.NotebookCellData( + vscode.NotebookCellKind.Code, + cell.document.getText(), + constants.CellLanguageIdentifier + ); + updatedCellData.metadata = cell.metadata; + await versionSpecificFunctions.replaceNotebookCells(cell.notebook.uri, new vscode.NotebookRange(cell.index, cell.index + 1), [updatedCellData]); + } + } +} + export function toInteractiveDocumentElement(cell: vscode.NotebookCell): InteractiveDocumentElement { + const cellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(cell); return { executionOrder: cell.executionSummary?.executionOrder ?? 0, kernelName: cell.kind === vscode.NotebookCellKind.Code - ? getSimpleLanguage(cell.document.languageId) + ? cellMetadata.kernelName ?? 'csharp' : 'markdown', contents: cell.document.getText(), outputs: cell.outputs.map(vsCodeCellOutputToContractCellOutput) diff --git a/src/dotnet-interactive-vscode-common/tests/dynamicGrammarSemanticTokenProvider.test.ts b/src/dotnet-interactive-vscode-common/tests/dynamicGrammarSemanticTokenProvider.test.ts new file mode 100644 index 0000000000..8177c7a186 --- /dev/null +++ b/src/dotnet-interactive-vscode-common/tests/dynamicGrammarSemanticTokenProvider.test.ts @@ -0,0 +1,495 @@ +// 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. + +import * as contracts from '../../src/vscode-common/dotnet-interactive/contracts'; +import * as path from 'path'; +import * as vscodeLike from '../../src/vscode-common/interfaces/vscode-like'; +import { expect } from 'chai'; +import { DynamicGrammarSemanticTokenProvider, VSCodeExtensionLike } from '../../src/vscode-common/dynamicGrammarSemanticTokenProvider'; +import { Logger } from '../../src/vscode-common/dotnet-interactive'; + +describe('dynamic grammar tests', async () => { + let logMessages: string[] = []; + let grammarContentsByPath: Map; + const testUri: vscodeLike.Uri = { + fsPath: '', + scheme: '' + }; + let dynamicTokenProvider: DynamicGrammarSemanticTokenProvider; + + const defaultKernelInfos: contracts.KernelInfo[] = [ + { + localName: 'test-csharp', + uri: 'kernel://test-csharp', + languageName: 'csharp', + aliases: ['see-sharp-alias'], + displayName: '', + supportedKernelCommands: [], + supportedDirectives: [], + }, + { + localName: 'test-python', + uri: 'kernel://test-python', + languageName: 'python', + aliases: [], + displayName: '', + supportedKernelCommands: [], + supportedDirectives: [], + } + ]; + + // set this value to true to see all diagnostic messages produced during the test + const displayDiagnosticMessages = false; + + Logger.configure('test-host', (entry) => { + logMessages.push(entry.message); + }); + + async function getTokens(initialKernelName: string, code: string): Promise<{ tokenText: string, tokenType: string }[]> { + const lines = code.split('\n'); + const tokens = await dynamicTokenProvider.getTokens(testUri, initialKernelName, code); + return tokens.map(token => { + return { + tokenText: lines[token.line].substring(token.startColumn, token.endColumn), + tokenType: token.tokenType + }; + }); + } + + it('sub parsing within magic commands', async () => { + const code = '#!some-magic-command --option1 value1 /option2 value2 argument1 "some string"'; + const tokens = await getTokens('test-csharp', code); + expect(tokens).to.deep.equal([ + { + tokenText: '#!', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: 'some-magic-command', + tokenType: 'polyglot-notebook-keyword-control' + }, + { + tokenText: ' ', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '--option1', + tokenType: 'polyglot-notebook-constant-language' + }, + { + tokenText: ' ', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: 'value1', + tokenType: 'polyglot-notebook-constant-numeric' + }, + { + tokenText: ' ', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '/option2', + tokenType: 'polyglot-notebook-constant-language' + }, + { + tokenText: ' ', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: 'value2', + tokenType: 'polyglot-notebook-constant-numeric' + }, + { + tokenText: ' ', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: 'argument1', + tokenType: 'polyglot-notebook-constant-numeric' + }, + { + tokenText: ' ', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '"', + tokenType: 'polyglot-notebook-string' + }, + { + tokenText: 'some string', + tokenType: 'polyglot-notebook-string' + }, + { + tokenText: '"', + tokenType: 'polyglot-notebook-string' + } + ]); + }); + + it("magic commands don't invalidate the language selector", async () => { + const code = ` +// csharp comment 1 +#!some-magic-command +// csharp comment 2 +`; + const tokens = await getTokens('test-csharp', code); + expect(tokens).to.deep.equal([ + { + tokenText: '// csharp comment 1', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '#!', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: 'some-magic-command', + tokenType: 'polyglot-notebook-keyword-control' + }, + { + tokenText: '// csharp comment 2', + tokenType: 'polyglot-notebook-comment' + }, + ]); + }); + + it("tokens are classified according to each language's grammar", async () => { + const code = ` +// C# comment +# C# keyword + +#!test-python + +# Python comment +// Python keyword +`; + const tokens = await getTokens('test-csharp', code); + expect(tokens).to.deep.equal([ + { + tokenText: '// C# comment', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '# C# keyword', + tokenType: 'polyglot-notebook-keyword' + }, + { + tokenText: '# Python comment', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '// Python keyword', + tokenType: 'polyglot-notebook-keyword' + } + ]); + }); + + it('language switch can be specified with a kernel alias', async () => { + const code = ` +#!see-sharp-alias +// C# alias comment +`; + // defaulting to `python` to prove that the kernel selector worked + const tokens = await getTokens('test-python', code); + expect(tokens).to.deep.equal([ + { + tokenText: '// C# alias comment', + tokenType: 'polyglot-notebook-comment' + } + ]); + }); + + it('token parsing is updated when grammar is rebuilt with _ALL_ new KernelInfos', async () => { + const code = ` +// C# Comment +#!test-erlang +% Erlang comment +`; + + // looks like a magic command followed by garbage + const initialTokens = await getTokens('test-csharp', code); + expect(initialTokens).to.deep.equal([ + { + tokenText: '// C# Comment', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '#!', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: 'test-erlang', + tokenType: 'polyglot-notebook-keyword-control' + } + ]); + + // rebuild the grammar with all new KernelInfos + const updatedKernelInfos: contracts.KernelInfo[] = [ + { + localName: 'test-erlang', + uri: 'kernel://test-erlang', + languageName: 'erlang', + aliases: [], + displayName: '', + supportedKernelCommands: [], + supportedDirectives: [], + }, + ...defaultKernelInfos + ]; + dynamicTokenProvider.rebuildNotebookGrammar(testUri, updatedKernelInfos); + + // looks like a proper token + const realTokens = await getTokens('test-csharp', code); + expect(realTokens).to.deep.equal([ + { + tokenText: '// C# Comment', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '% Erlang comment', + tokenType: 'polyglot-notebook-comment' + } + ]); + }); + + it('token parsing is updated when grammar is rebuilt with _SOME_ new KernelInfos', async () => { + const code = ` +// C# Comment +#!test-erlang +% Erlang comment +`; + + // looks like a magic command followed by garbage + const initialTokens = await getTokens('test-csharp', code); + expect(initialTokens).to.deep.equal([ + { + tokenText: '// C# Comment', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '#!', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: 'test-erlang', + tokenType: 'polyglot-notebook-keyword-control' + } + ]); + + // rebuild the grammar with an additional KernelInfo + const newKernelInfo: contracts.KernelInfo = { + localName: 'test-erlang', + uri: 'kernel://test-erlang', + languageName: 'erlang', + aliases: [], + displayName: '', + supportedKernelCommands: [], + supportedDirectives: [], + }; + dynamicTokenProvider.rebuildNotebookGrammar(testUri, [newKernelInfo]); + + // looks like a proper token + const realTokens = await getTokens('test-csharp', code); + expect(realTokens).to.deep.equal([ + { + tokenText: '// C# Comment', + tokenType: 'polyglot-notebook-comment' + }, + { + tokenText: '% Erlang comment', + tokenType: 'polyglot-notebook-comment' + } + ]); + }); + + it('tokens are parsed when kernel specifies a language alias instead of the root language name', async () => { + const kernelInfoWithAlias: contracts.KernelInfo = { + localName: 'test-kernel-with-alias', + uri: 'kernel://test-kernel-with-alias', + languageName: 'see_sharp', // this is an alias and not the real name "csharp" + aliases: [], + displayName: '', + supportedKernelCommands: [], + supportedDirectives: [] + }; + dynamicTokenProvider.rebuildNotebookGrammar(testUri, [kernelInfoWithAlias]); + + const code = ` +#!${kernelInfoWithAlias.localName} +// C# comment from an alias +`; + const tokens = await getTokens(kernelInfoWithAlias.localName, code); + expect(tokens).to.deep.equal([ + { + tokenText: '// C# comment from an alias', + tokenType: 'polyglot-notebook-comment' + } + ]); + }); + + it('tokens are not parsed when no language name is specified in KernelInfo', async () => { + const updatedKernelInfos: contracts.KernelInfo[] = [ + { + localName: 'test-perl', + uri: 'kernel://test-perl', + languageName: undefined, // not specified; no grammar should be applied + aliases: [], + displayName: '', + supportedKernelCommands: [], + supportedDirectives: [], + }, + ...defaultKernelInfos + ]; + dynamicTokenProvider.rebuildNotebookGrammar(testUri, updatedKernelInfos); + + const code = ` +#!test-perl +$x = "this is perl code"; +# Perl comment`; + const tokens = await getTokens('test-csharp', code); + expect(tokens).to.deep.equal([]); // should be empty, but this gives better error messages + }); + + it('tokens are not parsed when language name in KernelInfo does not match any known language', async () => { + const updatedKernelInfos: contracts.KernelInfo[] = [ + { + localName: 'test-perl', + uri: 'kernel://test-perl', + languageName: 'not-perl', // language name is specified, but doesn't match any known language + aliases: [], + displayName: '', + supportedKernelCommands: [], + supportedDirectives: [], + }, + ...defaultKernelInfos + ]; + dynamicTokenProvider.rebuildNotebookGrammar(testUri, updatedKernelInfos); + + const code = ` +#!test-perl +$x = "this is perl code"; +# Perl comment`; + const tokens = await getTokens('test-csharp', code); + expect(tokens).to.deep.equal([]); // should be empty, but this gives better error messages + }); + + //////////////////////////////////////////////////////////////////////////// + // setup stuff + //////////////////////////////////////////////////////////////////////////// + + beforeEach(async () => { + logMessages = []; + + const packageJSON = require(path.join(__dirname, '..', '..', '..', 'package.json')); + + // this mimics other extensions' data and grammar specifications + // TODO: dump and rehydrate a real extension list? seems excessive + const testExtensionGrammars: VSCodeExtensionLike[] = [ + { + id: 'test-extension.csharp', + extensionPath: '', + packageJSON: { + contributes: { + grammars: [ + { + language: 'csharp', + scopeName: 'source.cs', + path: 'csharp.tmGrammar.json' + } + ], + languages: [ + { + id: 'csharp', // same as grammars.language above + aliases: [ + 'see_sharp' + ] + } + ] + } + } + }, + { + id: 'test-extension.python', + extensionPath: '', + packageJSON: { + contributes: { + grammars: [ + { + language: 'python', + scopeName: 'source.python', + path: 'python.tmGrammar.json' + } + ] + // no aliases + } + } + }, + // Erlang isn't in the default kernel list, but it's used later + { + id: 'test-extension.erlang', + extensionPath: '', + packageJSON: { + contributes: { + grammars: [ + { + language: 'erlang', + scopeName: 'source.erlang', + path: 'erlang.tmGrammar.json' + } + ] + // no aliases + } + } + } + ]; + + grammarContentsByPath = new Map(); + grammarContentsByPath.set('csharp.tmGrammar.json', JSON.stringify({ + scopeName: 'source.cs', + patterns: [ + { + name: 'comment.line.csharp', + match: '//.*' + }, + { + name: 'keyword.hash.csharp', + match: '#.*' + } + ] + })); + + grammarContentsByPath.set('erlang.tmGrammar.json', JSON.stringify({ + scopeName: 'source.erlang', + patterns: [ + { + name: 'comment.line.erlang', + match: '%.*' + } + ] + })); + grammarContentsByPath.set('python.tmGrammar.json', JSON.stringify({ + scopeName: 'source.python', + patterns: [ + { + name: 'comment.line.python', + match: '#.*' + }, + { + name: 'keyword.div.python', + match: '//.*' + } + ] + })); + dynamicTokenProvider = new DynamicGrammarSemanticTokenProvider(packageJSON, testExtensionGrammars, path => true, path => grammarContentsByPath.get(path)!); + await dynamicTokenProvider.init(); + + dynamicTokenProvider.rebuildNotebookGrammar(testUri, defaultKernelInfos); + }); + + afterEach(() => { + if (displayDiagnosticMessages) { + expect(logMessages).to.deep.equal([]); + } + }); +}); diff --git a/src/dotnet-interactive-vscode-common/tests/grammar.test.ts b/src/dotnet-interactive-vscode-common/tests/grammar.test.ts deleted file mode 100644 index 1e95efd033..0000000000 --- a/src/dotnet-interactive-vscode-common/tests/grammar.test.ts +++ /dev/null @@ -1,351 +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. - -import { expect } from 'chai'; - -import * as fs from 'fs'; -import * as path from 'path'; -import * as oniguruma from 'vscode-oniguruma'; -import * as vsctm from 'vscode-textmate'; - -describe('TextMate grammar tests', async () => { - before(async () => { - // prepare grammar parser - const nodeModulesDir = path.join(__dirname, '..', '..', '..', 'node_modules'); - const onigWasmPath = path.join(nodeModulesDir, 'vscode-oniguruma', 'release', 'onig.wasm'); - const wasmBin = fs.readFileSync(onigWasmPath).buffer; - await oniguruma.loadWASM(wasmBin); - }); - - // prepare grammar scope loader - const grammarDir = path.join(__dirname, '..', '..', '..', 'syntaxes'); - const registry = new vsctm.Registry({ - onigLib: Promise.resolve({ - createOnigScanner: (sources) => new oniguruma.OnigScanner(sources), - createOnigString: (str) => new oniguruma.OnigString(str) - }), - loadGrammar: (scopeName) => { - return new Promise((resolve, reject) => { - const grammarFileName = `${scopeName}.tmGrammar.json`; - const grammarFilePath = path.join(grammarDir, grammarFileName); - let contents: string; - if (!fs.existsSync(grammarFilePath)) { - // tests can't delegate to well-known languages because those grammars aren't in this repo, so we create a catch-all - const emptyGrammar = { - scopeName, - patterns: [ - { - name: `language.line.${scopeName}`, - match: '^.*$' - } - ] - }; - contents = JSON.stringify(emptyGrammar); - } else { - const buffer = fs.readFileSync(grammarFilePath); - contents = buffer.toString('utf-8'); - } - - const grammar = vsctm.parseRawGrammar(contents, grammarFilePath); - resolve(grammar); - }); - } - }); - - async function getTokens(text: Array, initialScope?: string): Promise>> { - const grammar = await registry.loadGrammar(initialScope ?? 'source.dotnet-interactive'); - let ruleStack = vsctm.INITIAL; - let allTokens = []; - for (let i = 0; i < text.length; i++) { - let lineTokens = []; - const line = text[i]; - const parsedLineTokens = grammar!.tokenizeLine(line, ruleStack); - for (let j = 0; j < parsedLineTokens.tokens.length; j++) { - const token = parsedLineTokens.tokens[j]; - const tokenText = line.substring(token.startIndex, token.endIndex); - lineTokens.push({ - tokenText, - scopes: token.scopes - }); - } - - allTokens.push(lineTokens); - ruleStack = parsedLineTokens.ruleStack; - } - - return allTokens; - } - - it('all supported language specifiers', async () => { - const text = [ - '#!csharp', - '#!cs', - '#!fsharp', - '#!fs', - '#!html', - '#!javascript', - '#!js', - '#!markdown', - '#!md', - '#!mermaid', - '#!powershell', - '#!pwsh', - '#!sql', - '#!sql-adventureworks', - '#!kql', - '#!kql-default', - ]; - const tokens = await getTokens(text); - expect(tokens).to.deep.equal([ - [ - { - tokenText: '#!csharp', - scopes: ['source.dotnet-interactive', 'language.switch.csharp'] - } - ], - [ - { - tokenText: '#!cs', - scopes: ['source.dotnet-interactive', 'language.switch.csharp'] - } - ], - [ - { - tokenText: '#!fsharp', - scopes: ['source.dotnet-interactive', 'language.switch.fsharp'] - } - ], - [ - { - tokenText: '#!fs', - scopes: ['source.dotnet-interactive', 'language.switch.fsharp'] - } - ], - [ - { - tokenText: '#!html', - scopes: ['source.dotnet-interactive', 'language.switch.html'] - } - ], - [ - { - tokenText: '#!javascript', - scopes: ['source.dotnet-interactive', 'language.switch.javascript'] - } - ], - [ - { - tokenText: '#!js', - scopes: ['source.dotnet-interactive', 'language.switch.javascript'] - } - ], - [ - { - tokenText: '#!markdown', - scopes: ['source.dotnet-interactive', 'language.switch.markdown'] - } - ], - [ - { - tokenText: '#!md', - scopes: ['source.dotnet-interactive', 'language.switch.markdown'] - } - ], - [ - { - tokenText: '#!mermaid', - scopes: ['source.dotnet-interactive', 'language.switch.mermaid'] - } - ], - [ - { - tokenText: '#!powershell', - scopes: ['source.dotnet-interactive', 'language.switch.powershell'] - } - ], - [ - { - tokenText: '#!pwsh', - scopes: ['source.dotnet-interactive', 'language.switch.powershell'] - } - ], - [ - { - tokenText: '#!sql', - scopes: ['source.dotnet-interactive', 'language.switch.sql'] - } - ], - [ - { - tokenText: '#!sql-adventureworks', - scopes: ['source.dotnet-interactive', 'language.switch.sql'] - } - ], - [ - { - tokenText: '#!kql', - scopes: ['source.dotnet-interactive', 'language.switch.kql'] - } - ], - [ - { - tokenText: '#!kql-default', - scopes: ['source.dotnet-interactive', 'language.switch.kql'] - } - ], - ]); - }); - - it("magic command doesn't invalidate language", async () => { - const text = [ - '#!fsharp', - '// this is fsharp', - '#!some-magic-command', - '// this is still fsharp' - ]; - const tokens = await getTokens(text); - expect(tokens).to.deep.equal([ - [ - { - tokenText: '#!fsharp', - scopes: ['source.dotnet-interactive', 'language.switch.fsharp'] - } - ], - [ - { - tokenText: '// this is fsharp', - scopes: ['source.dotnet-interactive', 'language.switch.fsharp', 'language.line.source.fsharp'] - } - ], - [ - { - tokenText: '#!', - scopes: ['source.dotnet-interactive', 'language.switch.fsharp', 'comment.line.magic-commands', 'comment.line.magic-commands.hash-bang'] - }, - { - tokenText: 'some-magic-command', - scopes: ['source.dotnet-interactive', 'language.switch.fsharp', 'comment.line.magic-commands', 'keyword.control.magic-commands'] - } - ], - [ - { - tokenText: '// this is still fsharp', - scopes: ['source.dotnet-interactive', 'language.switch.fsharp', 'language.line.source.fsharp'] - } - ] - ]); - }); - - const allLanguages = [ - ['csharp', 'csharp'], - ['fsharp', 'fsharp'], - ['html', 'html'], - ['javascript', 'javascript'], - ['markdown', 'markdown'], - ['mermaid', 'mermaid'], - ['powershell', 'powershell'], - ['sql', 'sql'], - ['sql-adventureworks', 'sql'], - ['kql', 'kql'], - ['kql-default', 'kql'], - ]; - - for (const [magicCommand, language] of allLanguages) { - it(`language ${language} can switch to all other languages`, async () => { - let text = [`#!${magicCommand}`]; - let expected = [ - [ - { - tokenText: `#!${magicCommand}`, - scopes: ['source.dotnet-interactive', `language.switch.${language}`] - } - ] - ]; - for (const [otherMagicCommand, otherLanguage] of allLanguages) { - text.push(`#!${otherMagicCommand}`); - expected.push([ - { - tokenText: `#!${otherMagicCommand}`, - scopes: ['source.dotnet-interactive', `language.switch.${otherLanguage}`] - } - ]); - } - - const tokens = await getTokens(text); - expect(tokens).to.deep.equal(expected); - }); - } - - it('sub-parsing within magic commands', async () => { - const text = ['#!share --from csharp x "some string" /a b']; - const tokens = await getTokens(text, 'source.dotnet-interactive.magic-commands'); - expect(tokens).to.deep.equal([ - [ - { - tokenText: '#!', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'comment.line.magic-commands.hash-bang'] - }, - { - tokenText: 'share', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'keyword.control.magic-commands'] - }, - { - tokenText: ' ', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands'] - }, - { - tokenText: '--from', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'constant.language.magic-commands'] - }, - { - tokenText: ' ', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands'] - }, - { - tokenText: 'csharp', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'variable.parameter.magic-commands'] - }, - { - tokenText: ' ', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands'] - }, - { - tokenText: 'x', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'variable.parameter.magic-commands'] - }, - { - tokenText: ' ', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands'] - }, - { - tokenText: '"', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'string.quoted.double.magic-commands'] - }, - { - tokenText: 'some string', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'string.quoted.double.magic-commands'] - }, - { - tokenText: '"', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'string.quoted.double.magic-commands'] - }, - { - tokenText: ' ', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands'] - }, - { - tokenText: '/a', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'constant.language.magic-commands'] - }, - { - tokenText: ' ', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands'] - }, - { - tokenText: 'b', - scopes: ['source.dotnet-interactive.magic-commands', 'comment.line.magic-commands', 'variable.parameter.magic-commands'] - } - ] - ]); - }); -}); diff --git a/src/dotnet-interactive-vscode-common/tests/ipynbMetadata.test.ts b/src/dotnet-interactive-vscode-common/tests/ipynbMetadata.test.ts deleted file mode 100644 index b0328ad11f..0000000000 --- a/src/dotnet-interactive-vscode-common/tests/ipynbMetadata.test.ts +++ /dev/null @@ -1,578 +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. - -import { expect } from 'chai'; -import { DotNetCellMetadata, getCellLanguage, getDotNetMetadata, getLanguageInfoMetadata, LanguageInfoMetadata, withDotNetCellMetadata, withDotNetKernelMetadata } from '../../src/vscode-common/ipynbUtilities'; - -describe('ipynb metadata tests', () => { - describe('document metadata', () => { - it(`document metadata can be read when present`, () => { - const documentMetadata = { - custom: { - metadata: { - language_info: { - name: 'csharp' - } - } - } - }; - const languageInfoMetadata = getLanguageInfoMetadata(documentMetadata); - expect(languageInfoMetadata.name).to.equal('csharp'); - }); - - it(`document metadata well-known values are transformed`, () => { - const wellKnownLanguagePairs = [ - ['C#', 'csharp'], - ['F#', 'fsharp'], - ['PowerShell', 'pwsh'] - ]; - for (const languagePair of wellKnownLanguagePairs) { - const languageName = languagePair[0]; - const expectedResult = languagePair[1]; - const documentMetadata = { - custom: { - metadata: { - language_info: { - name: languageName - } - } - } - }; - const languageInfoMetadata = getLanguageInfoMetadata(documentMetadata); - expect(languageInfoMetadata.name).to.equal(expectedResult); - } - }); - - it(`document metadata non-special-cased language returns as self`, () => { - const documentMetadata = { - custom: { - metadata: { - language_info: { - name: 'see-sharp' - } - } - } - }; - const languageInfoMetadata = getLanguageInfoMetadata(documentMetadata); - expect(languageInfoMetadata.name).to.equal('see-sharp'); - }); - - it(`document metadata with undefined language also comes back as undefined`, () => { - const documentMetadata = { - custom: { - metadata: { - language_info: { - name: undefined - } - } - } - }; - const languageInfoMetadata = getLanguageInfoMetadata(documentMetadata); - expect(languageInfoMetadata.name).to.equal(undefined); - }); - - it(`document metadata with non-string value comes back as undefined`, () => { - const documentMetadata = { - custom: { - metadata: { - language_info: { - name: 42 - } - } - } - }; - const languageInfoMetadata = getLanguageInfoMetadata(documentMetadata); - expect(languageInfoMetadata.name).to.equal(undefined); - }); - - it(`document metadata is default when not the correct shape 1`, () => { - const documentMetadata = { - custom: { - metadata_but_not_the_correct_shape: { - language_info: { - name: 'csharp' - } - } - } - }; - const languageInfoMetadata = getLanguageInfoMetadata(documentMetadata); - expect(languageInfoMetadata).to.deep.equal({ - name: undefined - }); - }); - - it(`document metadata is default when not the correct shape 2`, () => { - const documentMetadata = { - custom: { - metadata: { - language_info_but_not_the_correct_shape: { - name: 'csharp' - } - } - } - }; - const languageInfoMetadata = getLanguageInfoMetadata(documentMetadata); - expect(languageInfoMetadata).to.deep.equal({ - name: undefined - }); - }); - - it(`document metadata is default when not the correct shape 3`, () => { - const documentMetadata = { - custom: { - metadata: { - language_info: { - name_but_not_the_correct_shape: 'csharp' - } - } - } - }; - const languageInfoMetadata = getLanguageInfoMetadata(documentMetadata); - expect(languageInfoMetadata).to.deep.equal({ - name: undefined - }); - }); - }); - - describe('kernelspec metadata', () => { - it(`sets kernelspec data when document metadata is undefined`, () => { - const documentMetadata = undefined; - const newDocumentMetadata = withDotNetKernelMetadata(documentMetadata); - expect(newDocumentMetadata).to.deep.equal({ - custom: { - metadata: { - kernelspec: { - display_name: '.NET (C#)', - language: 'C#', - name: '.net-csharp', - }, - language_info: { - file_extension: '.cs', - mimetype: 'text/x-csharp', - name: 'C#', - pygments_lexer: 'csharp', - version: '9.0', - }, - } - } - }); - }); - - it(`sets kernelspec data when not present`, () => { - const documentMetadata = { - custom: { - } - }; - const newDocumentMetadata = withDotNetKernelMetadata(documentMetadata); - expect(newDocumentMetadata).to.deep.equal({ - custom: { - metadata: { - kernelspec: { - display_name: '.NET (C#)', - language: 'C#', - name: '.net-csharp', - }, - language_info: { - file_extension: '.cs', - mimetype: 'text/x-csharp', - name: 'C#', - pygments_lexer: 'csharp', - version: '9.0', - }, - } - } - }); - }); - - it(`merges kernelspec data with existing`, () => { - const documentMetadata = { - custom: { - metadata: { - kernelspec: { - some_existing_key: 'some existing value' - } - } - } - }; - const newDocumentMetadata = withDotNetKernelMetadata(documentMetadata); - expect(newDocumentMetadata).to.deep.equal({ - custom: { - metadata: { - kernelspec: { - display_name: '.NET (C#)', - language: 'C#', - name: '.net-csharp', - some_existing_key: 'some existing value', - }, - language_info: { - file_extension: '.cs', - mimetype: 'text/x-csharp', - name: 'C#', - pygments_lexer: 'csharp', - version: '9.0', - }, - } - } - }); - }); - - it(`preserves other metadata while setting kernelspec`, () => { - const documentMetadata = { - custom: { - metadata: { - some_custom_metadata: { - key1: 'value 1' - } - }, - some_other_custom_data: { - key2: 'value 2' - } - } - }; - const newDocumentMetadata = withDotNetKernelMetadata(documentMetadata); - expect(newDocumentMetadata).to.deep.equal({ - custom: { - metadata: { - kernelspec: { - display_name: '.NET (C#)', - language: 'C#', - name: '.net-csharp', - }, - language_info: { - file_extension: '.cs', - mimetype: 'text/x-csharp', - name: 'C#', - pygments_lexer: 'csharp', - version: '9.0', - }, - some_custom_metadata: { - key1: 'value 1' - } - }, - some_other_custom_data: { - key2: 'value 2' - } - } - }); - }); - - it(`preserves other metadata when kernelspec is already present`, () => { - const documentMetadata = { - custom: { - metadata: { - kernelspec: { - name: 'this will be overwritten', - some_existing_key: 'some existing value' - }, - some_custom_metadata: { - key1: 'value 1' - } - }, - some_other_custom_data: { - key2: 'value 2' - } - } - }; - const newDocumentMetadata = withDotNetKernelMetadata(documentMetadata); - expect(newDocumentMetadata).to.deep.equal({ - custom: { - metadata: { - kernelspec: { - display_name: '.NET (C#)', - language: 'C#', - name: '.net-csharp', - some_existing_key: 'some existing value' - }, - language_info: { - file_extension: '.cs', - mimetype: 'text/x-csharp', - name: 'C#', - pygments_lexer: 'csharp', - version: '9.0', - }, - some_custom_metadata: { - key1: 'value 1' - } - }, - some_other_custom_data: { - key2: 'value 2' - } - } - }); - }); - - it(`preserves original kernelspec when it is already present and is a .net kernelspec`, () => { - const documentMetadata = { - custom: { - metadata: { - kernelspec: { - display_name: ".NET (PowerShell)", - language: "PowerShell", - name: ".net-powershell" - }, - language_info: { - file_extension: ".ps1", - mimetype: "text/x-powershell", - name: "PowerShell", - pygments_lexer: "powershell", - version: "7.0" - }, - some_custom_metadata: { - key1: 'value 1' - } - }, - some_other_custom_data: { - key2: 'value 2' - } - } - }; - const newDocumentMetadata = withDotNetKernelMetadata(documentMetadata); - expect(newDocumentMetadata).to.deep.equal({ - custom: { - metadata: { - kernelspec: { - display_name: ".NET (PowerShell)", - language: "PowerShell", - name: ".net-powershell" - }, - language_info: { - file_extension: ".ps1", - mimetype: "text/x-powershell", - name: "PowerShell", - pygments_lexer: "powershell", - version: "7.0" - }, - some_custom_metadata: { - key1: 'value 1' - } - }, - some_other_custom_data: { - key2: 'value 2' - } - } - }); - }); - }); - - describe('cell metadata', () => { - it(`cell metadata can be read when present`, () => { - const cellMetadata = { - custom: { - metadata: { - dotnet_interactive: { - language: 'see-sharp' - } - } - } - }; - const dotnetMetadata = getDotNetMetadata(cellMetadata); - expect(dotnetMetadata.language).to.equal('see-sharp'); - }); - - it(`cell metadata is empty when not the correct shape 1`, () => { - const cellMetadata = { - custom: { - metadata_but_not_the_correct_shape: { - dotnet_interactive: { - language: 'see-sharp' - } - } - } - }; - const dotnetMetadata = getDotNetMetadata(cellMetadata); - expect(dotnetMetadata.language).to.equal(undefined); - }); - - it(`cell metadata is empty when not the correct shape 2`, () => { - const cellMetadata = { - custom: { - metadata: { - dotnet_interactive_but_not_the_correct_shape: { - language: 'see-sharp' - } - } - } - }; - const dotnetMetadata = getDotNetMetadata(cellMetadata); - expect(dotnetMetadata.language).to.equal(undefined); - }); - - it(`cell metadata is empty when not the correct shape 3`, () => { - const cellMetadata = { - custom: { - metadata: { - dotnet_interactive: { - language_but_not_the_correct_shape: 'see-sharp' - } - } - } - }; - const dotnetMetadata = getDotNetMetadata(cellMetadata); - expect(dotnetMetadata.language).to.equal(undefined); - }); - - it(`cell metadata returns empty info if shape isn't correct`, () => { - const cellMetadata = { - custom: { - metadata: { - dotnet_interactive: { - language: 42 // not a string - } - } - } - }; - const dotnetMetadata = getDotNetMetadata(cellMetadata); - expect(dotnetMetadata.language).to.equal(undefined); - }); - - it(`cell metadata is added when not present`, () => { - const existingCellMetadata = {}; - const updatedCellMetadata = withDotNetCellMetadata(existingCellMetadata, 'pwsh'); - expect(updatedCellMetadata).to.deep.equal({ - custom: { - metadata: { - dotnet_interactive: { - language: 'pwsh' - } - } - } - }); - }); - - it(`existing cell metadata is preserved when updated`, () => { - const existingCellMetadata = { - number: 42, - custom: { - anotherNumber: 43, - metadata: { - stillAnotherNumber: 44, - dotnet_interactive: { - aReallyDeepNumber: 45, - language: 'not-pwsh' - } - } - } - }; - const updatedCellMetadata = withDotNetCellMetadata(existingCellMetadata, 'pwsh'); - expect(updatedCellMetadata).to.deep.equal({ - number: 42, - custom: { - anotherNumber: 43, - metadata: { - stillAnotherNumber: 44, - dotnet_interactive: { - aReallyDeepNumber: 45, - language: 'pwsh' - } - } - } - }); - }); - }); - - describe('cell language selection', () => { - it(`sets the cell language first from cell text`, () => { - const cellText = '#!javascript\r\nalert(1+1);'; - const cellMetadata: DotNetCellMetadata = { - language: 'pwsh', - }; - const documentMetadata: LanguageInfoMetadata = { - name: 'fsharp', - }; - const cellReportedLanguage = 'dotnet-interactive.csharp'; - const cellLanguage = getCellLanguage(cellText, cellMetadata, documentMetadata, cellReportedLanguage); - expect(cellLanguage).to.equal('dotnet-interactive.javascript'); - }); - - it(`does not use the cell text for the language if a non-language specifier is on the first line; cell metadata is used`, () => { - const cellText = '#!about\r\n1+1'; - const cellMetadata: DotNetCellMetadata = { - language: 'pwsh', - }; - const documentMetadata: LanguageInfoMetadata = { - name: 'fsharp', - }; - const cellReportedLanguage = 'dotnet-interactive.csharp'; - const cellLanguage = getCellLanguage(cellText, cellMetadata, documentMetadata, cellReportedLanguage); - expect(cellLanguage).to.equal('dotnet-interactive.pwsh'); - }); - - it(`sets the cell language second from cell metadata if cell text doesn't specify a language`, () => { - const cellText = '1+1'; - const cellMetadata: DotNetCellMetadata = { - language: 'pwsh', - }; - const documentMetadata: LanguageInfoMetadata = { - name: 'fsharp', - }; - const cellReportedLanguage = 'dotnet-interactive.csharp'; - const cellLanguage = getCellLanguage(cellText, cellMetadata, documentMetadata, cellReportedLanguage); - expect(cellLanguage).to.equal('dotnet-interactive.pwsh'); - }); - - it(`sets the cell language third from document metadata if cell reported language is unsupported`, () => { - const cellText = '1+1'; - const cellMetadata: DotNetCellMetadata = { - language: undefined, - }; - const documentMetadata: LanguageInfoMetadata = { - name: 'fsharp', - }; - const cellReportedLanguage = 'python'; - const cellLanguage = getCellLanguage(cellText, cellMetadata, documentMetadata, cellReportedLanguage); - expect(cellLanguage).to.equal('dotnet-interactive.fsharp'); - }); - - it(`cell metadata is undefined, cell reported language is used if it's a dotnet language`, () => { - const cellText = '1+1'; - const cellMetadata: DotNetCellMetadata = { - language: undefined, - }; - const documentMetadata: LanguageInfoMetadata = { - name: 'fsharp', - }; - const cellReportedLanguage = 'dotnet-interactive.csharp'; - const cellLanguage = getCellLanguage(cellText, cellMetadata, documentMetadata, cellReportedLanguage); - expect(cellLanguage).to.equal('dotnet-interactive.csharp'); - }); - - it(`cell metadata is undefined, document metadata is undefined, fall back to what the cell thinks it is, but make it a dotnet-interactive langugae`, () => { - const cellText = '1+1'; - const cellMetadata: DotNetCellMetadata = { - language: undefined, - }; - const documentMetadata: LanguageInfoMetadata = { - name: undefined, - }; - const cellReportedLanguage = 'csharp'; - const cellLanguage = getCellLanguage(cellText, cellMetadata, documentMetadata, cellReportedLanguage); - expect(cellLanguage).to.equal('dotnet-interactive.csharp'); - }); - - it(`cell metadata is undefined, document metadata is undefined, fall back to what the cell thinks it is; supported`, () => { - const cellText = '1+1'; - const cellMetadata: DotNetCellMetadata = { - language: undefined, - }; - const documentMetadata: LanguageInfoMetadata = { - name: undefined, - }; - const cellReportedLanguage = 'dotnet-interactive.csharp'; - const cellLanguage = getCellLanguage(cellText, cellMetadata, documentMetadata, cellReportedLanguage); - expect(cellLanguage).to.equal('dotnet-interactive.csharp'); - }); - - it(`cell metadata is undefined, document metadata is undefined, fall back to what the cell thinks it is; unsupported`, () => { - const cellText = '1+1'; - const cellMetadata: DotNetCellMetadata = { - language: undefined, - }; - const documentMetadata: LanguageInfoMetadata = { - name: undefined, - }; - const cellReportedLanguage = 'ruby'; - const cellLanguage = getCellLanguage(cellText, cellMetadata, documentMetadata, cellReportedLanguage); - expect(cellLanguage).to.equal('ruby'); - }); - }); -}); diff --git a/src/dotnet-interactive-vscode-common/tests/kernelSelectorUtilities.test.ts b/src/dotnet-interactive-vscode-common/tests/kernelSelectorUtilities.test.ts new file mode 100644 index 0000000000..7d38fdc559 --- /dev/null +++ b/src/dotnet-interactive-vscode-common/tests/kernelSelectorUtilities.test.ts @@ -0,0 +1,62 @@ +// 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. + +import * as contracts from '../../src/vscode-common/dotnet-interactive/contracts'; +import * as kernelSelectorUtilities from '../../src/vscode-common/kernelSelectorUtilities'; +import * as vscodeLike from '../../src/vscode-common/interfaces/vscode-like'; +import { expect } from 'chai'; +import { CompositeKernel, Kernel } from '../../src/vscode-common/dotnet-interactive'; + +describe('kernel selector utility tests', async () => { + + it('kernel selector options are properly generated from composite kernel and notebook metadata', () => { + const kernel = new CompositeKernel('composite'); + + // add C# kernel that supports `SubmitCode` + const cs = new Kernel('csharp', 'csharp', '10.0', 'See Sharp'); + cs.kernelInfo.supportedKernelCommands = [{ name: contracts.SubmitCodeType }]; + kernel.add(cs); + + // add webview kernel that _doesn't_ support `SubmitCode` + const wv = new Kernel('webview'); + kernel.add(wv); + + const notebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'some-notebook', + scheme: 'file' + }, + metadata: { + polyglot_notebook: { + kernelInfo: { + defaultKernelName: 'unused', + items: [ + { + name: 'csharp', + aliases: [] + }, + { + name: 'fsharp', + aliases: [] + } + ] + } + } + } + }; + + const kernelSelectorOptions = kernelSelectorUtilities.getKernelSelectorOptions(kernel, notebookDocument, contracts.SubmitCodeType); + expect(kernelSelectorOptions).to.deep.equal([ + { + kernelName: 'csharp', + displayValue: 'csharp - See Sharp', + languageName: 'csharp' + }, + { + kernelName: 'fsharp', + displayValue: 'fsharp - fsharp' + } + ]); + }); + +}); diff --git a/src/dotnet-interactive-vscode-common/tests/metadataUtilities.test.ts b/src/dotnet-interactive-vscode-common/tests/metadataUtilities.test.ts new file mode 100644 index 0000000000..3bef518548 --- /dev/null +++ b/src/dotnet-interactive-vscode-common/tests/metadataUtilities.test.ts @@ -0,0 +1,713 @@ +// 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. + +import * as contracts from '../../src/vscode-common/dotnet-interactive/contracts'; +import * as metadataUtilities from '../../src/vscode-common/metadataUtilities'; +import * as vscodeLike from '../../src/vscode-common/interfaces/vscode-like'; +import { expect } from 'chai'; +import { CompositeKernel, Kernel } from '../../src/vscode-common/dotnet-interactive'; + +describe('metadata utility tests', async () => { + + it('ipynb notebooks can be identified', () => { + const notebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'notebook.ipynb', + scheme: 'untitled' + }, + metadata: {} + }; + const isIpynb = metadataUtilities.isIpynbNotebook(notebookDocument); + expect(isIpynb).to.be.true; + }); + + it('interactive notebook can be identified from .dib', () => { + const notebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'notebook.dib', + scheme: 'untitled' + }, + metadata: {} + }; + const isDotNet = metadataUtilities.isDotNetNotebook(notebookDocument); + expect(isDotNet).to.be.true; + }); + + it('interactive notebook can be identified from metadata in .ipynb', () => { + const notebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'notebook.ipynb', + scheme: 'untitled' + }, + metadata: { + custom: { + metadata: { + kernelspec: { + name: '.net-csharp' + } + } + } + } + }; + const isDotNet = metadataUtilities.isDotNetNotebook(notebookDocument); + expect(isDotNet).to.be.true; + }); + + it('non-interactive notebook is not identified from metadata in .ipynb', () => { + const notebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'notebook.ipynb', + scheme: 'untitled' + }, + metadata: { + custom: { + metadata: { + kernelspec: { + name: 'python' + } + } + } + } + }; + const isDotNet = metadataUtilities.isDotNetNotebook(notebookDocument); + expect(isDotNet).to.be.false; + }); + + it('cell metadata can be extracted from an interactive document element with old metadata', () => { + const interactiveDocumentElement: contracts.InteractiveDocumentElement = { + contents: '', + outputs: [], + executionOrder: 0, + metadata: { + dotnet_interactive: { + language: 'fsharp' + } + } + }; + const notebookCellMetadata = metadataUtilities.getNotebookCellMetadataFromInteractiveDocumentElement(interactiveDocumentElement); + expect(notebookCellMetadata).to.deep.equal({ + kernelName: 'fsharp' + }); + }); + + it('cell metadata can be extracted from an interactive document element', () => { + const interactiveDocumentElement: contracts.InteractiveDocumentElement = { + contents: '', + outputs: [], + executionOrder: 0, + metadata: { + polyglot_notebook: { + kernelName: 'fsharp' + } + } + }; + const notebookCellMetadata = metadataUtilities.getNotebookCellMetadataFromInteractiveDocumentElement(interactiveDocumentElement); + expect(notebookCellMetadata).to.deep.equal({ + kernelName: 'fsharp' + }); + }); + + it('cell metadata can be extracted from a notebook cell with old metadata', () => { + const notebookCell: vscodeLike.NotebookCell = { + kind: vscodeLike.NotebookCellKind.Code, + metadata: { + custom: { + metadata: { + dotnet_interactive: { + language: 'fsharp' + } + } + } + } + }; + const notebookCellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(notebookCell); + expect(notebookCellMetadata).to.deep.equal({ + kernelName: 'fsharp' + }); + }); + + it('cell metadata can be extracted from a notebook cell', () => { + const notebookCell: vscodeLike.NotebookCell = { + kind: vscodeLike.NotebookCellKind.Code, + metadata: { + custom: { + metadata: { + polyglot_notebook: { + kernelName: 'fsharp' + } + } + } + } + }; + const notebookCellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(notebookCell); + expect(notebookCellMetadata).to.deep.equal({ + kernelName: 'fsharp' + }); + }); + + it('cell metadata can be extracted from a notebook cell', () => { + + }); + + it('notebook metadata can be extracted from an interactive document', () => { + const interactiveDocument: contracts.InteractiveDocument = { + elements: [], + metadata: { + custom: { + name: 'some value' + }, + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + } + }; + const notebookDocumentMetadata = metadataUtilities.getNotebookDocumentMetadataFromInteractiveDocument(interactiveDocument); + expect(notebookDocumentMetadata).to.deep.equal({ + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + }); + }); + + it('notebook metadata can be extracted from vscode notebook document', () => { + const notebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'some-notebook.ipynb', + scheme: 'file' + }, + metadata: { + custom: { + name: 'some value' + }, + polyglot_notebook: { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + } + } + }; + const notebookDocumentMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(notebookDocument); + expect(notebookDocumentMetadata).to.deep.equal({ + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + }); + }); + + it('kernel infos can be created from .dib notebook document', () => { + const notebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'notebook.dib', + scheme: 'file' + }, + metadata: { + polyglot_notebook: { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'fsharp', + aliases: [] + }, + { + name: 'snake', + languageName: 'python', + aliases: [] + } + ] + } + } + } + }; + const kernelInfos = metadataUtilities.getKernelInfosFromNotebookDocument(notebookDocument); + expect(kernelInfos).to.deep.equal([ + { + localName: 'fsharp', + uri: 'unused', + aliases: [], + languageName: undefined, + displayName: 'unused', + supportedKernelCommands: [], + supportedDirectives: [] + }, + { + localName: 'snake', + uri: 'unused', + aliases: [], + languageName: 'python', + displayName: 'unused', + supportedKernelCommands: [], + supportedDirectives: [] + } + ]); + }); + + it('kernel infos can be created from .ipynb notebook document', () => { + const notebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'notebook.ipynb', + scheme: 'file' + }, + metadata: { + custom: { + metadata: { + polyglot_notebook: { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'fsharp', + aliases: [] + }, + { + name: 'snake', + languageName: 'python', + aliases: [] + } + ] + } + } + } + } + } + }; + const kernelInfos = metadataUtilities.getKernelInfosFromNotebookDocument(notebookDocument); + expect(kernelInfos).to.deep.equal([ + { + localName: 'fsharp', + uri: 'unused', + aliases: [], + languageName: undefined, + displayName: 'unused', + supportedKernelCommands: [], + supportedDirectives: [] + }, + { + localName: 'snake', + uri: 'unused', + aliases: [], + languageName: 'python', + displayName: 'unused', + supportedKernelCommands: [], + supportedDirectives: [] + } + ]); + }); + + it('kernelspec metadata can be created from notebook document metadata (C#)', () => { + const notebookDocumentMetadata: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: 'csharp', + items: [] + } + }; + const kernelspecMetadata = metadataUtilities.getKernelspecMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata); + expect(kernelspecMetadata).to.deep.equal({ + display_name: '.NET (C#)', + language: 'C#', + name: '.net-csharp' + }); + }); + + it('kernelspec metadata can be created from notebook document metadata (F#)', () => { + const notebookDocumentMetadata: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [] + } + }; + const kernelspecMetadata = metadataUtilities.getKernelspecMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata); + expect(kernelspecMetadata).to.deep.equal({ + display_name: '.NET (F#)', + language: 'F#', + name: '.net-fsharp' + }); + }); + + it('kernelspec metadata can be created from notebook document metadata (PowerShell)', () => { + const notebookDocumentMetadata: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: 'pwsh', + items: [] + } + }; + const kernelspecMetadata = metadataUtilities.getKernelspecMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata); + expect(kernelspecMetadata).to.deep.equal({ + display_name: '.NET (PowerShell)', + language: 'PowerShell', + name: '.net-pwsh' + }); + }); + + it('new ipynb metadata can be created from existing data', () => { + const notebookMetadata: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: 'csharp', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + } + ] + } + }; + const existingMetadata: { [key: string]: any } = { + custom: { + metadata: { + kernelspec: 'this gets replaced', + someKey: 'some value' + }, + metadata2: 'electric boogaloo' + }, + notCustom: 'not custom' + }; + const newRawMetadata = metadataUtilities.createNewIpynbMetadataWithNotebookDocumentMetadata(existingMetadata, notebookMetadata); + expect(newRawMetadata).to.deep.equal({ + custom: { + metadata: { + kernelspec: { + display_name: '.NET (C#)', + language: 'C#', + name: '.net-csharp' + }, + polyglot_notebook: { + kernelInfo: { + defaultKernelName: 'csharp', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + } + ] + } + }, + someKey: 'some value' + }, + metadata2: 'electric boogaloo' + }, + notCustom: 'not custom' + }); + }); + + it('notebook metadata can be extracted from a composite kernel', () => { + const kernel = new CompositeKernel('composite'); + const cs = new Kernel('csharp', 'csharp'); + cs.kernelInfo.aliases.push('cs'); + kernel.add(cs); + const fs = new Kernel('fsharp', 'fsharp'); + fs.kernelInfo.aliases.push('fs'); + kernel.add(fs); + kernel.defaultKernelName = fs.name; + + const notebookDocumentMetadata = metadataUtilities.getNotebookDocumentMetadataFromCompositeKernel(kernel); + expect(notebookDocumentMetadata).to.deep.equal({ + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + }, + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + }); + }); + + it('interactive document cell metadata can be created from notebook cell metadata', () => { + const notebookCellMetadata: metadataUtilities.NotebookCellMetadata = { + kernelName: 'fsharp' + }; + const interactiveDocumentElementMetadata = metadataUtilities.getRawInteractiveDocumentElementMetadataFromNotebookCellMetadata(notebookCellMetadata); + expect(interactiveDocumentElementMetadata).to.deep.equal({ + kernelName: 'fsharp' + }); + }); + + it('notebook cell metadata can be created from notebook cell metadata', () => { + const notebookCellMetadata: metadataUtilities.NotebookCellMetadata = { + kernelName: 'fsharp' + }; + const rawNotebookDocumentElementMetadata = metadataUtilities.getRawNotebookCellMetadataFromNotebookCellMetadata(notebookCellMetadata); + expect(rawNotebookDocumentElementMetadata).to.deep.equal({ + custom: { + metadata: { + dotnet_interactive: { + language: 'fsharp' + }, + polyglot_notebook: { + kernelName: 'fsharp' + } + } + } + }); + }); + + it('interactive document metadata can be created from notebook metadata', () => { + const notebookDocumentMetadata: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + }, + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + }; + const interactiveDocumentMetadata = metadataUtilities.getRawInteractiveDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata); + expect(interactiveDocumentMetadata).to.deep.equal({ + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + }, + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + }); + }); + + it('notebook document metadata can be created from notebook metadata for ipynb', () => { + const notebookDocumentMetadata: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + }, + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + }; + const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata, true); + expect(rawNotebookDocumentMetadata).to.deep.equal({ + custom: { + metadata: { + kernelspec: { + display_name: ".NET (F#)", + language: "F#", + name: ".net-fsharp" + }, + polyglot_notebook: { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + }, + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + } + } + } + }); + }); + + it('notebook document metadata can be created from notebook metadata for dib', () => { + const notebookDocumentMetadata: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + }, + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + }; + const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookDocumentMetadata, false); + expect(rawNotebookDocumentMetadata).to.deep.equal({ + polyglot_notebook: { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + }, + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + } + }); + }); + + it('notebook cell metadata can be merged', () => { + const baseMetadata: metadataUtilities.NotebookCellMetadata = { + kernelName: 'csharp' + }; + const metadataWithNewValues: metadataUtilities.NotebookCellMetadata = { + kernelName: 'fsharp' + }; + const resultMetadata = metadataUtilities.mergeNotebookCellMetadata(baseMetadata, metadataWithNewValues); + expect(resultMetadata).to.deep.equal({ + kernelName: 'fsharp' + }); + }); + + it('notebook metadata can be merged', () => { + const baseMetadata: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: 'base default kernel name', + items: [ + { + name: 'csharp', + aliases: [], + }, + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + }; + const metadataWithNewValues: metadataUtilities.NotebookDocumentMetadata = { + kernelInfo: { + defaultKernelName: 'original default kernel name will be retained', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + } + ] + } + }; + const resultMetadata = metadataUtilities.mergeNotebookDocumentMetadata(baseMetadata, metadataWithNewValues); + expect(resultMetadata).to.deep.equal({ + kernelInfo: { + defaultKernelName: 'base default kernel name', + items: [ + { + name: 'csharp', + aliases: ['cs'], + languageName: 'csharp' + }, + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + }); + }); + + it('raw metadata can be merged', () => { + const baseMetadata: { [key: string]: any } = { + custom: { + name: 'some value' + }, + polyglot_notebook: { + this_will: 'be replaced' + } + }; + const metadataWithNewValues: { [key: string]: any } = { + polyglot_notebook: { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + } + }; + const resultMetadata = metadataUtilities.mergeRawMetadata(baseMetadata, metadataWithNewValues); + expect(resultMetadata).to.deep.equal({ + custom: { + name: 'some value' + }, + polyglot_notebook: { + kernelInfo: { + defaultKernelName: 'fsharp', + items: [ + { + name: 'fsharp', + aliases: ['fs'], + languageName: 'fsharp' + } + ] + } + } + }); + }); + +}); diff --git a/src/dotnet-interactive-vscode-common/tests/misc.test.ts b/src/dotnet-interactive-vscode-common/tests/misc.test.ts index 9f1e536686..b9b7bf8069 100644 --- a/src/dotnet-interactive-vscode-common/tests/misc.test.ts +++ b/src/dotnet-interactive-vscode-common/tests/misc.test.ts @@ -4,66 +4,12 @@ import { expect } from 'chai'; import { DisplayElement, ErrorElement, TextElement } from '../../src/vscode-common/dotnet-interactive/contracts'; import { isDisplayOutput, isErrorOutput, isTextOutput, reshapeOutputValueForVsCode } from '../../src/vscode-common/interfaces/utilities'; -import { isDotNetNotebookMetadata, isIpynbFile } from '../../src/vscode-common/ipynbUtilities'; import { createUri, debounce, executeSafe, getVersionNumber, getWorkingDirectoryForNotebook, parse, processArguments, stringify } from '../../src/vscode-common/utilities'; import { decodeToString } from './utilities'; import * as vscodeLike from '../../src/vscode-common/interfaces/vscode-like'; describe('Miscellaneous tests', () => { - describe('.NET notebook detection', () => { - it('.NET notebook is detected by kernelspec', () => { - const metadata = { - custom: { - metadata: { - kernelspec: { - name: '.net-fsharp-dev' - } - } - } - }; - expect(isDotNetNotebookMetadata(metadata)).is.true; - }); - - it('.NET notebook is detected by language info', () => { - const metadata = { - custom: { - metadata: { - language_info: { - name: 'dotnet-interactive.pwsh' - } - } - } - }; - expect(isDotNetNotebookMetadata(metadata)).is.true; - }); - - it('non-.NET notebook is not detected by kernelspec', () => { - const metadata = { - custom: { - metadata: { - kernelspec: { - name: 'python' - } - } - } - }; - expect(isDotNetNotebookMetadata(metadata)).is.false; - }); - - it('non-.NET notebook is not detected by language info', () => { - const metadata = { - custom: { - metadata: { - language_info: { - name: 'python' - } - } - } - }; - expect(isDotNetNotebookMetadata(metadata)).is.false; - }); - }); it(`verify command and argument replacement is as expected`, () => { let template = { @@ -267,16 +213,4 @@ describe('Miscellaneous tests', () => { }); } }); - - describe('.ipynb helpers', () => { - it('file extension of .ipynb matches', () => { - expect(isIpynbFile('notebook.ipynb')).to.be.true; - expect(isIpynbFile('NOTEBOOK.IPYNB')).to.be.true; - }); - - it(`file extension of .dib doesn't match`, () => { - expect(isIpynbFile('notebook.dib')).to.be.false; - expect(isIpynbFile('notebook.dotnet-interactive')).to.be.false; - }); - }); }); diff --git a/src/dotnet-interactive-vscode-insiders/.vscode/settings.json b/src/dotnet-interactive-vscode-insiders/.vscode/settings.json index ae3ab70d36..4b484f4ddd 100644 --- a/src/dotnet-interactive-vscode-insiders/.vscode/settings.json +++ b/src/dotnet-interactive-vscode-insiders/.vscode/settings.json @@ -11,4 +11,5 @@ "files.trimTrailingWhitespace": true, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts "typescript.tsc.autoDetect": "off", + "typescript.format.enable": true } \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/.vscodeignore b/src/dotnet-interactive-vscode-insiders/.vscodeignore index 2d974d52d4..0050fa2491 100644 --- a/src/dotnet-interactive-vscode-insiders/.vscodeignore +++ b/src/dotnet-interactive-vscode-insiders/.vscodeignore @@ -2,6 +2,7 @@ .vscode-test/** out/src/common/tests/** src/** +tools/** .gitignore mocha.opts testConfig.json diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.kql.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/grammars/kql.tmGrammar.json similarity index 98% rename from src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.kql.tmGrammar.json rename to src/dotnet-interactive-vscode-insiders/grammars/kql.tmGrammar.json index f477023bee..baac818a14 100644 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.kql.tmGrammar.json +++ b/src/dotnet-interactive-vscode-insiders/grammars/kql.tmGrammar.json @@ -1,12 +1,6 @@ { - "scopeName": "source.dotnet-interactive.kql", + "scopeName": "source.kql", "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, { "//comment": " Query statements-https://docs.microsoft.com/en-us/azure/kusto/query/statements", "match": "\\b(let|set|alias|declare|pattern|restrict|access|to|set)\\b", diff --git a/src/dotnet-interactive-vscode-insiders/package-lock.json b/src/dotnet-interactive-vscode-insiders/package-lock.json index cc91533a00..e7842a5321 100644 --- a/src/dotnet-interactive-vscode-insiders/package-lock.json +++ b/src/dotnet-interactive-vscode-insiders/package-lock.json @@ -13,6 +13,8 @@ "node-fetch": "2.6.7", "rxjs": "7.5.6", "uuid": "8.3.2", + "vscode-oniguruma": "1.6.1", + "vscode-textmate": "6.0.0", "vscode-uri": "3.0.6" }, "devDependencies": { @@ -46,9 +48,7 @@ "tmp": "0.2.1", "typescript": "4.4.4", "vsce": "2.10.2", - "vscode-oniguruma": "1.6.1", - "vscode-test": "1.6.1", - "vscode-textmate": "6.0.0" + "vscode-test": "1.6.1" }, "engines": { "vscode": "1.74.0-insider" @@ -2501,20 +2501,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", @@ -5282,8 +5268,7 @@ "node_modules/vscode-oniguruma": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz", - "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==", - "dev": true + "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==" }, "node_modules/vscode-test": { "version": "1.6.1", @@ -5303,8 +5288,7 @@ "node_modules/vscode-textmate": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz", - "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", - "dev": true + "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==" }, "node_modules/vscode-uri": { "version": "3.0.6", @@ -7235,13 +7219,6 @@ "version": "1.0.0", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", @@ -9125,8 +9102,7 @@ "vscode-oniguruma": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz", - "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==", - "dev": true + "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==" }, "vscode-test": { "version": "1.6.1", @@ -9143,8 +9119,7 @@ "vscode-textmate": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz", - "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", - "dev": true + "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==" }, "vscode-uri": { "version": "3.0.6", diff --git a/src/dotnet-interactive-vscode-insiders/package.json b/src/dotnet-interactive-vscode-insiders/package.json index 5d1eabb34c..68aaa7904d 100644 --- a/src/dotnet-interactive-vscode-insiders/package.json +++ b/src/dotnet-interactive-vscode-insiders/package.json @@ -94,15 +94,15 @@ "jupyter.kernels": [ { "title": ".NET Interactive (C#)", - "defaultlanguage": "dotnet-interactive.csharp" + "defaultlanguage": "polyglot-notebook" }, { "title": ".NET Interactive (F#)", - "defaultlanguage": "dotnet-interactive.fsharp" + "defaultlanguage": "polyglot-notebook" }, { "title": ".NET Interactive (PowerShell)", - "defaultlanguage": "dotnet-interactive.pwsh" + "defaultlanguage": "polyglot-notebook" } ], "configuration": { @@ -234,6 +234,10 @@ "command": "polyglot-notebook.shareValueWith", "title": "Share value with..." }, + { + "command": "polyglot-notebook.selectCellKernel", + "title": "Polyglot Notebook: Select cell kernel" + }, { "command": "dotnet-interactive.openNotebook", "title": ".NET Interactive: Open notebook" @@ -281,77 +285,10 @@ ], "languages": [ { - "id": "dotnet-interactive", - "aliases": [ - "Polyglot Notebook" - ] - }, - { - "id": "dotnet-interactive.csharp", - "aliases": [ - "C# (Polyglot Notebook)" - ], - "configuration": "./syntaxes/csharp.language-configuration.json" - }, - { - "id": "dotnet-interactive.fsharp", + "id": "polyglot-notebook", "aliases": [ - "F# (Polyglot Notebook)" - ], - "configuration": "./syntaxes/fsharp.language-configuration.json" - }, - { - "id": "dotnet-interactive.html", - "aliases": [ - "HTML (Polyglot Notebook)" + "Code" ] - }, - { - "id": "dotnet-interactive.javascript", - "aliases": [ - "JavaScript (Polyglot Notebook)" - ], - "configuration": "./syntaxes/javascript.language-configuration.json" - }, - { - "id": "dotnet-interactive.magic-commands", - "aliases": [ - "Polyglot Notebook Magic Commands" - ], - "configuration": "./syntaxes/magic-commands.language-configuration.json" - }, - { - "id": "dotnet-interactive.markdown", - "aliases": [ - "Markdown (Polyglot Notebook)" - ] - }, - { - "id": "dotnet-interactive.mermaid", - "aliases": [ - "Mermaid (Polyglot Notebook)" - ] - }, - { - "id": "dotnet-interactive.pwsh", - "aliases": [ - "PowerShell (Polyglot Notebook)" - ], - "configuration": "./syntaxes/powershell.language-configuration.json" - }, - { - "id": "dotnet-interactive.sql", - "aliases": [ - "SQL (Polyglot Notebook)" - ], - "configuration": "./syntaxes/sql.language-configuration.json" - }, - { - "id": "dotnet-interactive.kql", - "aliases": [ - "Kusto Query Language (KQL) (Polyglot Notebook)" - ], - "configuration": "./syntaxes/kql.language-configuration.json" } ], "menus": { @@ -368,181 +305,445 @@ } ] }, - "grammars": [ - { - "language": "dotnet-interactive", - "scopeName": "source.dotnet-interactive", - "path": "./syntaxes/source.dotnet-interactive.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.csharp", - "scopeName": "source.dotnet-interactive.csharp", - "path": "./syntaxes/source.dotnet-interactive.csharp.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, + "semanticTokenScopes": [ { - "language": "dotnet-interactive.fsharp", - "scopeName": "source.dotnet-interactive.fsharp", - "path": "./syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.html", - "scopeName": "source.dotnet-interactive.html", - "path": "./syntaxes/source.dotnet-interactive.html.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.javascript", - "scopeName": "source.dotnet-interactive.javascript", - "path": "./syntaxes/source.dotnet-interactive.javascript.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.magic-commands", - "scopeName": "source.dotnet-interactive.magic-commands", - "path": "./syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.markdown", - "scopeName": "source.dotnet-interactive.markdown", - "path": "./syntaxes/source.dotnet-interactive.markdown.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.mermaid", - "scopeName": "source.dotnet-interactive.mermaid", - "path": "./syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.pwsh", - "scopeName": "source.dotnet-interactive.powershell", - "path": "./syntaxes/source.dotnet-interactive.powershell.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.sql", - "scopeName": "source.dotnet-interactive.sql", - "path": "./syntaxes/source.dotnet-interactive.sql.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.kql", - "scopeName": "source.dotnet-interactive.kql", - "path": "./syntaxes/source.dotnet-interactive.kql.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" + "comment": "the `scopes` object is generated by a script", + "scopes": { + "polyglot-notebook-entity-name-function": [ + "entity.name.function" + ], + "polyglot-notebook-entity-name-variable-local": [ + "entity.name.variable.local" + ], + "polyglot-notebook-entity-name-type": [ + "entity.name.type" + ], + "polyglot-notebook-entity-name-type-namespace": [ + "entity.name.type.namespace" + ], + "polyglot-notebook-punctuation-accessor": [ + "punctuation.accessor" + ], + "polyglot-notebook-punctuation-definition-variable": [ + "punctuation.definition.variable" + ], + "polyglot-notebook-punctuation-parenthesis": [ + "punctuation.parenthesis" + ], + "polyglot-notebook-punctuation-terminator-statement": [ + "punctuation.terminator.statement" + ], + "polyglot-notebook-variable-other-object": [ + "variable.other.object" + ], + "polyglot-notebook-variable-other-readwrite": [ + "variable.other.readwrite" + ], + "polyglot-notebook-variable-other-readwrite-global": [ + "variable.other.readwrite.global" + ], + "polyglot-notebook-meta-bracket": [ + "meta.bracket" + ], + "polyglot-notebook-meta-attribute": [ + "meta.attribute" + ], + "polyglot-notebook-meta-function": [ + "meta.function" + ], + "polyglot-notebook-meta-function-call-arguments": [ + "meta.function-call.arguments" + ], + "polyglot-notebook-meta-function-call-generic": [ + "meta.function-call.generic" + ], + "polyglot-notebook-meta-indexed-name": [ + "meta.indexed-name" + ], + "polyglot-notebook-meta-item-access-arguments": [ + "meta.item-access.arguments" + ], + "polyglot-notebook-punctuation-definition-arguments-begin": [ + "punctuation.definition.arguments.begin" + ], + "polyglot-notebook-punctuation-definition-arguments-end": [ + "punctuation.definition.arguments.end" + ], + "polyglot-notebook-punctuation-definition-list-begin": [ + "punctuation.definition.list.begin" + ], + "polyglot-notebook-punctuation-definition-list-end": [ + "punctuation.definition.list.end" + ], + "polyglot-notebook-punctuation-definition-parameters-begin": [ + "punctuation.definition.parameters.begin" + ], + "polyglot-notebook-punctuation-definition-parameters-end": [ + "punctuation.definition.parameters.end" + ], + "polyglot-notebook-punctuation-section-function-begin": [ + "punctuation.section.function.begin" + ], + "polyglot-notebook-punctuation-section-function-end": [ + "punctuation.section.function.end" + ], + "polyglot-notebook-punctuation-separator-element": [ + "punctuation.separator.element" + ], + "polyglot-notebook-punctuation-separator-colon": [ + "punctuation.separator.colon" + ], + "polyglot-notebook-punctuation-separator-period": [ + "punctuation.separator.period" + ], + "polyglot-notebook-punctuation-separator-slice": [ + "punctuation.separator.slice" + ], + "polyglot-notebook-variable-parameter-function-call": [ + "variable.parameter.function-call" + ], + "polyglot-notebook-punctuation-section-brackets-single-begin": [ + "punctuation.section.brackets.single.begin" + ], + "polyglot-notebook-punctuation-section-brackets-single-end": [ + "punctuation.section.brackets.single.end" + ], + "polyglot-notebook-punctuation-section-parens-begin": [ + "punctuation.section.parens.begin" + ], + "polyglot-notebook-punctuation-section-parens-end": [ + "punctuation.section.parens.end" + ], + "polyglot-notebook-punctuation-separator-parameters": [ + "punctuation.separator.parameters" + ], + "polyglot-notebook-support-function": [ + "support.function" + ], + "polyglot-notebook-variable-other": [ + "variable.other" + ], + "polyglot-notebook-variable-function": [ + "variable.function" + ], + "polyglot-notebook-variable-parameter": [ + "variable.parameter" + ], + "polyglot-notebook-meta-group-simple-subexpression": [ + "meta.group.simple.subexpression" + ], + "polyglot-notebook-punctuation-section-group-begin": [ + "punctuation.section.group.begin" + ], + "polyglot-notebook-punctuation-section-group-end": [ + "punctuation.section.group.end" + ], + "polyglot-notebook-meta-embedded": [ + "meta.embedded" + ], + "polyglot-notebook-source-groovy-embedded": [ + "source.groovy.embedded" + ], + "polyglot-notebook-emphasis": [ + "emphasis" + ], + "polyglot-notebook-strong": [ + "strong" + ], + "polyglot-notebook-meta-diff-header": [ + "meta.diff.header" + ], + "polyglot-notebook-comment": [ + "comment" + ], + "polyglot-notebook-constant-language": [ + "constant.language" + ], + "polyglot-notebook-constant-numeric": [ + "constant.numeric" + ], + "polyglot-notebook-variable-other-enummember": [ + "variable.other.enummember" + ], + "polyglot-notebook-keyword-operator-plus-exponent": [ + "keyword.operator.plus.exponent" + ], + "polyglot-notebook-keyword-operator-minus-exponent": [ + "keyword.operator.minus.exponent" + ], + "polyglot-notebook-constant-regexp": [ + "constant.regexp" + ], + "polyglot-notebook-entity-name-tag": [ + "entity.name.tag" + ], + "polyglot-notebook-entity-name-selector": [ + "entity.name.selector" + ], + "polyglot-notebook-entity-other-attribute-name": [ + "entity.other.attribute-name" + ], + "polyglot-notebook-entity-other-attribute-name-class-css": [ + "entity.other.attribute-name.class.css" + ], + "polyglot-notebook-entity-other-attribute-name-class-mixin-css": [ + "entity.other.attribute-name.class.mixin.css" + ], + "polyglot-notebook-entity-other-attribute-name-id-css": [ + "entity.other.attribute-name.id.css" + ], + "polyglot-notebook-entity-other-attribute-name-parent-selector-css": [ + "entity.other.attribute-name.parent-selector.css" + ], + "polyglot-notebook-entity-other-attribute-name-pseudo-class-css": [ + "entity.other.attribute-name.pseudo-class.css" + ], + "polyglot-notebook-entity-other-attribute-name-pseudo-element-css": [ + "entity.other.attribute-name.pseudo-element.css" + ], + "polyglot-notebook-entity-other-attribute-name-scss": [ + "entity.other.attribute-name.scss" + ], + "polyglot-notebook-invalid": [ + "invalid" + ], + "polyglot-notebook-markup-underline": [ + "markup.underline" + ], + "polyglot-notebook-markup-bold": [ + "markup.bold" + ], + "polyglot-notebook-markup-heading": [ + "markup.heading" + ], + "polyglot-notebook-markup-italic": [ + "markup.italic" + ], + "polyglot-notebook-markup-strikethrough": [ + "markup.strikethrough" + ], + "polyglot-notebook-markup-inserted": [ + "markup.inserted" + ], + "polyglot-notebook-markup-deleted": [ + "markup.deleted" + ], + "polyglot-notebook-markup-changed": [ + "markup.changed" + ], + "polyglot-notebook-punctuation-definition-quote-begin-markdown": [ + "punctuation.definition.quote.begin.markdown" + ], + "polyglot-notebook-punctuation-definition-list-begin-markdown": [ + "punctuation.definition.list.begin.markdown" + ], + "polyglot-notebook-markup-inline-raw": [ + "markup.inline.raw" + ], + "polyglot-notebook-punctuation-definition-tag": [ + "punctuation.definition.tag" + ], + "polyglot-notebook-meta-preprocessor": [ + "meta.preprocessor" + ], + "polyglot-notebook-entity-name-function-preprocessor": [ + "entity.name.function.preprocessor" + ], + "polyglot-notebook-meta-preprocessor-string": [ + "meta.preprocessor.string" + ], + "polyglot-notebook-meta-preprocessor-numeric": [ + "meta.preprocessor.numeric" + ], + "polyglot-notebook-meta-structure-dictionary-key-python": [ + "meta.structure.dictionary.key.python" + ], + "polyglot-notebook-storage": [ + "storage" + ], + "polyglot-notebook-storage-type": [ + "storage.type" + ], + "polyglot-notebook-storage-modifier": [ + "storage.modifier" + ], + "polyglot-notebook-keyword-operator-noexcept": [ + "keyword.operator.noexcept" + ], + "polyglot-notebook-string": [ + "string" + ], + "polyglot-notebook-meta-embedded-assembly": [ + "meta.embedded.assembly" + ], + "polyglot-notebook-string-comment-buffered-block-pug": [ + "string.comment.buffered.block.pug" + ], + "polyglot-notebook-string-quoted-pug": [ + "string.quoted.pug" + ], + "polyglot-notebook-string-interpolated-pug": [ + "string.interpolated.pug" + ], + "polyglot-notebook-string-unquoted-plain-in-yaml": [ + "string.unquoted.plain.in.yaml" + ], + "polyglot-notebook-string-unquoted-plain-out-yaml": [ + "string.unquoted.plain.out.yaml" + ], + "polyglot-notebook-string-unquoted-block-yaml": [ + "string.unquoted.block.yaml" + ], + "polyglot-notebook-string-quoted-single-yaml": [ + "string.quoted.single.yaml" + ], + "polyglot-notebook-string-quoted-double-xml": [ + "string.quoted.double.xml" + ], + "polyglot-notebook-string-quoted-single-xml": [ + "string.quoted.single.xml" + ], + "polyglot-notebook-string-unquoted-cdata-xml": [ + "string.unquoted.cdata.xml" + ], + "polyglot-notebook-string-quoted-double-html": [ + "string.quoted.double.html" + ], + "polyglot-notebook-string-quoted-single-html": [ + "string.quoted.single.html" + ], + "polyglot-notebook-string-unquoted-html": [ + "string.unquoted.html" + ], + "polyglot-notebook-string-quoted-single-handlebars": [ + "string.quoted.single.handlebars" + ], + "polyglot-notebook-string-quoted-double-handlebars": [ + "string.quoted.double.handlebars" + ], + "polyglot-notebook-string-regexp": [ + "string.regexp" + ], + "polyglot-notebook-punctuation-definition-template-expression-begin": [ + "punctuation.definition.template-expression.begin" + ], + "polyglot-notebook-punctuation-definition-template-expression-end": [ + "punctuation.definition.template-expression.end" + ], + "polyglot-notebook-punctuation-section-embedded": [ + "punctuation.section.embedded" + ], + "polyglot-notebook-meta-template-expression": [ + "meta.template.expression" + ], + "polyglot-notebook-support-constant-property-value": [ + "support.constant.property-value" + ], + "polyglot-notebook-support-constant-font-name": [ + "support.constant.font-name" + ], + "polyglot-notebook-support-constant-media-type": [ + "support.constant.media-type" + ], + "polyglot-notebook-support-constant-media": [ + "support.constant.media" + ], + "polyglot-notebook-constant-other-color-rgb-value": [ + "constant.other.color.rgb-value" + ], + "polyglot-notebook-constant-other-rgb-value": [ + "constant.other.rgb-value" + ], + "polyglot-notebook-support-constant-color": [ + "support.constant.color" + ], + "polyglot-notebook-support-type-vendored-property-name": [ + "support.type.vendored.property-name" + ], + "polyglot-notebook-support-type-property-name": [ + "support.type.property-name" + ], + "polyglot-notebook-variable-css": [ + "variable.css" + ], + "polyglot-notebook-variable-scss": [ + "variable.scss" + ], + "polyglot-notebook-variable-other-less": [ + "variable.other.less" + ], + "polyglot-notebook-source-coffee-embedded": [ + "source.coffee.embedded" + ], + "polyglot-notebook-support-type-property-name-json": [ + "support.type.property-name.json" + ], + "polyglot-notebook-keyword": [ + "keyword" + ], + "polyglot-notebook-keyword-control": [ + "keyword.control" + ], + "polyglot-notebook-keyword-operator": [ + "keyword.operator" + ], + "polyglot-notebook-keyword-operator-new": [ + "keyword.operator.new" + ], + "polyglot-notebook-keyword-operator-expression": [ + "keyword.operator.expression" + ], + "polyglot-notebook-keyword-operator-cast": [ + "keyword.operator.cast" + ], + "polyglot-notebook-keyword-operator-sizeof": [ + "keyword.operator.sizeof" + ], + "polyglot-notebook-keyword-operator-alignof": [ + "keyword.operator.alignof" + ], + "polyglot-notebook-keyword-operator-typeid": [ + "keyword.operator.typeid" + ], + "polyglot-notebook-keyword-operator-alignas": [ + "keyword.operator.alignas" + ], + "polyglot-notebook-keyword-operator-instanceof": [ + "keyword.operator.instanceof" + ], + "polyglot-notebook-keyword-operator-logical-python": [ + "keyword.operator.logical.python" + ], + "polyglot-notebook-keyword-operator-wordlike": [ + "keyword.operator.wordlike" + ], + "polyglot-notebook-keyword-other-unit": [ + "keyword.other.unit" + ], + "polyglot-notebook-punctuation-section-embedded-begin-php": [ + "punctuation.section.embedded.begin.php" + ], + "polyglot-notebook-punctuation-section-embedded-end-php": [ + "punctuation.section.embedded.end.php" + ], + "polyglot-notebook-support-function-git-rebase": [ + "support.function.git-rebase" + ], + "polyglot-notebook-constant-sha-git-rebase": [ + "constant.sha.git-rebase" + ], + "polyglot-notebook-storage-modifier-import-java": [ + "storage.modifier.import.java" + ], + "polyglot-notebook-variable-language-wildcard-java": [ + "variable.language.wildcard.java" + ], + "polyglot-notebook-storage-modifier-package-java": [ + "storage.modifier.package.java" + ], + "polyglot-notebook-variable-language": [ + "variable.language" + ] } } ] @@ -594,15 +795,15 @@ "tmp": "0.2.1", "typescript": "4.4.4", "vsce": "2.10.2", - "vscode-oniguruma": "1.6.1", - "vscode-test": "1.6.1", - "vscode-textmate": "6.0.0" + "vscode-test": "1.6.1" }, "dependencies": { "compare-versions": "3.6.0", "node-fetch": "2.6.7", - "uuid": "8.3.2", "rxjs": "7.5.6", + "uuid": "8.3.2", + "vscode-oniguruma": "1.6.1", + "vscode-textmate": "6.0.0", "vscode-uri": "3.0.6" } } \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/src/notebookControllers.ts b/src/dotnet-interactive-vscode-insiders/src/notebookControllers.ts index daa8b5760d..365baf789e 100644 --- a/src/dotnet-interactive-vscode-insiders/src/notebookControllers.ts +++ b/src/dotnet-interactive-vscode-insiders/src/notebookControllers.ts @@ -7,21 +7,19 @@ import * as contracts from './vscode-common/dotnet-interactive/contracts'; import * as vscodeLike from './vscode-common/interfaces/vscode-like'; import * as diagnostics from './vscode-common/diagnostics'; import * as vscodeUtilities from './vscode-common/vscodeUtilities'; -import { getSimpleLanguage, isDotnetInteractiveLanguage, jupyterViewType, notebookCellLanguages } from './vscode-common/interactiveNotebook'; -import { getCellLanguage, getDotNetMetadata, getLanguageInfoMetadata, isDotNetNotebookMetadata, withDotNetKernelMetadata } from './vscode-common/ipynbUtilities'; import { reshapeOutputValueForVsCode } from './vscode-common/interfaces/utilities'; import { selectDotNetInteractiveKernelForJupyter } from './vscode-common/commands'; import { ErrorOutputCreator, InteractiveClient } from './vscode-common/interactiveClient'; import { LogEntry, Logger } from './vscode-common/dotnet-interactive/logger'; -import * as verstionSpecificFunctions from './versionSpecificFunctions'; -import { KernelCommandOrEventEnvelope } from './vscode-common/dotnet-interactive/connection'; +import { isKernelEventEnvelope, KernelCommandOrEventEnvelope } from './vscode-common/dotnet-interactive/connection'; import * as rxjs from 'rxjs'; +import * as metadataUtilities from './vscode-common/metadataUtilities'; +import * as constants from './vscode-common/constants'; +import * as versionSpecificFunctions from './versionSpecificFunctions'; +import * as semanticTokens from './vscode-common/documentSemanticTokenProvider'; const executionTasks: Map = new Map(); -const viewType = 'polyglot-notebook'; -const legacyViewType = 'dotnet-interactive-legacy'; - export interface DotNetNotebookKernelConfiguration { clientMapper: ClientMapper, preloadUris: vscode.Uri[], @@ -32,13 +30,13 @@ export class DotNetNotebookKernel { private disposables: { dispose(): void }[] = []; - constructor(readonly config: DotNetNotebookKernelConfiguration) { + constructor(readonly config: DotNetNotebookKernelConfiguration, readonly tokensProvider: semanticTokens.DocumentSemanticTokensProvider) { const preloads = config.preloadUris.map(uri => new vscode.NotebookRendererScript(uri)); // .dib execution const dibController = vscode.notebooks.createNotebookController( - 'polyglot-notebook', - viewType, + constants.NotebookControllerId, + constants.NotebookViewType, '.NET Interactive', this.executeHandler.bind(this), preloads @@ -47,8 +45,8 @@ export class DotNetNotebookKernel { // .dotnet-interactive execution const legacyController = vscode.notebooks.createNotebookController( - 'polyglot-notebook-legacy', - legacyViewType, + constants.LegacyNotebookControllerId, + constants.LegacyNotebookViewType, '.NET Interactive', this.executeHandler.bind(this), preloads @@ -57,8 +55,8 @@ export class DotNetNotebookKernel { // .ipynb execution via Jupyter extension (optional) const jupyterController = vscode.notebooks.createNotebookController( - 'polyglot-notebook-for-jupyter', - jupyterViewType, + constants.JupyterNotebookControllerId, + constants.JupyterViewType, '.NET Interactive', this.executeHandler.bind(this), preloads @@ -72,15 +70,31 @@ export class DotNetNotebookKernel { this.commonControllerInit(jupyterController); this.disposables.push(vscode.workspace.onDidOpenNotebookDocument(async notebook => { - if (isDotNetNotebook(notebook)) { - // eagerly spin up the backing process - const client = await config.clientMapper.getOrAddClient(notebook.uri); - client.resetExecutionCount(); - - if (notebook.notebookType === jupyterViewType) { - jupyterController.updateNotebookAffinity(notebook, vscode.NotebookControllerAffinity.Preferred); - await selectDotNetInteractiveKernelForJupyter(); - await updateNotebookMetadata(notebook, this.config.clientMapper); + await this.onNotebookOpen(notebook, config.clientMapper, jupyterController); + })); + + // ...but we may have to look at already opened ones if we were activated late + for (const notebook of vscode.workspace.notebookDocuments) { + this.onNotebookOpen(notebook, config.clientMapper, jupyterController); + } + + this.disposables.push(vscode.workspace.onDidOpenTextDocument(async textDocument => { + if (vscode.window.activeNotebookEditor) { + const notebook = vscode.window.activeNotebookEditor.notebook; + if (metadataUtilities.isDotNetNotebook(notebook)) { + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(notebook); + const cells = notebook.getCells(); + const foundCell = cells.find(cell => cell.document === textDocument); + if (foundCell && foundCell.index > 0) { + // if we found the cell and it's not the first, ensure it has kernel metadata + const cellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(foundCell); + if (!cellMetadata.kernelName) { + // no kernel metadata; copy from previous cell + const previousCell = cells[foundCell.index - 1]; + const previousCellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(previousCell); + await vscodeUtilities.setCellKernelName(foundCell, previousCellMetadata.kernelName ?? notebookMetadata.kernelInfo.defaultKernelName); + } + } } } })); @@ -90,10 +104,29 @@ export class DotNetNotebookKernel { this.disposables.forEach(d => d.dispose()); } + private async onNotebookOpen(notebook: vscode.NotebookDocument, clientMapper: ClientMapper, jupyterController: vscode.NotebookController): Promise { + if (metadataUtilities.isDotNetNotebook(notebook)) { + // prepare initial grammar + const kernelInfos = metadataUtilities.getKernelInfosFromNotebookDocument(notebook); + this.tokensProvider.dynamicTokenProvider.rebuildNotebookGrammar(notebook.uri, kernelInfos); + + // eagerly spin up the backing process + const client = await clientMapper.getOrAddClient(notebook.uri); + client.resetExecutionCount(); + + if (notebook.notebookType === constants.JupyterViewType) { + jupyterController.updateNotebookAffinity(notebook, vscode.NotebookControllerAffinity.Preferred); + await selectDotNetInteractiveKernelForJupyter(); + } + + await updateNotebookMetadata(notebook, this.config.clientMapper); + } + } + private uriMessageHandlerMap: Map> = new Map(); private commonControllerInit(controller: vscode.NotebookController) { - controller.supportedLanguages = notebookCellLanguages; + controller.supportedLanguages = [constants.CellLanguageIdentifier]; controller.supportsExecutionOrder = true; this.disposables.push(controller.onDidReceiveMessage(e => { const notebookUri = e.editor.notebook.uri; @@ -109,7 +142,7 @@ export class DotNetNotebookKernel { this.config.clientMapper.getOrAddClient(notebookUri).then(() => { const kernelInfoProduced = ((e.message.kernelInfoProduced)).map(e => e.event); const hostUri = e.message.hostUri; - verstionSpecificFunctions.hashBangConnect(this.config.clientMapper, hostUri, kernelInfoProduced, this.uriMessageHandlerMap, (arg) => controller.postMessage(arg), notebookUri); + versionSpecificFunctions.hashBangConnect(this.config.clientMapper, hostUri, kernelInfoProduced, this.uriMessageHandlerMap, (arg) => controller.postMessage(arg), notebookUri); }); break; } @@ -161,7 +194,7 @@ export class DotNetNotebookKernel { diagnosticCollection.set(cell.document.uri, diags.filter(d => d.severity !== contracts.DiagnosticSeverity.Hidden).map(vscodeUtilities.toVsCodeDiagnostic)); } - return client.execute(source, getSimpleLanguage(cell.document.languageId), outputObserver, diagnosticObserver, { id: cell.document.uri.toString() }).then(async (success) => { + return client.execute(source, vscodeUtilities.getCellKernelName(cell), outputObserver, diagnosticObserver, { id: cell.document.uri.toString() }).then(async (success) => { await outputUpdatePromise; endExecution(client, cell, success); }).catch(async () => { @@ -183,27 +216,67 @@ async function updateNotebookMetadata(notebook: vscode.NotebookDocument, clientM try { // update various metadata await updateDocumentKernelspecMetadata(notebook); - await updateCellLanguages(notebook); + await updateCellLanguagesAndKernels(notebook); // force creation of the client so we don't have to wait for the user to execute a cell to get the tool - await clientMapper.getOrAddClient(notebook.uri); + const client = await clientMapper.getOrAddClient(notebook.uri); + await updateKernelInfoMetadata(client, notebook); } catch (err) { vscode.window.showErrorMessage(`Failed to set document metadata for '${notebook.uri}': ${err}`); } } -export async function updateCellLanguages(document: vscode.NotebookDocument): Promise { - const documentLanguageInfo = getLanguageInfoMetadata(document.metadata); +async function updateKernelInfoMetadata(client: InteractiveClient, document: vscode.NotebookDocument): Promise { + const isIpynb = metadataUtilities.isIpynbNotebook(document); + client.channel.receiver.subscribe({ + next: async (commandOrEventEnvelope) => { + if (isKernelEventEnvelope(commandOrEventEnvelope) && commandOrEventEnvelope.eventType === contracts.KernelInfoProducedType) { + // got info about a kernel; either update an existing entry, or add a new one + let metadataChanged = false; + const kernelInfoProduced = commandOrEventEnvelope.event; + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); + for (const item of notebookMetadata.kernelInfo.items) { + if (item.name === kernelInfoProduced.kernelInfo.localName) { + metadataChanged = true; + item.languageName = kernelInfoProduced.kernelInfo.languageName; + item.aliases = kernelInfoProduced.kernelInfo.aliases; + } + } + + if (!metadataChanged) { + // nothing changed, must be a new kernel + notebookMetadata.kernelInfo.items.push({ + name: kernelInfoProduced.kernelInfo.localName, + languageName: kernelInfoProduced.kernelInfo.languageName, + aliases: kernelInfoProduced.kernelInfo.aliases + }); + } + + const existingRawNotebookDocumentMetadata = document.metadata; + const updatedRawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata, isIpynb); + const newRawNotebookDocumentMetadata = metadataUtilities.mergeRawMetadata(existingRawNotebookDocumentMetadata, updatedRawNotebookDocumentMetadata); + await versionSpecificFunctions.replaceNotebookMetadata(document.uri, newRawNotebookDocumentMetadata); + } + } + }); + + const notebookDocumentMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); + const kernelNotebokMetadata = metadataUtilities.getNotebookDocumentMetadataFromCompositeKernel(client.kernel); + const mergedMetadata = metadataUtilities.mergeNotebookDocumentMetadata(notebookDocumentMetadata, kernelNotebokMetadata); + const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(mergedMetadata, isIpynb); + await versionSpecificFunctions.replaceNotebookMetadata(document.uri, rawNotebookDocumentMetadata); +} + +async function updateCellLanguagesAndKernels(document: vscode.NotebookDocument): Promise { + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); - // update cell language + // update cell language and kernel await Promise.all(document.getCells().map(async (cell) => { - const cellMetadata = getDotNetMetadata(cell.metadata); - const cellText = cell.document.getText(); - const newLanguage = cell.kind === vscode.NotebookCellKind.Code - ? getCellLanguage(cellText, cellMetadata, documentLanguageInfo, cell.document.languageId) - : 'markdown'; - if (cell.document.languageId !== newLanguage) { - await vscode.languages.setTextDocumentLanguage(cell.document, newLanguage); + const cellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(cell); + await vscodeUtilities.ensureCellLanguage(cell); + if (!cellMetadata.kernelName) { + // no kernel specified; apply global + vscodeUtilities.setCellKernelName(cell, notebookMetadata.kernelInfo.defaultKernelName); } })); } @@ -258,32 +331,7 @@ function generateVsCodeNotebookCellOutputItem(data: Uint8Array, mime: string, st } async function updateDocumentKernelspecMetadata(document: vscode.NotebookDocument): Promise { - const documentKernelMetadata = withDotNetKernelMetadata(document.metadata); - const notebookEdit = vscode.NotebookEdit.updateNotebookMetadata(documentKernelMetadata); - const edit = new vscode.WorkspaceEdit(); - edit.set(document.uri, [notebookEdit]); - await vscode.workspace.applyEdit(edit); -} - -function isDotNetNotebook(notebook: vscode.NotebookDocument): boolean { - if (notebook.uri.toString().endsWith('.dib')) { - return true; - } - - if (isDotNetNotebookMetadata(notebook.metadata)) { - // metadata looked correct - return true; - } - - if (notebook.uri.scheme === 'untitled' && notebook.cellCount === 1) { - // untitled with a single cell, check cell - const cell = notebook.cellAt(0); - if (isDotnetInteractiveLanguage(cell.document.languageId) && cell.document.getText() === '') { - // language was one of ours and cell was emtpy - return true; - } - } - - // doesn't look like us - return false; + const documentMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); + const newMetadata = metadataUtilities.createNewIpynbMetadataWithNotebookDocumentMetadata(document.metadata, documentMetadata); + await versionSpecificFunctions.replaceNotebookMetadata(document.uri, newMetadata); } diff --git a/src/dotnet-interactive-vscode-insiders/src/notebookSerializers.ts b/src/dotnet-interactive-vscode-insiders/src/notebookSerializers.ts index 1849f01958..cf9beebe75 100644 --- a/src/dotnet-interactive-vscode-insiders/src/notebookSerializers.ts +++ b/src/dotnet-interactive-vscode-insiders/src/notebookSerializers.ts @@ -5,40 +5,62 @@ import * as vscode from 'vscode'; import * as contracts from './vscode-common/dotnet-interactive/contracts'; import * as utilities from './vscode-common/interfaces/utilities'; import * as vscodeLike from './vscode-common/interfaces/vscode-like'; -import { getNotebookSpecificLanguage, getSimpleLanguage, languageToCellKind } from './vscode-common/interactiveNotebook'; -import { getEol, vsCodeCellOutputToContractCellOutput } from './vscode-common/vscodeUtilities'; +import { languageToCellKind } from './vscode-common/interactiveNotebook'; +import * as vscodeUtilities from './vscode-common/vscodeUtilities'; import { NotebookParserServer } from './vscode-common/notebookParserServer'; import { Eol } from './vscode-common/interfaces'; +import * as metadataUtilities from './vscode-common/metadataUtilities'; +import * as constants from './vscode-common/constants'; function toInteractiveDocumentElement(cell: vscode.NotebookCellData): contracts.InteractiveDocumentElement { + // just need to match the shape + const fakeCell: vscodeLike.NotebookCell = { + kind: 0, + metadata: cell.metadata ?? {} + }; + const notebookCellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(fakeCell); const outputs = cell.outputs || []; return { executionOrder: cell.executionSummary?.executionOrder ?? 0, - kernelName: getSimpleLanguage(cell.languageId), + kernelName: cell.languageId === 'markdown' ? 'markdown' : notebookCellMetadata.kernelName ?? 'csharp', contents: cell.value, - outputs: outputs.map(vsCodeCellOutputToContractCellOutput) + outputs: outputs.map(vscodeUtilities.vsCodeCellOutputToContractCellOutput) }; } async function deserializeNotebookByType(parserServer: NotebookParserServer, serializationType: contracts.DocumentSerializationType, rawData: Uint8Array): Promise { const interactiveDocument = await parserServer.parseInteractiveDocument(serializationType, rawData); + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromInteractiveDocument(interactiveDocument); + const createForIpynb = serializationType === contracts.DocumentSerializationType.Ipynb; + const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata, createForIpynb); const notebookData: vscode.NotebookData = { - cells: interactiveDocument.elements.map(toVsCodeNotebookCellData) + cells: interactiveDocument.elements.map(element => toVsCodeNotebookCellData(element)), + metadata: rawNotebookDocumentMetadata }; return notebookData; } async function serializeNotebookByType(parserServer: NotebookParserServer, serializationType: contracts.DocumentSerializationType, eol: Eol, data: vscode.NotebookData): Promise { + // just need to match the shape + const fakeNotebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'unused', + scheme: 'unused' + }, + metadata: data.metadata ?? {} + }; + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(fakeNotebookDocument); + const rawInteractiveDocumentNotebookMetadata = metadataUtilities.getRawInteractiveDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata); const interactiveDocument: contracts.InteractiveDocument = { elements: data.cells.map(toInteractiveDocumentElement), - metadata: {} + metadata: rawInteractiveDocumentNotebookMetadata }; const rawData = await parserServer.serializeNotebook(serializationType, eol, interactiveDocument); return rawData; } export function createAndRegisterNotebookSerializers(context: vscode.ExtensionContext, parserServer: NotebookParserServer): Map { - const eol = getEol(); + const eol = vscodeUtilities.getEol(); const createAndRegisterSerializer = (serializationType: contracts.DocumentSerializationType, notebookType: string): vscode.NotebookSerializer => { const serializer: vscode.NotebookSerializer = { deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): Promise { @@ -54,8 +76,8 @@ export function createAndRegisterNotebookSerializers(context: vscode.ExtensionCo }; const serializers = new Map(); - serializers.set('polyglot-notebook', createAndRegisterSerializer(contracts.DocumentSerializationType.Dib, 'polyglot-notebook')); - serializers.set('jupyter-notebook', createAndRegisterSerializer(contracts.DocumentSerializationType.Ipynb, 'polyglot-notebook-jupyter')); + serializers.set(constants.NotebookViewType, createAndRegisterSerializer(contracts.DocumentSerializationType.Dib, constants.NotebookViewType)); + serializers.set(constants.JupyterViewType, createAndRegisterSerializer(contracts.DocumentSerializationType.Ipynb, constants.JupyterNotebookViewType)); return serializers; } @@ -63,7 +85,12 @@ function toVsCodeNotebookCellData(cell: contracts.InteractiveDocumentElement): v const cellData = new vscode.NotebookCellData( languageToCellKind(cell.kernelName), cell.contents, - getNotebookSpecificLanguage(cell.kernelName)); + cell.kernelName === 'markdown' ? 'markdown' : constants.CellLanguageIdentifier); + const notebookCellMetadata: metadataUtilities.NotebookCellMetadata = { + kernelName: cell.kernelName + }; + const rawNotebookCellMetadata = metadataUtilities.getRawNotebookCellMetadataFromNotebookCellMetadata(notebookCellMetadata); + cellData.metadata = rawNotebookCellMetadata; cellData.outputs = cell.outputs.map(outputElementToVsCodeCellOutput); return cellData; } diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/csharp.language-configuration.json b/src/dotnet-interactive-vscode-insiders/syntaxes/csharp.language-configuration.json deleted file mode 100644 index 666d4a65e1..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/csharp.language-configuration.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": [ - "/*", - "*/" - ] - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - }, - { - "open": "/**", - "close": " */", - "notIn": [ - "string" - ] - } - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ] - ], - "folding": { - "markers": { - "start": "^\\s*//\\s*#?region\\b", - "end": "^\\s*//\\s*#?endregion\\b" - } - }, - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", - "indentationRules": { - "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", - "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" - } -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/fsharp.language-configuration.json b/src/dotnet-interactive-vscode-insiders/syntaxes/fsharp.language-configuration.json deleted file mode 100644 index e024b63095..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/fsharp.language-configuration.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": [ - "(*", - "*)" - ] - }, - "autoClosingPairs": [ - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "{", - "}" - ], - [ - "<@", - "@>" - ], - [ - "``", - "``" - ], - [ - "\"", - "\"" - ], - [ - "(|", - "|)" - ], - [ - "[<", - ">]" - ], - [ - "[|", - "|]" - ], - [ - "(*", - "*)" - ], - [ - "<@@", - "@@>" - ], - [ - "{|", - "|}" - ] - ], - "surroundingPairs": [ - [ - "<@@", - "@@>" - ], - [ - "(|", - "|)" - ], - [ - "[<", - ">]" - ], - [ - "[|", - "|]" - ], - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "\"", - "\"" - ], - [ - "\"\"\"", - "\"\"\"" - ], - [ - "(*", - "*)" - ], - [ - "<@", - "@>" - ], - [ - "'", - "'" - ], - [ - "{|", - "|}" - ] - ], - "brackets": [ - [ - "(", - ")" - ], - [ - "(*", - "*)" - ], - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "[|", - "|]" - ], - [ - "<@", - "@>" - ], - [ - "<@@", - "@@>" - ], - [ - "{|", - "|}" - ] - ], - "wordPattern": "(``((\\w*\\s?)\\s?)*``)|((\\w*')(\\w'?)*)|(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\$\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\\/\\s]+)" -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/javascript.language-configuration.json b/src/dotnet-interactive-vscode-insiders/syntaxes/javascript.language-configuration.json deleted file mode 100644 index 4a32c28b99..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/javascript.language-configuration.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": [ - "/*", - "*/" - ] - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - }, - { - "open": "`", - "close": "`", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "/**", - "close": " */", - "notIn": [ - "string" - ] - } - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ], - [ - "`", - "`" - ] - ], - "folding": { - "markers": { - "start": "^\\s*//\\s*#?region\\b", - "end": "^\\s*//\\s*#?endregion\\b" - } - }, - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", - "indentationRules": { - "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", - "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" - } -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/kql.language-configuration.json b/src/dotnet-interactive-vscode-insiders/syntaxes/kql.language-configuration.json deleted file mode 100644 index 672ab68d8d..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/kql.language-configuration.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "comments": { - "lineComment": "//" - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - } - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ] - ], - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", - "indentationRules": { - "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", - "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" - } -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/magic-commands.language-configuration.json b/src/dotnet-interactive-vscode-insiders/syntaxes/magic-commands.language-configuration.json deleted file mode 100644 index 611407e61c..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/magic-commands.language-configuration.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "autoClosingPairs": [ - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - } - ], - "surroundingPairs": [ - [ - "\"", - "\"" - ] - ] -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/powershell.language-configuration.json b/src/dotnet-interactive-vscode-insiders/syntaxes/powershell.language-configuration.json deleted file mode 100644 index 93b4714682..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/powershell.language-configuration.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "indentationRules": { - "increaseIndentPattern": "^.*\\{[^}\"']*$", - "decreaseIndentPattern": "^(.*\\*/)?\\s*\\}.*$" - }, - "comments": { - "lineComment": "#", - "blockComment": [ - "<#", - "#>" - ], - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - ], -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.csharp.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.csharp.tmGrammar.json deleted file mode 100644 index 8b2da5d73f..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.csharp.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.csharp", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.cs" - } - ] -} diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json deleted file mode 100644 index 4e628ec7ee..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.fsharp", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.fsharp" - } - ] -} diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.html.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.html.tmGrammar.json deleted file mode 100644 index 7bafbbc60f..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.html.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.html", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "text.html.basic" - } - ] -} diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.javascript.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.javascript.tmGrammar.json deleted file mode 100644 index 7929da301d..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.javascript.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.javascript", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.js" - } - ] -} diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json deleted file mode 100644 index 0c8272f629..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": ".NET Interactive Magic Commands", - "scopeName": "source.dotnet-interactive.magic-commands", - "patterns": [ - { - "name": "comment.line.magic-commands", - "begin": "^(#!)(?!([cf](#|s(harp)?)|powershell|pwsh|html|javascript|js|markdown|md|sql(-.+)?|kql(-.+)?))", - "end": "(?<=$)", - "beginCaptures": { - "1": { - "name": "comment.line.magic-commands.hash-bang" - } - }, - "patterns": [ - { - "include": "#magic-command-name" - }, - { - "include": "#strings" - }, - { - "include": "#option" - }, - { - "include": "#argument" - } - ] - } - ], - "repository": { - "magic-command-name": { - "patterns": [ - { - "name": "keyword.control.magic-commands", - "match": "(?<=^#!)[a-zA-Z0-9_-]+" - } - ] - }, - "option": { - "patterns": [ - { - "name": "constant.language.magic-commands", - "match": "(--?|/)[^\\s\\\"]+" - } - ] - }, - "argument": { - "patterns": [ - { - "name": "variable.parameter.magic-commands", - "match": "[^\\s\\\"]+" - } - ] - }, - "strings": { - "patterns": [ - { - "name": "string.quoted.double.magic-commands", - "begin": "\"", - "end": "\"", - "patterns": [ - { - "name": "constant.character.escape.magic-commands", - "match": "\\." - } - ] - } - ] - } - } -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.markdown.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.markdown.tmGrammar.json deleted file mode 100644 index 304d317b66..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.markdown.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.markdown", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "text.html.markdown" - } - ] -} diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json deleted file mode 100644 index 5dc7770d33..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.mermaid", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - } - ] -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.powershell.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.powershell.tmGrammar.json deleted file mode 100644 index ff7092565c..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.powershell.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.powershell", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.powershell" - } - ] -} diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.sql.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.sql.tmGrammar.json deleted file mode 100644 index 9aa9e56d56..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.sql.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.sql", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.sql" - } - ] -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.tmGrammar.json b/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.tmGrammar.json deleted file mode 100644 index 36d7b3afff..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/source.dotnet-interactive.tmGrammar.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive", - "patterns": [ - { - "begin": "^#!cs(harp)?\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.csharp", - "patterns": [ - { - "include": "source.dotnet-interactive.csharp" - } - ] - }, - { - "begin": "^#!fs(harp)?\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.fsharp", - "patterns": [ - { - "include": "source.dotnet-interactive.fsharp" - } - ] - }, - { - "begin": "^#!html\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.html", - "patterns": [ - { - "include": "source.dotnet-interactive.html" - } - ] - }, - { - "begin": "^#!(javascript|js)\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.javascript", - "patterns": [ - { - "include": "source.dotnet-interactive.javascript" - } - ] - }, - { - "begin": "^#!(markdown|md)\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.markdown", - "patterns": [ - { - "include": "source.dotnet-interactive.markdown" - } - ] - }, - { - "begin": "^#!mermaid\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.mermaid", - "patterns": [ - { - "include": "source.dotnet-interactive.mermaid" - } - ] - }, - { - "begin": "^#!(powershell|pwsh)\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.powershell", - "patterns": [ - { - "include": "source.dotnet-interactive.powershell" - } - ] - }, - { - "begin": "^#!sql(-.+)?\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.sql", - "patterns": [ - { - "include": "source.dotnet-interactive.sql" - } - ] - }, - { - "begin": "^#!kql(-.+)?\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.kql", - "patterns": [ - { - "include": "source.dotnet-interactive.kql" - } - ] - } - ] -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/syntaxes/sql.language-configuration.json b/src/dotnet-interactive-vscode-insiders/syntaxes/sql.language-configuration.json deleted file mode 100644 index 35bc9062be..0000000000 --- a/src/dotnet-interactive-vscode-insiders/syntaxes/sql.language-configuration.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "comments": { - "lineComment": "--", - "blockComment": [ - "/*", - "*/" - ] - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - }, - { - "open": "/**", - "close": " */", - "notIn": [ - "string" - ] - } - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ] - ], - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", - "indentationRules": { - "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", - "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" - } -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode-insiders/tools/buildSemanticTokenScopes.js b/src/dotnet-interactive-vscode-insiders/tools/buildSemanticTokenScopes.js new file mode 100644 index 0000000000..eabb12a9a7 --- /dev/null +++ b/src/dotnet-interactive-vscode-insiders/tools/buildSemanticTokenScopes.js @@ -0,0 +1,139 @@ +// 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. + +//////////////////////////////////////////////////////////////////////////////// +// This script will rarely need to be run. +// +// Usage: +// node .\buildSemanticTokenScopes.js +// +// Result: +// The current all relevant `package.json` files will be modified to contain +// a bunch of custom semantic token scopes. +// +// For our custom grammar parser we need to register a bunch of semantic token +// types with VS Code and we essentially need one semantic token type per each +// type of TextMate grammar scope. To get a reasonably complete list of tokens +// we simply read one of the default VS Code theme files and extract all of the +// listed scope names. +//////////////////////////////////////////////////////////////////////////////// + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const scopes = {}; + +// these are scope names that we've encountered in the wild that weren't in the +// default theme file, but we still want to colorize properly +const manualScopesToAdd = [ + 'entity.name.function', + 'entity.name.variable.local', + 'entity.name.type', + 'entity.name.type.namespace', + 'punctuation.accessor', + 'punctuation.definition.variable', + 'punctuation.parenthesis', + 'punctuation.terminator.statement', + 'variable.other.object', + 'variable.other.readwrite', + 'variable.other.readwrite.global', + // julia + 'meta.bracket', + // python + 'meta.attribute', + 'meta.function', + 'meta.function-call.arguments', + 'meta.function-call.generic', + 'meta.indexed-name', + 'meta.item-access.arguments', + 'punctuation.definition.arguments.begin', + 'punctuation.definition.arguments.end', + 'punctuation.definition.list.begin', + 'punctuation.definition.list.end', + 'punctuation.definition.parameters.begin', + 'punctuation.definition.parameters.end', + 'punctuation.section.function.begin', + 'punctuation.section.function.end', + 'punctuation.separator.element', + 'punctuation.separator.colon', + 'punctuation.separator.period', + 'punctuation.separator.slice', + 'variable.parameter.function-call', + // r + 'punctuation.section.brackets.single.begin', + 'punctuation.section.brackets.single.end', + 'punctuation.section.parens.begin', + 'punctuation.section.parens.end', + 'punctuation.separator.parameters', + 'support.function', + 'variable.other', + 'variable.function', + 'variable.parameter', + // pwsh + 'meta.group.simple.subexpression', + 'punctuation.section.group.begin', + 'punctuation.section.group.end', +]; + +const semanticTokenPrefix = 'polyglot-notebook'; +function addScope(scopeName) { + if (scopeName.indexOf(' ') >= 0) { + // not dealing with hierarchical scopes right now (separated with spaces) + return; + } + + // a scope like `comment.line` gets turned into `polyglot-notebook-comment-line` + const specializedName = `${semanticTokenPrefix}-${scopeName.replace(/\./g, '-')}`; + scopes[specializedName] = [scopeName]; + console.log(`added scope [${specializedName}] for [${scopeName}]`); +} + +function processScopesFromThemePath(themePath) { + const themeContents = fs.readFileSync(themePath, 'utf-8'); + + // the theme file has trailing commas in its objects/arrays, so we have to do a terrible thing to read it + const themeObject = eval('_ = ' + themeContents); + for (const scopeColorBlock of themeObject.tokenColors) { + if (typeof scopeColorBlock.scope === 'string') { + addScope(scopeColorBlock.scope); + } + else if (Array.isArray(scopeColorBlock.scope)) { + for (const scopeValue of scopeColorBlock.scope) { + if (typeof scopeValue === 'string') { + addScope(scopeValue); + } + } + } + } +} + +for (const manualScope of manualScopesToAdd) { + addScope(manualScope); +} + +const themeSubPath = 'Programs/Microsoft VS Code Insiders/resources/app/extensions/theme-defaults/themes/light_vs.json'; +const themePath = path.join(process.env['LOCALAPPDATA'], ...themeSubPath.split('/')); +processScopesFromThemePath(themePath); + +const projectJsonPaths = [ + path.join(__dirname, '..', 'package.json'), // insiders + path.join(__dirname, '..', '..', 'dotnet-interactive-vscode', 'package.json'), // stable +]; + +for (const projectJsonPath of projectJsonPaths) { + console.log(`adding scopes to file ${projectJsonPath}`); + const projectJsonContents = JSON.parse(fs.readFileSync(projectJsonPath)); + projectJsonContents.contributes.semanticTokenScopes = [ + { + comment: 'the `scopes` object is generated by a script', + scopes: scopes + } + ]; + + let updatedProjectJsonContents = JSON.stringify(projectJsonContents, null, 2); + if (os.platform() === 'win32') { + updatedProjectJsonContents = updatedProjectJsonContents.replace(/\n/g, '\r\n'); + } + + fs.writeFileSync(projectJsonPath, updatedProjectJsonContents); +} diff --git a/src/dotnet-interactive-vscode/.vscode/settings.json b/src/dotnet-interactive-vscode/.vscode/settings.json index ae3ab70d36..4b484f4ddd 100644 --- a/src/dotnet-interactive-vscode/.vscode/settings.json +++ b/src/dotnet-interactive-vscode/.vscode/settings.json @@ -11,4 +11,5 @@ "files.trimTrailingWhitespace": true, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts "typescript.tsc.autoDetect": "off", + "typescript.format.enable": true } \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.kql.tmGrammar.json b/src/dotnet-interactive-vscode/grammars/kql.tmGrammar.json similarity index 98% rename from src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.kql.tmGrammar.json rename to src/dotnet-interactive-vscode/grammars/kql.tmGrammar.json index 69d6d0fcb6..baac818a14 100644 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.kql.tmGrammar.json +++ b/src/dotnet-interactive-vscode/grammars/kql.tmGrammar.json @@ -1,12 +1,6 @@ { - "scopeName": "source.dotnet-interactive.kql", + "scopeName": "source.kql", "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, { "//comment": " Query statements-https://docs.microsoft.com/en-us/azure/kusto/query/statements", "match": "\\b(let|set|alias|declare|pattern|restrict|access|to|set)\\b", @@ -119,7 +113,7 @@ }, { "//comment": " User-defined functions-https://docs.microsoft.com/en-us/azure/kusto/query/functions/user-defined-functions", - "match": "\\.(create-or-alter|show)", + "match": "\\.create-or-alter", "name": "keyword.functions.kql" }, { diff --git a/src/dotnet-interactive-vscode/package-lock.json b/src/dotnet-interactive-vscode/package-lock.json index f325debdae..a2c5b8023d 100644 --- a/src/dotnet-interactive-vscode/package-lock.json +++ b/src/dotnet-interactive-vscode/package-lock.json @@ -13,6 +13,8 @@ "node-fetch": "2.6.7", "rxjs": "7.5.6", "uuid": "8.3.2", + "vscode-oniguruma": "1.6.1", + "vscode-textmate": "6.0.0", "vscode-uri": "3.0.6" }, "devDependencies": { @@ -46,9 +48,7 @@ "tmp": "0.2.1", "typescript": "4.4.4", "vsce": "2.10.2", - "vscode-oniguruma": "1.6.1", - "vscode-test": "1.6.1", - "vscode-textmate": "6.0.0" + "vscode-test": "1.6.1" }, "engines": { "vscode": "^1.73.0" @@ -2386,20 +2386,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", @@ -5244,8 +5230,7 @@ "node_modules/vscode-oniguruma": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz", - "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==", - "dev": true + "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==" }, "node_modules/vscode-test": { "version": "1.6.1", @@ -5265,8 +5250,7 @@ "node_modules/vscode-textmate": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz", - "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", - "dev": true + "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==" }, "node_modules/vscode-uri": { "version": "3.0.6", @@ -7137,13 +7121,6 @@ "version": "1.0.0", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "fstream": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", @@ -9129,8 +9106,7 @@ "vscode-oniguruma": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz", - "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==", - "dev": true + "integrity": "sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ==" }, "vscode-test": { "version": "1.6.1", @@ -9147,8 +9123,7 @@ "vscode-textmate": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz", - "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==", - "dev": true + "integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==" }, "vscode-uri": { "version": "3.0.6", diff --git a/src/dotnet-interactive-vscode/package.json b/src/dotnet-interactive-vscode/package.json index af74b3fad3..b7525ec5ed 100644 --- a/src/dotnet-interactive-vscode/package.json +++ b/src/dotnet-interactive-vscode/package.json @@ -94,15 +94,15 @@ "jupyter.kernels": [ { "title": ".NET Interactive (C#)", - "defaultlanguage": "dotnet-interactive.csharp" + "defaultlanguage": "polyglot-notebook" }, { "title": ".NET Interactive (F#)", - "defaultlanguage": "dotnet-interactive.fsharp" + "defaultlanguage": "polyglot-notebook" }, { "title": ".NET Interactive (PowerShell)", - "defaultlanguage": "dotnet-interactive.pwsh" + "defaultlanguage": "polyglot-notebook" } ], "configuration": { @@ -177,7 +177,7 @@ }, "dotnet-interactive.minimumInteractiveToolVersion": { "type": "string", - "default": "1.0.352802", + "default": "1.0.355307", "description": "The minimum required version of the .NET Interactive tool." } } @@ -234,6 +234,10 @@ "command": "polyglot-notebook.shareValueWith", "title": "Share value with..." }, + { + "command": "polyglot-notebook.selectCellKernel", + "title": "Polyglot Notebook: Select cell kernel" + }, { "command": "dotnet-interactive.openNotebook", "title": ".NET Interactive: Open notebook" @@ -281,77 +285,10 @@ ], "languages": [ { - "id": "dotnet-interactive", - "aliases": [ - "Polyglot Notebook" - ] - }, - { - "id": "dotnet-interactive.csharp", - "aliases": [ - "C# (Polyglot Notebook)" - ], - "configuration": "./syntaxes/csharp.language-configuration.json" - }, - { - "id": "dotnet-interactive.fsharp", + "id": "polyglot-notebook", "aliases": [ - "F# (Polyglot Notebook)" - ], - "configuration": "./syntaxes/fsharp.language-configuration.json" - }, - { - "id": "dotnet-interactive.html", - "aliases": [ - "HTML (Polyglot Notebook)" + "Code" ] - }, - { - "id": "dotnet-interactive.javascript", - "aliases": [ - "JavaScript (Polyglot Notebook)" - ], - "configuration": "./syntaxes/javascript.language-configuration.json" - }, - { - "id": "dotnet-interactive.magic-commands", - "aliases": [ - "Polyglot Notebook Magic Commands" - ], - "configuration": "./syntaxes/magic-commands.language-configuration.json" - }, - { - "id": "dotnet-interactive.markdown", - "aliases": [ - "Markdown (Polyglot Notebook)" - ] - }, - { - "id": "dotnet-interactive.mermaid", - "aliases": [ - "Mermaid (Polyglot Notebook)" - ] - }, - { - "id": "dotnet-interactive.pwsh", - "aliases": [ - "PowerShell (Polyglot Notebook)" - ], - "configuration": "./syntaxes/powershell.language-configuration.json" - }, - { - "id": "dotnet-interactive.sql", - "aliases": [ - "SQL (Polyglot Notebook)" - ], - "configuration": "./syntaxes/sql.language-configuration.json" - }, - { - "id": "dotnet-interactive.kql", - "aliases": [ - "Kusto Query Language (KQL) (Polyglot Notebook)" - ], - "configuration": "./syntaxes/kql.language-configuration.json" } ], "menus": { @@ -368,181 +305,445 @@ } ] }, - "grammars": [ - { - "language": "dotnet-interactive", - "scopeName": "source.dotnet-interactive", - "path": "./syntaxes/source.dotnet-interactive.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.csharp", - "scopeName": "source.dotnet-interactive.csharp", - "path": "./syntaxes/source.dotnet-interactive.csharp.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, + "semanticTokenScopes": [ { - "language": "dotnet-interactive.fsharp", - "scopeName": "source.dotnet-interactive.fsharp", - "path": "./syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.html", - "scopeName": "source.dotnet-interactive.html", - "path": "./syntaxes/source.dotnet-interactive.html.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.javascript", - "scopeName": "source.dotnet-interactive.javascript", - "path": "./syntaxes/source.dotnet-interactive.javascript.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.magic-commands", - "scopeName": "source.dotnet-interactive.magic-commands", - "path": "./syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.markdown", - "scopeName": "source.dotnet-interactive.markdown", - "path": "./syntaxes/source.dotnet-interactive.markdown.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.mermaid", - "scopeName": "source.dotnet-interactive.mermaid", - "path": "./syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.pwsh", - "scopeName": "source.dotnet-interactive.powershell", - "path": "./syntaxes/source.dotnet-interactive.powershell.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.sql", - "scopeName": "source.dotnet-interactive.sql", - "path": "./syntaxes/source.dotnet-interactive.sql.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" - } - }, - { - "language": "dotnet-interactive.kql", - "scopeName": "source.dotnet-interactive.kql", - "path": "./syntaxes/source.dotnet-interactive.kql.tmGrammar.json", - "embeddedLanguages": { - "language.switch.csharp": "dotnet-interactive.csharp", - "language.switch.fsharp": "dotnet-interactive.fsharp", - "language.switch.html": "dotnet-interactive.html", - "language.switch.javascript": "dotnet-interactive.javascript", - "language.switch.markdown": "dotnet-interactive.markdown", - "language.switch.mermaid": "dotnet-interactive.mermaid", - "language.switch.powershell": "dotnet-interactive.pwsh", - "language.switch.sql": "dotnet-interactive.sql", - "language.switch.kql": "dotnet-interactive.kql" + "comment": "the `scopes` object is generated by a script", + "scopes": { + "polyglot-notebook-entity-name-function": [ + "entity.name.function" + ], + "polyglot-notebook-entity-name-variable-local": [ + "entity.name.variable.local" + ], + "polyglot-notebook-entity-name-type": [ + "entity.name.type" + ], + "polyglot-notebook-entity-name-type-namespace": [ + "entity.name.type.namespace" + ], + "polyglot-notebook-punctuation-accessor": [ + "punctuation.accessor" + ], + "polyglot-notebook-punctuation-definition-variable": [ + "punctuation.definition.variable" + ], + "polyglot-notebook-punctuation-parenthesis": [ + "punctuation.parenthesis" + ], + "polyglot-notebook-punctuation-terminator-statement": [ + "punctuation.terminator.statement" + ], + "polyglot-notebook-variable-other-object": [ + "variable.other.object" + ], + "polyglot-notebook-variable-other-readwrite": [ + "variable.other.readwrite" + ], + "polyglot-notebook-variable-other-readwrite-global": [ + "variable.other.readwrite.global" + ], + "polyglot-notebook-meta-bracket": [ + "meta.bracket" + ], + "polyglot-notebook-meta-attribute": [ + "meta.attribute" + ], + "polyglot-notebook-meta-function": [ + "meta.function" + ], + "polyglot-notebook-meta-function-call-arguments": [ + "meta.function-call.arguments" + ], + "polyglot-notebook-meta-function-call-generic": [ + "meta.function-call.generic" + ], + "polyglot-notebook-meta-indexed-name": [ + "meta.indexed-name" + ], + "polyglot-notebook-meta-item-access-arguments": [ + "meta.item-access.arguments" + ], + "polyglot-notebook-punctuation-definition-arguments-begin": [ + "punctuation.definition.arguments.begin" + ], + "polyglot-notebook-punctuation-definition-arguments-end": [ + "punctuation.definition.arguments.end" + ], + "polyglot-notebook-punctuation-definition-list-begin": [ + "punctuation.definition.list.begin" + ], + "polyglot-notebook-punctuation-definition-list-end": [ + "punctuation.definition.list.end" + ], + "polyglot-notebook-punctuation-definition-parameters-begin": [ + "punctuation.definition.parameters.begin" + ], + "polyglot-notebook-punctuation-definition-parameters-end": [ + "punctuation.definition.parameters.end" + ], + "polyglot-notebook-punctuation-section-function-begin": [ + "punctuation.section.function.begin" + ], + "polyglot-notebook-punctuation-section-function-end": [ + "punctuation.section.function.end" + ], + "polyglot-notebook-punctuation-separator-element": [ + "punctuation.separator.element" + ], + "polyglot-notebook-punctuation-separator-colon": [ + "punctuation.separator.colon" + ], + "polyglot-notebook-punctuation-separator-period": [ + "punctuation.separator.period" + ], + "polyglot-notebook-punctuation-separator-slice": [ + "punctuation.separator.slice" + ], + "polyglot-notebook-variable-parameter-function-call": [ + "variable.parameter.function-call" + ], + "polyglot-notebook-punctuation-section-brackets-single-begin": [ + "punctuation.section.brackets.single.begin" + ], + "polyglot-notebook-punctuation-section-brackets-single-end": [ + "punctuation.section.brackets.single.end" + ], + "polyglot-notebook-punctuation-section-parens-begin": [ + "punctuation.section.parens.begin" + ], + "polyglot-notebook-punctuation-section-parens-end": [ + "punctuation.section.parens.end" + ], + "polyglot-notebook-punctuation-separator-parameters": [ + "punctuation.separator.parameters" + ], + "polyglot-notebook-support-function": [ + "support.function" + ], + "polyglot-notebook-variable-other": [ + "variable.other" + ], + "polyglot-notebook-variable-function": [ + "variable.function" + ], + "polyglot-notebook-variable-parameter": [ + "variable.parameter" + ], + "polyglot-notebook-meta-group-simple-subexpression": [ + "meta.group.simple.subexpression" + ], + "polyglot-notebook-punctuation-section-group-begin": [ + "punctuation.section.group.begin" + ], + "polyglot-notebook-punctuation-section-group-end": [ + "punctuation.section.group.end" + ], + "polyglot-notebook-meta-embedded": [ + "meta.embedded" + ], + "polyglot-notebook-source-groovy-embedded": [ + "source.groovy.embedded" + ], + "polyglot-notebook-emphasis": [ + "emphasis" + ], + "polyglot-notebook-strong": [ + "strong" + ], + "polyglot-notebook-meta-diff-header": [ + "meta.diff.header" + ], + "polyglot-notebook-comment": [ + "comment" + ], + "polyglot-notebook-constant-language": [ + "constant.language" + ], + "polyglot-notebook-constant-numeric": [ + "constant.numeric" + ], + "polyglot-notebook-variable-other-enummember": [ + "variable.other.enummember" + ], + "polyglot-notebook-keyword-operator-plus-exponent": [ + "keyword.operator.plus.exponent" + ], + "polyglot-notebook-keyword-operator-minus-exponent": [ + "keyword.operator.minus.exponent" + ], + "polyglot-notebook-constant-regexp": [ + "constant.regexp" + ], + "polyglot-notebook-entity-name-tag": [ + "entity.name.tag" + ], + "polyglot-notebook-entity-name-selector": [ + "entity.name.selector" + ], + "polyglot-notebook-entity-other-attribute-name": [ + "entity.other.attribute-name" + ], + "polyglot-notebook-entity-other-attribute-name-class-css": [ + "entity.other.attribute-name.class.css" + ], + "polyglot-notebook-entity-other-attribute-name-class-mixin-css": [ + "entity.other.attribute-name.class.mixin.css" + ], + "polyglot-notebook-entity-other-attribute-name-id-css": [ + "entity.other.attribute-name.id.css" + ], + "polyglot-notebook-entity-other-attribute-name-parent-selector-css": [ + "entity.other.attribute-name.parent-selector.css" + ], + "polyglot-notebook-entity-other-attribute-name-pseudo-class-css": [ + "entity.other.attribute-name.pseudo-class.css" + ], + "polyglot-notebook-entity-other-attribute-name-pseudo-element-css": [ + "entity.other.attribute-name.pseudo-element.css" + ], + "polyglot-notebook-entity-other-attribute-name-scss": [ + "entity.other.attribute-name.scss" + ], + "polyglot-notebook-invalid": [ + "invalid" + ], + "polyglot-notebook-markup-underline": [ + "markup.underline" + ], + "polyglot-notebook-markup-bold": [ + "markup.bold" + ], + "polyglot-notebook-markup-heading": [ + "markup.heading" + ], + "polyglot-notebook-markup-italic": [ + "markup.italic" + ], + "polyglot-notebook-markup-strikethrough": [ + "markup.strikethrough" + ], + "polyglot-notebook-markup-inserted": [ + "markup.inserted" + ], + "polyglot-notebook-markup-deleted": [ + "markup.deleted" + ], + "polyglot-notebook-markup-changed": [ + "markup.changed" + ], + "polyglot-notebook-punctuation-definition-quote-begin-markdown": [ + "punctuation.definition.quote.begin.markdown" + ], + "polyglot-notebook-punctuation-definition-list-begin-markdown": [ + "punctuation.definition.list.begin.markdown" + ], + "polyglot-notebook-markup-inline-raw": [ + "markup.inline.raw" + ], + "polyglot-notebook-punctuation-definition-tag": [ + "punctuation.definition.tag" + ], + "polyglot-notebook-meta-preprocessor": [ + "meta.preprocessor" + ], + "polyglot-notebook-entity-name-function-preprocessor": [ + "entity.name.function.preprocessor" + ], + "polyglot-notebook-meta-preprocessor-string": [ + "meta.preprocessor.string" + ], + "polyglot-notebook-meta-preprocessor-numeric": [ + "meta.preprocessor.numeric" + ], + "polyglot-notebook-meta-structure-dictionary-key-python": [ + "meta.structure.dictionary.key.python" + ], + "polyglot-notebook-storage": [ + "storage" + ], + "polyglot-notebook-storage-type": [ + "storage.type" + ], + "polyglot-notebook-storage-modifier": [ + "storage.modifier" + ], + "polyglot-notebook-keyword-operator-noexcept": [ + "keyword.operator.noexcept" + ], + "polyglot-notebook-string": [ + "string" + ], + "polyglot-notebook-meta-embedded-assembly": [ + "meta.embedded.assembly" + ], + "polyglot-notebook-string-comment-buffered-block-pug": [ + "string.comment.buffered.block.pug" + ], + "polyglot-notebook-string-quoted-pug": [ + "string.quoted.pug" + ], + "polyglot-notebook-string-interpolated-pug": [ + "string.interpolated.pug" + ], + "polyglot-notebook-string-unquoted-plain-in-yaml": [ + "string.unquoted.plain.in.yaml" + ], + "polyglot-notebook-string-unquoted-plain-out-yaml": [ + "string.unquoted.plain.out.yaml" + ], + "polyglot-notebook-string-unquoted-block-yaml": [ + "string.unquoted.block.yaml" + ], + "polyglot-notebook-string-quoted-single-yaml": [ + "string.quoted.single.yaml" + ], + "polyglot-notebook-string-quoted-double-xml": [ + "string.quoted.double.xml" + ], + "polyglot-notebook-string-quoted-single-xml": [ + "string.quoted.single.xml" + ], + "polyglot-notebook-string-unquoted-cdata-xml": [ + "string.unquoted.cdata.xml" + ], + "polyglot-notebook-string-quoted-double-html": [ + "string.quoted.double.html" + ], + "polyglot-notebook-string-quoted-single-html": [ + "string.quoted.single.html" + ], + "polyglot-notebook-string-unquoted-html": [ + "string.unquoted.html" + ], + "polyglot-notebook-string-quoted-single-handlebars": [ + "string.quoted.single.handlebars" + ], + "polyglot-notebook-string-quoted-double-handlebars": [ + "string.quoted.double.handlebars" + ], + "polyglot-notebook-string-regexp": [ + "string.regexp" + ], + "polyglot-notebook-punctuation-definition-template-expression-begin": [ + "punctuation.definition.template-expression.begin" + ], + "polyglot-notebook-punctuation-definition-template-expression-end": [ + "punctuation.definition.template-expression.end" + ], + "polyglot-notebook-punctuation-section-embedded": [ + "punctuation.section.embedded" + ], + "polyglot-notebook-meta-template-expression": [ + "meta.template.expression" + ], + "polyglot-notebook-support-constant-property-value": [ + "support.constant.property-value" + ], + "polyglot-notebook-support-constant-font-name": [ + "support.constant.font-name" + ], + "polyglot-notebook-support-constant-media-type": [ + "support.constant.media-type" + ], + "polyglot-notebook-support-constant-media": [ + "support.constant.media" + ], + "polyglot-notebook-constant-other-color-rgb-value": [ + "constant.other.color.rgb-value" + ], + "polyglot-notebook-constant-other-rgb-value": [ + "constant.other.rgb-value" + ], + "polyglot-notebook-support-constant-color": [ + "support.constant.color" + ], + "polyglot-notebook-support-type-vendored-property-name": [ + "support.type.vendored.property-name" + ], + "polyglot-notebook-support-type-property-name": [ + "support.type.property-name" + ], + "polyglot-notebook-variable-css": [ + "variable.css" + ], + "polyglot-notebook-variable-scss": [ + "variable.scss" + ], + "polyglot-notebook-variable-other-less": [ + "variable.other.less" + ], + "polyglot-notebook-source-coffee-embedded": [ + "source.coffee.embedded" + ], + "polyglot-notebook-support-type-property-name-json": [ + "support.type.property-name.json" + ], + "polyglot-notebook-keyword": [ + "keyword" + ], + "polyglot-notebook-keyword-control": [ + "keyword.control" + ], + "polyglot-notebook-keyword-operator": [ + "keyword.operator" + ], + "polyglot-notebook-keyword-operator-new": [ + "keyword.operator.new" + ], + "polyglot-notebook-keyword-operator-expression": [ + "keyword.operator.expression" + ], + "polyglot-notebook-keyword-operator-cast": [ + "keyword.operator.cast" + ], + "polyglot-notebook-keyword-operator-sizeof": [ + "keyword.operator.sizeof" + ], + "polyglot-notebook-keyword-operator-alignof": [ + "keyword.operator.alignof" + ], + "polyglot-notebook-keyword-operator-typeid": [ + "keyword.operator.typeid" + ], + "polyglot-notebook-keyword-operator-alignas": [ + "keyword.operator.alignas" + ], + "polyglot-notebook-keyword-operator-instanceof": [ + "keyword.operator.instanceof" + ], + "polyglot-notebook-keyword-operator-logical-python": [ + "keyword.operator.logical.python" + ], + "polyglot-notebook-keyword-operator-wordlike": [ + "keyword.operator.wordlike" + ], + "polyglot-notebook-keyword-other-unit": [ + "keyword.other.unit" + ], + "polyglot-notebook-punctuation-section-embedded-begin-php": [ + "punctuation.section.embedded.begin.php" + ], + "polyglot-notebook-punctuation-section-embedded-end-php": [ + "punctuation.section.embedded.end.php" + ], + "polyglot-notebook-support-function-git-rebase": [ + "support.function.git-rebase" + ], + "polyglot-notebook-constant-sha-git-rebase": [ + "constant.sha.git-rebase" + ], + "polyglot-notebook-storage-modifier-import-java": [ + "storage.modifier.import.java" + ], + "polyglot-notebook-variable-language-wildcard-java": [ + "variable.language.wildcard.java" + ], + "polyglot-notebook-storage-modifier-package-java": [ + "storage.modifier.package.java" + ], + "polyglot-notebook-variable-language": [ + "variable.language" + ] } } ] @@ -594,15 +795,15 @@ "tmp": "0.2.1", "typescript": "4.4.4", "vsce": "2.10.2", - "vscode-oniguruma": "1.6.1", - "vscode-test": "1.6.1", - "vscode-textmate": "6.0.0" + "vscode-test": "1.6.1" }, "dependencies": { "compare-versions": "3.6.0", "node-fetch": "2.6.7", - "uuid": "8.3.2", "rxjs": "7.5.6", + "uuid": "8.3.2", + "vscode-oniguruma": "1.6.1", + "vscode-textmate": "6.0.0", "vscode-uri": "3.0.6" } } \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/src/notebookControllers.ts b/src/dotnet-interactive-vscode/src/notebookControllers.ts index d8391c9609..8c337a68f1 100644 --- a/src/dotnet-interactive-vscode/src/notebookControllers.ts +++ b/src/dotnet-interactive-vscode/src/notebookControllers.ts @@ -7,21 +7,19 @@ import * as contracts from './vscode-common/dotnet-interactive/contracts'; import * as vscodeLike from './vscode-common/interfaces/vscode-like'; import * as diagnostics from './vscode-common/diagnostics'; import * as vscodeUtilities from './vscode-common/vscodeUtilities'; -import { getSimpleLanguage, isDotnetInteractiveLanguage, jupyterViewType, notebookCellLanguages } from './vscode-common/interactiveNotebook'; -import { getCellLanguage, getDotNetMetadata, getLanguageInfoMetadata, isDotNetNotebookMetadata, withDotNetKernelMetadata } from './vscode-common/ipynbUtilities'; import { reshapeOutputValueForVsCode } from './vscode-common/interfaces/utilities'; import { selectDotNetInteractiveKernelForJupyter } from './vscode-common/commands'; import { ErrorOutputCreator, InteractiveClient } from './vscode-common/interactiveClient'; import { LogEntry, Logger } from './vscode-common/dotnet-interactive/logger'; -import * as versionSpecificFunctions from './versionSpecificFunctions'; -import { KernelCommandOrEventEnvelope } from './vscode-common/dotnet-interactive/connection'; +import { isKernelEventEnvelope, KernelCommandOrEventEnvelope } from './vscode-common/dotnet-interactive/connection'; import * as rxjs from 'rxjs'; +import * as metadataUtilities from './vscode-common/metadataUtilities'; +import * as constants from './vscode-common/constants'; +import * as versionSpecificFunctions from './versionSpecificFunctions'; +import * as semanticTokens from './vscode-common/documentSemanticTokenProvider'; const executionTasks: Map = new Map(); -const viewType = 'polyglot-notebook'; -const legacyViewType = 'dotnet-interactive-legacy'; - export interface DotNetNotebookKernelConfiguration { clientMapper: ClientMapper, preloadUris: vscode.Uri[], @@ -32,13 +30,13 @@ export class DotNetNotebookKernel { private disposables: { dispose(): void }[] = []; - constructor(readonly config: DotNetNotebookKernelConfiguration) { + constructor(readonly config: DotNetNotebookKernelConfiguration, readonly tokensProvider: semanticTokens.DocumentSemanticTokensProvider) { const preloads = config.preloadUris.map(uri => new vscode.NotebookRendererScript(uri)); // .dib execution const dibController = vscode.notebooks.createNotebookController( - 'polyglot-notebook', - viewType, + constants.NotebookControllerId, + constants.NotebookViewType, '.NET Interactive', this.executeHandler.bind(this), preloads @@ -47,8 +45,8 @@ export class DotNetNotebookKernel { // .dotnet-interactive execution const legacyController = vscode.notebooks.createNotebookController( - 'polyglot-notebook-legacy', - legacyViewType, + constants.LegacyNotebookControllerId, + constants.LegacyNotebookViewType, '.NET Interactive', this.executeHandler.bind(this), preloads @@ -57,8 +55,8 @@ export class DotNetNotebookKernel { // .ipynb execution via Jupyter extension (optional) const jupyterController = vscode.notebooks.createNotebookController( - 'polyglot-notebook-for-jupyter', - jupyterViewType, + constants.JupyterNotebookControllerId, + constants.JupyterViewType, '.NET Interactive', this.executeHandler.bind(this), preloads @@ -72,15 +70,31 @@ export class DotNetNotebookKernel { this.commonControllerInit(jupyterController); this.disposables.push(vscode.workspace.onDidOpenNotebookDocument(async notebook => { - if (isDotNetNotebook(notebook)) { - // eagerly spin up the backing process - const client = await config.clientMapper.getOrAddClient(notebook.uri); - client.resetExecutionCount(); - - if (notebook.notebookType === jupyterViewType) { - jupyterController.updateNotebookAffinity(notebook, vscode.NotebookControllerAffinity.Preferred); - await selectDotNetInteractiveKernelForJupyter(); - await updateNotebookMetadata(notebook, this.config.clientMapper); + await this.onNotebookOpen(notebook, config.clientMapper, jupyterController); + })); + + // ...but we may have to look at already opened ones if we were activated late + for (const notebook of vscode.workspace.notebookDocuments) { + this.onNotebookOpen(notebook, config.clientMapper, jupyterController); + } + + this.disposables.push(vscode.workspace.onDidOpenTextDocument(async textDocument => { + if (vscode.window.activeNotebookEditor) { + const notebook = vscode.window.activeNotebookEditor.notebook; + if (isDotNetNotebook(notebook)) { + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(notebook); + const cells = notebook.getCells(); + const foundCell = cells.find(cell => cell.document === textDocument); + if (foundCell && foundCell.index > 0) { + // if we found the cell and it's not the first, ensure it has kernel metadata + const cellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(foundCell); + if (!cellMetadata.kernelName) { + // no kernel metadata; copy from previous cell + const previousCell = cells[foundCell.index - 1]; + const previousCellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(previousCell); + await vscodeUtilities.setCellKernelName(foundCell, previousCellMetadata.kernelName ?? notebookMetadata.kernelInfo.defaultKernelName); + } + } } } })); @@ -90,10 +104,29 @@ export class DotNetNotebookKernel { this.disposables.forEach(d => d.dispose()); } + private async onNotebookOpen(notebook: vscode.NotebookDocument, clientMapper: ClientMapper, jupyterController: vscode.NotebookController): Promise { + if (isDotNetNotebook(notebook)) { + // prepare initial grammar + const kernelInfos = metadataUtilities.getKernelInfosFromNotebookDocument(notebook); + this.tokensProvider.dynamicTokenProvider.rebuildNotebookGrammar(notebook.uri, kernelInfos); + + // eagerly spin up the backing process + const client = await clientMapper.getOrAddClient(notebook.uri); + client.resetExecutionCount(); + + if (notebook.notebookType === constants.JupyterViewType) { + jupyterController.updateNotebookAffinity(notebook, vscode.NotebookControllerAffinity.Preferred); + await selectDotNetInteractiveKernelForJupyter(); + } + + await updateNotebookMetadata(notebook, this.config.clientMapper); + } + } + private uriMessageHandlerMap: Map> = new Map(); private commonControllerInit(controller: vscode.NotebookController) { - controller.supportedLanguages = notebookCellLanguages; + controller.supportedLanguages = [constants.CellLanguageIdentifier]; controller.supportsExecutionOrder = true; this.disposables.push(controller.onDidReceiveMessage(e => { const notebookUri = e.editor.notebook.uri; @@ -161,7 +194,7 @@ export class DotNetNotebookKernel { diagnosticCollection.set(cell.document.uri, diags.filter(d => d.severity !== contracts.DiagnosticSeverity.Hidden).map(vscodeUtilities.toVsCodeDiagnostic)); } - return client.execute(source, getSimpleLanguage(cell.document.languageId), outputObserver, diagnosticObserver, { id: cell.document.uri.toString() }).then(async (success) => { + return client.execute(source, vscodeUtilities.getCellKernelName(cell), outputObserver, diagnosticObserver, { id: cell.document.uri.toString() }).then(async (success) => { await outputUpdatePromise; endExecution(client, cell, success); }).catch(async () => { @@ -183,27 +216,67 @@ async function updateNotebookMetadata(notebook: vscode.NotebookDocument, clientM try { // update various metadata await updateDocumentKernelspecMetadata(notebook); - await updateCellLanguages(notebook); + await updateCellLanguagesAndKernels(notebook); // force creation of the client so we don't have to wait for the user to execute a cell to get the tool - await clientMapper.getOrAddClient(notebook.uri); + const client = await clientMapper.getOrAddClient(notebook.uri); + await updateKernelInfoMetadata(client, notebook); } catch (err) { vscode.window.showErrorMessage(`Failed to set document metadata for '${notebook.uri}': ${err}`); } } -export async function updateCellLanguages(document: vscode.NotebookDocument): Promise { - const documentLanguageInfo = getLanguageInfoMetadata(document.metadata); +async function updateKernelInfoMetadata(client: InteractiveClient, document: vscode.NotebookDocument): Promise { + const isIpynb = metadataUtilities.isIpynbNotebook(document); + client.channel.receiver.subscribe({ + next: async (commandOrEventEnvelope) => { + if (isKernelEventEnvelope(commandOrEventEnvelope) && commandOrEventEnvelope.eventType === contracts.KernelInfoProducedType) { + // got info about a kernel; either update an existing entry, or add a new one + let metadataChanged = false; + const kernelInfoProduced = commandOrEventEnvelope.event; + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); + for (const item of notebookMetadata.kernelInfo.items) { + if (item.name === kernelInfoProduced.kernelInfo.localName) { + metadataChanged = true; + item.languageName = kernelInfoProduced.kernelInfo.languageName; + item.aliases = kernelInfoProduced.kernelInfo.aliases; + } + } + + if (!metadataChanged) { + // nothing changed, must be a new kernel + notebookMetadata.kernelInfo.items.push({ + name: kernelInfoProduced.kernelInfo.localName, + languageName: kernelInfoProduced.kernelInfo.languageName, + aliases: kernelInfoProduced.kernelInfo.aliases + }); + } + + const existingRawNotebookDocumentMetadata = document.metadata; + const updatedRawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata, isIpynb); + const newRawNotebookDocumentMetadata = metadataUtilities.mergeRawMetadata(existingRawNotebookDocumentMetadata, updatedRawNotebookDocumentMetadata); + await versionSpecificFunctions.replaceNotebookMetadata(document.uri, newRawNotebookDocumentMetadata); + } + } + }); + + const notebookDocumentMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); + const kernelNotebokMetadata = metadataUtilities.getNotebookDocumentMetadataFromCompositeKernel(client.kernel); + const mergedMetadata = metadataUtilities.mergeNotebookDocumentMetadata(notebookDocumentMetadata, kernelNotebokMetadata); + const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(mergedMetadata, isIpynb); + await versionSpecificFunctions.replaceNotebookMetadata(document.uri, rawNotebookDocumentMetadata); +} + +async function updateCellLanguagesAndKernels(document: vscode.NotebookDocument): Promise { + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); - // update cell language + // update cell language and kernel await Promise.all(document.getCells().map(async (cell) => { - const cellMetadata = getDotNetMetadata(cell.metadata); - const cellText = cell.document.getText(); - const newLanguage = cell.kind === vscode.NotebookCellKind.Code - ? getCellLanguage(cellText, cellMetadata, documentLanguageInfo, cell.document.languageId) - : 'markdown'; - if (cell.document.languageId !== newLanguage) { - await vscode.languages.setTextDocumentLanguage(cell.document, newLanguage); + const cellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(cell); + await vscodeUtilities.ensureCellLanguage(cell); + if (!cellMetadata.kernelName) { + // no kernel specified; apply global + vscodeUtilities.setCellKernelName(cell, notebookMetadata.kernelInfo.defaultKernelName); } })); } @@ -258,32 +331,22 @@ function generateVsCodeNotebookCellOutputItem(data: Uint8Array, mime: string, st } async function updateDocumentKernelspecMetadata(document: vscode.NotebookDocument): Promise { - const documentKernelMetadata = withDotNetKernelMetadata(document.metadata); - const notebookEdit = vscode.NotebookEdit.updateNotebookMetadata(documentKernelMetadata); - const edit = new vscode.WorkspaceEdit(); - edit.set(document.uri, [notebookEdit]); - await vscode.workspace.applyEdit(edit); + const documentMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(document); + const newMetadata = metadataUtilities.createNewIpynbMetadataWithNotebookDocumentMetadata(document.metadata, documentMetadata); + await versionSpecificFunctions.replaceNotebookMetadata(document.uri, newMetadata); } function isDotNetNotebook(notebook: vscode.NotebookDocument): boolean { - if (notebook.uri.toString().endsWith('.dib')) { + const notebookUriString = notebook.uri.toString(); + if (notebookUriString.endsWith('.dib') || notebook.uri.fsPath.endsWith('.dib')) { return true; } - if (isDotNetNotebookMetadata(notebook.metadata)) { - // metadata looked correct + const kernelspecMetadata = metadataUtilities.getKernelspecMetadataFromIpynbNotebookDocument(notebook); + if (kernelspecMetadata.name.startsWith('.net-')) { return true; } - if (notebook.uri.scheme === 'untitled' && notebook.cellCount === 1) { - // untitled with a single cell, check cell - const cell = notebook.cellAt(0); - if (isDotnetInteractiveLanguage(cell.document.languageId) && cell.document.getText() === '') { - // language was one of ours and cell was emtpy - return true; - } - } - // doesn't look like us return false; } diff --git a/src/dotnet-interactive-vscode/src/notebookSerializers.ts b/src/dotnet-interactive-vscode/src/notebookSerializers.ts index 1849f01958..cf9beebe75 100644 --- a/src/dotnet-interactive-vscode/src/notebookSerializers.ts +++ b/src/dotnet-interactive-vscode/src/notebookSerializers.ts @@ -5,40 +5,62 @@ import * as vscode from 'vscode'; import * as contracts from './vscode-common/dotnet-interactive/contracts'; import * as utilities from './vscode-common/interfaces/utilities'; import * as vscodeLike from './vscode-common/interfaces/vscode-like'; -import { getNotebookSpecificLanguage, getSimpleLanguage, languageToCellKind } from './vscode-common/interactiveNotebook'; -import { getEol, vsCodeCellOutputToContractCellOutput } from './vscode-common/vscodeUtilities'; +import { languageToCellKind } from './vscode-common/interactiveNotebook'; +import * as vscodeUtilities from './vscode-common/vscodeUtilities'; import { NotebookParserServer } from './vscode-common/notebookParserServer'; import { Eol } from './vscode-common/interfaces'; +import * as metadataUtilities from './vscode-common/metadataUtilities'; +import * as constants from './vscode-common/constants'; function toInteractiveDocumentElement(cell: vscode.NotebookCellData): contracts.InteractiveDocumentElement { + // just need to match the shape + const fakeCell: vscodeLike.NotebookCell = { + kind: 0, + metadata: cell.metadata ?? {} + }; + const notebookCellMetadata = metadataUtilities.getNotebookCellMetadataFromNotebookCellElement(fakeCell); const outputs = cell.outputs || []; return { executionOrder: cell.executionSummary?.executionOrder ?? 0, - kernelName: getSimpleLanguage(cell.languageId), + kernelName: cell.languageId === 'markdown' ? 'markdown' : notebookCellMetadata.kernelName ?? 'csharp', contents: cell.value, - outputs: outputs.map(vsCodeCellOutputToContractCellOutput) + outputs: outputs.map(vscodeUtilities.vsCodeCellOutputToContractCellOutput) }; } async function deserializeNotebookByType(parserServer: NotebookParserServer, serializationType: contracts.DocumentSerializationType, rawData: Uint8Array): Promise { const interactiveDocument = await parserServer.parseInteractiveDocument(serializationType, rawData); + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromInteractiveDocument(interactiveDocument); + const createForIpynb = serializationType === contracts.DocumentSerializationType.Ipynb; + const rawNotebookDocumentMetadata = metadataUtilities.getRawNotebookDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata, createForIpynb); const notebookData: vscode.NotebookData = { - cells: interactiveDocument.elements.map(toVsCodeNotebookCellData) + cells: interactiveDocument.elements.map(element => toVsCodeNotebookCellData(element)), + metadata: rawNotebookDocumentMetadata }; return notebookData; } async function serializeNotebookByType(parserServer: NotebookParserServer, serializationType: contracts.DocumentSerializationType, eol: Eol, data: vscode.NotebookData): Promise { + // just need to match the shape + const fakeNotebookDocument: vscodeLike.NotebookDocument = { + uri: { + fsPath: 'unused', + scheme: 'unused' + }, + metadata: data.metadata ?? {} + }; + const notebookMetadata = metadataUtilities.getNotebookDocumentMetadataFromNotebookDocument(fakeNotebookDocument); + const rawInteractiveDocumentNotebookMetadata = metadataUtilities.getRawInteractiveDocumentMetadataFromNotebookDocumentMetadata(notebookMetadata); const interactiveDocument: contracts.InteractiveDocument = { elements: data.cells.map(toInteractiveDocumentElement), - metadata: {} + metadata: rawInteractiveDocumentNotebookMetadata }; const rawData = await parserServer.serializeNotebook(serializationType, eol, interactiveDocument); return rawData; } export function createAndRegisterNotebookSerializers(context: vscode.ExtensionContext, parserServer: NotebookParserServer): Map { - const eol = getEol(); + const eol = vscodeUtilities.getEol(); const createAndRegisterSerializer = (serializationType: contracts.DocumentSerializationType, notebookType: string): vscode.NotebookSerializer => { const serializer: vscode.NotebookSerializer = { deserializeNotebook(content: Uint8Array, _token: vscode.CancellationToken): Promise { @@ -54,8 +76,8 @@ export function createAndRegisterNotebookSerializers(context: vscode.ExtensionCo }; const serializers = new Map(); - serializers.set('polyglot-notebook', createAndRegisterSerializer(contracts.DocumentSerializationType.Dib, 'polyglot-notebook')); - serializers.set('jupyter-notebook', createAndRegisterSerializer(contracts.DocumentSerializationType.Ipynb, 'polyglot-notebook-jupyter')); + serializers.set(constants.NotebookViewType, createAndRegisterSerializer(contracts.DocumentSerializationType.Dib, constants.NotebookViewType)); + serializers.set(constants.JupyterViewType, createAndRegisterSerializer(contracts.DocumentSerializationType.Ipynb, constants.JupyterNotebookViewType)); return serializers; } @@ -63,7 +85,12 @@ function toVsCodeNotebookCellData(cell: contracts.InteractiveDocumentElement): v const cellData = new vscode.NotebookCellData( languageToCellKind(cell.kernelName), cell.contents, - getNotebookSpecificLanguage(cell.kernelName)); + cell.kernelName === 'markdown' ? 'markdown' : constants.CellLanguageIdentifier); + const notebookCellMetadata: metadataUtilities.NotebookCellMetadata = { + kernelName: cell.kernelName + }; + const rawNotebookCellMetadata = metadataUtilities.getRawNotebookCellMetadataFromNotebookCellMetadata(notebookCellMetadata); + cellData.metadata = rawNotebookCellMetadata; cellData.outputs = cell.outputs.map(outputElementToVsCodeCellOutput); return cellData; } diff --git a/src/dotnet-interactive-vscode/syntaxes/csharp.language-configuration.json b/src/dotnet-interactive-vscode/syntaxes/csharp.language-configuration.json deleted file mode 100644 index 666d4a65e1..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/csharp.language-configuration.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": [ - "/*", - "*/" - ] - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - }, - { - "open": "/**", - "close": " */", - "notIn": [ - "string" - ] - } - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ] - ], - "folding": { - "markers": { - "start": "^\\s*//\\s*#?region\\b", - "end": "^\\s*//\\s*#?endregion\\b" - } - }, - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", - "indentationRules": { - "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", - "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" - } -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/fsharp.language-configuration.json b/src/dotnet-interactive-vscode/syntaxes/fsharp.language-configuration.json deleted file mode 100644 index e024b63095..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/fsharp.language-configuration.json +++ /dev/null @@ -1,148 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": [ - "(*", - "*)" - ] - }, - "autoClosingPairs": [ - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "{", - "}" - ], - [ - "<@", - "@>" - ], - [ - "``", - "``" - ], - [ - "\"", - "\"" - ], - [ - "(|", - "|)" - ], - [ - "[<", - ">]" - ], - [ - "[|", - "|]" - ], - [ - "(*", - "*)" - ], - [ - "<@@", - "@@>" - ], - [ - "{|", - "|}" - ] - ], - "surroundingPairs": [ - [ - "<@@", - "@@>" - ], - [ - "(|", - "|)" - ], - [ - "[<", - ">]" - ], - [ - "[|", - "|]" - ], - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "\"", - "\"" - ], - [ - "\"\"\"", - "\"\"\"" - ], - [ - "(*", - "*)" - ], - [ - "<@", - "@>" - ], - [ - "'", - "'" - ], - [ - "{|", - "|}" - ] - ], - "brackets": [ - [ - "(", - ")" - ], - [ - "(*", - "*)" - ], - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "[|", - "|]" - ], - [ - "<@", - "@>" - ], - [ - "<@@", - "@@>" - ], - [ - "{|", - "|}" - ] - ], - "wordPattern": "(``((\\w*\\s?)\\s?)*``)|((\\w*')(\\w'?)*)|(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\$\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\\/\\s]+)" -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/javascript.language-configuration.json b/src/dotnet-interactive-vscode/syntaxes/javascript.language-configuration.json deleted file mode 100644 index 4a32c28b99..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/javascript.language-configuration.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": [ - "/*", - "*/" - ] - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - }, - { - "open": "`", - "close": "`", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "/**", - "close": " */", - "notIn": [ - "string" - ] - } - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ], - [ - "`", - "`" - ] - ], - "folding": { - "markers": { - "start": "^\\s*//\\s*#?region\\b", - "end": "^\\s*//\\s*#?endregion\\b" - } - }, - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", - "indentationRules": { - "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", - "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" - } -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/kql.language-configuration.json b/src/dotnet-interactive-vscode/syntaxes/kql.language-configuration.json deleted file mode 100644 index 672ab68d8d..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/kql.language-configuration.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "comments": { - "lineComment": "//" - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - } - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ] - ], - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", - "indentationRules": { - "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", - "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" - } -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/magic-commands.language-configuration.json b/src/dotnet-interactive-vscode/syntaxes/magic-commands.language-configuration.json deleted file mode 100644 index 611407e61c..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/magic-commands.language-configuration.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "autoClosingPairs": [ - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - } - ], - "surroundingPairs": [ - [ - "\"", - "\"" - ] - ] -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/powershell.language-configuration.json b/src/dotnet-interactive-vscode/syntaxes/powershell.language-configuration.json deleted file mode 100644 index 93b4714682..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/powershell.language-configuration.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "indentationRules": { - "increaseIndentPattern": "^.*\\{[^}\"']*$", - "decreaseIndentPattern": "^(.*\\*/)?\\s*\\}.*$" - }, - "comments": { - "lineComment": "#", - "blockComment": [ - "<#", - "#>" - ], - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - ], -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.csharp.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.csharp.tmGrammar.json deleted file mode 100644 index 8b2da5d73f..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.csharp.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.csharp", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.cs" - } - ] -} diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json deleted file mode 100644 index 4e628ec7ee..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.fsharp.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.fsharp", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.fsharp" - } - ] -} diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.html.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.html.tmGrammar.json deleted file mode 100644 index 7bafbbc60f..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.html.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.html", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "text.html.basic" - } - ] -} diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.javascript.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.javascript.tmGrammar.json deleted file mode 100644 index 7929da301d..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.javascript.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.javascript", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.js" - } - ] -} diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json deleted file mode 100644 index b1b579b7bf..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.magic-commands.tmGrammar.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": ".NET Interactive Magic Commands", - "scopeName": "source.dotnet-interactive.magic-commands", - "patterns": [ - { - "name": "comment.line.magic-commands", - "begin": "^(#!)(?!([cf](#|s(harp)?)|powershell|pwsh|html|javascript|js|markdown|md|mermaid|sql(-.+)?|kql(-.+)?))", - "end": "(?<=$)", - "beginCaptures": { - "1": { - "name": "comment.line.magic-commands.hash-bang" - } - }, - "patterns": [ - { - "include": "#magic-command-name" - }, - { - "include": "#strings" - }, - { - "include": "#option" - }, - { - "include": "#argument" - } - ] - } - ], - "repository": { - "magic-command-name": { - "patterns": [ - { - "name": "keyword.control.magic-commands", - "match": "(?<=^#!)[a-zA-Z0-9_-]+" - } - ] - }, - "option": { - "patterns": [ - { - "name": "constant.language.magic-commands", - "match": "(--?|/)[^\\s\\\"]+" - } - ] - }, - "argument": { - "patterns": [ - { - "name": "variable.parameter.magic-commands", - "match": "[^\\s\\\"]+" - } - ] - }, - "strings": { - "patterns": [ - { - "name": "string.quoted.double.magic-commands", - "begin": "\"", - "end": "\"", - "patterns": [ - { - "name": "constant.character.escape.magic-commands", - "match": "\\." - } - ] - } - ] - } - } -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.markdown.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.markdown.tmGrammar.json deleted file mode 100644 index 304d317b66..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.markdown.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.markdown", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "text.html.markdown" - } - ] -} diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json deleted file mode 100644 index 5dc7770d33..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.mermaid.tmGrammar.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.mermaid", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - } - ] -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.powershell.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.powershell.tmGrammar.json deleted file mode 100644 index ff7092565c..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.powershell.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.powershell", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.powershell" - } - ] -} diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.sql.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.sql.tmGrammar.json deleted file mode 100644 index 9aa9e56d56..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.sql.tmGrammar.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive.sql", - "patterns": [ - { - "include": "source.dotnet-interactive.magic-commands" - }, - { - "include": "source.dotnet-interactive" - }, - { - "include": "source.sql" - } - ] -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.tmGrammar.json b/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.tmGrammar.json deleted file mode 100644 index 36d7b3afff..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/source.dotnet-interactive.tmGrammar.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "scopeName": "source.dotnet-interactive", - "patterns": [ - { - "begin": "^#!cs(harp)?\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.csharp", - "patterns": [ - { - "include": "source.dotnet-interactive.csharp" - } - ] - }, - { - "begin": "^#!fs(harp)?\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.fsharp", - "patterns": [ - { - "include": "source.dotnet-interactive.fsharp" - } - ] - }, - { - "begin": "^#!html\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.html", - "patterns": [ - { - "include": "source.dotnet-interactive.html" - } - ] - }, - { - "begin": "^#!(javascript|js)\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.javascript", - "patterns": [ - { - "include": "source.dotnet-interactive.javascript" - } - ] - }, - { - "begin": "^#!(markdown|md)\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.markdown", - "patterns": [ - { - "include": "source.dotnet-interactive.markdown" - } - ] - }, - { - "begin": "^#!mermaid\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.mermaid", - "patterns": [ - { - "include": "source.dotnet-interactive.mermaid" - } - ] - }, - { - "begin": "^#!(powershell|pwsh)\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.powershell", - "patterns": [ - { - "include": "source.dotnet-interactive.powershell" - } - ] - }, - { - "begin": "^#!sql(-.+)?\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.sql", - "patterns": [ - { - "include": "source.dotnet-interactive.sql" - } - ] - }, - { - "begin": "^#!kql(-.+)?\\s+$", - "end": "(?=^#!(cs(harp)?|fs(harp)?|html|javascript|js|markdown|md|mermaid|powershell|pwsh|sql(-.+)?|kql(-.+)?)\\s+$)", - "name": "language.switch.kql", - "patterns": [ - { - "include": "source.dotnet-interactive.kql" - } - ] - } - ] -} \ No newline at end of file diff --git a/src/dotnet-interactive-vscode/syntaxes/sql.language-configuration.json b/src/dotnet-interactive-vscode/syntaxes/sql.language-configuration.json deleted file mode 100644 index 35bc9062be..0000000000 --- a/src/dotnet-interactive-vscode/syntaxes/sql.language-configuration.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "comments": { - "lineComment": "--", - "blockComment": [ - "/*", - "*/" - ] - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - }, - { - "open": "/**", - "close": " */", - "notIn": [ - "string" - ] - } - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ] - ], - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", - "indentationRules": { - "increaseIndentPattern": "^((?!\\/\\/).)*(\\{[^}\"'`]*|\\([^)\"'`]*|\\[[^\\]\"'`]*)$", - "decreaseIndentPattern": "^((?!.*?\\/\\*).*\\*/)?\\s*[\\}\\]].*$" - } -} \ No newline at end of file diff --git a/src/dotnet-interactive.Tests/CommandLine/CommandLineParserTests.cs b/src/dotnet-interactive.Tests/CommandLine/CommandLineParserTests.cs index dcd7a655e9..1e1854fbde 100644 --- a/src/dotnet-interactive.Tests/CommandLine/CommandLineParserTests.cs +++ b/src/dotnet-interactive.Tests/CommandLine/CommandLineParserTests.cs @@ -6,6 +6,7 @@ using System.CommandLine.IO; using System.CommandLine.NamingConventionBinder; using System.CommandLine.Parsing; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -116,7 +117,7 @@ public async Task stdio_mode_honors_log_path() using (var kernel = new CompositeKernel()) { - kernel.AddKernelConnector(new ConnectStdIoCommand()); + kernel.AddKernelConnector(new ConnectStdIoCommand(new Uri("kernel://test-kernel"))); await kernel.SendAsync(new SubmitCode($"#!connect stdio --kernel-name proxy --command \"{Dotnet.Path}\" \"{typeof(Program).Assembly.Location}\" stdio --log-path \"{logPath.Directory.FullName}\" --verbose")); @@ -320,6 +321,34 @@ public async Task jupyter_command_returns_error_if_connection_file_path_does_not testConsole.Error.ToString().Should().ContainAll("File does not exist", "not_exist.json"); } + [Fact] + public void stdio_command_kernel_host_defaults_to_process_id() + { + var result = _parser.Parse("stdio"); + + var binder = new ModelBinder(); + + var options = (StartupOptions)binder.CreateInstance(new InvocationContext(result).BindingContext); + + options.KernelHost + .Should() + .Be(new Uri($"kernel://pid-{Process.GetCurrentProcess().Id}")); + } + + [Fact] + public void stdio_command_kernel_name_can_be_specified() + { + var result = _parser.Parse("stdio --kernel-host some-kernel-name"); + + var binder = new ModelBinder(); + + var options = (StartupOptions)binder.CreateInstance(new InvocationContext(result).BindingContext); + + options.KernelHost + .Should() + .Be(new Uri("kernel://some-kernel-name")); + } + [Fact] public void stdio_command_working_dir_defaults_to_process_current() { diff --git a/src/dotnet-interactive.Tests/HttpApiTests.cs b/src/dotnet-interactive.Tests/HttpApiTests.cs index 651059a3fa..0d48419c10 100644 --- a/src/dotnet-interactive.Tests/HttpApiTests.cs +++ b/src/dotnet-interactive.Tests/HttpApiTests.cs @@ -400,7 +400,7 @@ public async Task stdio_mode_returns_javascript_api_via_http() using var kernel = new CompositeKernel(); - kernel.AddKernelConnector(new ConnectStdIoCommand()); + kernel.AddKernelConnector(new ConnectStdIoCommand(new Uri("kernel://test-kernel"))); await kernel.SendAsync(new SubmitCode($"#!connect stdio --kernel-name proxy --command \"{Dotnet.Path}\" \"{typeof(Program).Assembly.Location}\" stdio --http-port {port}")); diff --git a/src/dotnet-interactive.Tests/StdioConnectionTests.cs b/src/dotnet-interactive.Tests/StdioConnectionTests.cs index 65d34080f7..6ce1a5943b 100644 --- a/src/dotnet-interactive.Tests/StdioConnectionTests.cs +++ b/src/dotnet-interactive.Tests/StdioConnectionTests.cs @@ -40,7 +40,8 @@ protected override Task CreateConnectorAsync() var connector = new StdIoKernelConnector( command.ToArray(), - _configuration.WorkingDirectory); + kernelHostUri: new Uri("kernel://test-kernel"), + workingDirectory: _configuration.WorkingDirectory); RegisterForDisposal(connector); @@ -117,7 +118,7 @@ protected override SubmitCode CreateConnectCommand(string localKernelName) protected override void AddKernelConnector(CompositeKernel compositeKernel) { - compositeKernel.AddKernelConnector(new ConnectStdIoCommand()); + compositeKernel.AddKernelConnector(new ConnectStdIoCommand(new Uri("kernel://test-kernel"))); } } diff --git a/src/dotnet-interactive/CommandLine/CommandLineParser.cs b/src/dotnet-interactive/CommandLine/CommandLineParser.cs index 0ee3f26f25..c34fc7e134 100644 --- a/src/dotnet-interactive/CommandLine/CommandLineParser.cs +++ b/src/dotnet-interactive/CommandLine/CommandLineParser.cs @@ -286,6 +286,12 @@ Command StdIO() return new HttpPort(portNumber); }); + var kernelHostOption = new Option( + "--kernel-host", + parseArgument: x => x.Tokens.Count == 0 ? KernelHost.CreateHostUriForCurrentProcessId() : KernelHost.CreateHostUri(x.Tokens[0].Value), + isDefault: true, + description: "Name of the kernel host."); + var workingDirOption = new Option( "--working-dir", () => new DirectoryInfo(Environment.CurrentDirectory), @@ -298,6 +304,7 @@ Command StdIO() defaultKernelOption, httpPortRangeOption, httpPortOption, + kernelHostOption, workingDirOption }; @@ -313,8 +320,8 @@ Command StdIO() : new BrowserFrontendEnvironment(); var kernel = CreateKernel( - options.DefaultKernel, - frontendEnvironment, + options.DefaultKernel, + frontendEnvironment, startupOptions, telemetrySender); @@ -333,7 +340,7 @@ Command StdIO() var host = kernel.UseHost( sender, receiver, - KernelHost.CreateHostUriForCurrentProcessId()); + startupOptions.KernelHost); var isVSCode = context.ParseResult.Directives.Contains("vscode") || !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CODESPACES")); @@ -379,12 +386,11 @@ Command StdIO() } return 0; - }); return stdIOCommand; } - + Command NotebookParser() { var notebookParserCommand = new Command( @@ -489,7 +495,7 @@ private static CompositeKernel CreateKernel(string defaultKernelName, FrontendEn kernel.AddKernelConnector(new ConnectNamedPipeCommand()); kernel.AddKernelConnector(new ConnectSignalRCommand()); - kernel.AddKernelConnector(new ConnectStdIoCommand()); + kernel.AddKernelConnector(new ConnectStdIoCommand(startupOptions.KernelHost)); if (startupOptions.Verbose) { diff --git a/src/dotnet-interactive/CommandLine/StartupOptions.cs b/src/dotnet-interactive/CommandLine/StartupOptions.cs index 227d959a5a..875e5d2cd5 100644 --- a/src/dotnet-interactive/CommandLine/StartupOptions.cs +++ b/src/dotnet-interactive/CommandLine/StartupOptions.cs @@ -1,6 +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.IO; using Microsoft.DotNet.Interactive.Http; @@ -13,12 +14,14 @@ public StartupOptions( bool verbose = false, HttpPortRange httpPortRange = null, HttpPort httpPort = null, + Uri kernelHost = null, DirectoryInfo workingDir = null) { LogPath = logPath; Verbose = verbose; HttpPortRange = httpPortRange; HttpPort = httpPort; + KernelHost = kernelHost; WorkingDir = workingDir; } @@ -29,9 +32,11 @@ public StartupOptions( public HttpPort HttpPort { get; internal set; } public HttpPortRange HttpPortRange { get; internal set; } + + public Uri KernelHost { get; } public DirectoryInfo WorkingDir { get; internal set; } public bool EnableHttpApi => HttpPort is not null || HttpPortRange is not null; } -} \ No newline at end of file +} diff --git a/src/dotnet-interactive/Connection/ConnectStdIO.cs b/src/dotnet-interactive/Connection/ConnectStdIO.cs index 2a0b07f47e..062d742773 100644 --- a/src/dotnet-interactive/Connection/ConnectStdIO.cs +++ b/src/dotnet-interactive/Connection/ConnectStdIO.cs @@ -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; using System.CommandLine; using System.CommandLine.Invocation; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.Interactive.Connection; @@ -11,16 +13,28 @@ namespace Microsoft.DotNet.Interactive.App.Connection { public class ConnectStdIoCommand : ConnectKernelCommand { - public ConnectStdIoCommand() : base("stdio", + private static int KernelHostAuthoritySuffix = 1; + private Uri _kernelHostUri; + + public ConnectStdIoCommand(Uri kernelHostUri) : base("stdio", "Connects to a kernel using the stdio protocol") { + _kernelHostUri = kernelHostUri; + AddOption(CommandOption); AddOption(WorkingDirectoryOption); + AddOption(KernelHostUriOption); + } + + private string CreateKernelHostAuthority() + { + var suffix = Interlocked.Increment(ref KernelHostAuthoritySuffix); + return $"{_kernelHostUri.Authority}-{suffix}"; } public Option WorkingDirectoryOption { get; } = - new("--working-directory", - getDefaultValue: () => new DirectoryInfo(Directory.GetCurrentDirectory()), + new("--working-directory", + getDefaultValue: () => new DirectoryInfo(Directory.GetCurrentDirectory()), "The working directory"); public Option CommandOption { get; } = @@ -30,18 +44,26 @@ public ConnectStdIoCommand() : base("stdio", IsRequired = true, }; + public Option KernelHostUriOption { get; } = new( + "--kernel-host", + parseArgument: result => result.Tokens.Count == 0 ? null : result.Tokens[0].Value, + isDefault: true, + description: "Name of the kernel host."); + public override Task ConnectKernelAsync( KernelInvocationContext context, InvocationContext commandLineContext) { var command = commandLineContext.ParseResult.GetValueForOption(CommandOption); var workingDir = commandLineContext.ParseResult.GetValueForOption(WorkingDirectoryOption); + var kernelHostAuthority = commandLineContext.ParseResult.GetValueForOption(KernelHostUriOption) ?? CreateKernelHostAuthority(); + var kernelHostUri = KernelHost.CreateHostUri(kernelHostAuthority); - var connector = new StdIoKernelConnector(command, workingDir); + var connector = new StdIoKernelConnector(command, kernelHostUri, workingDir); var localName = commandLineContext.ParseResult.GetValueForOption(KernelNameOption); return connector.CreateKernelAsync(localName); } } -} \ No newline at end of file +} diff --git a/src/dotnet-interactive/Connection/StdIoKernelConnector.cs b/src/dotnet-interactive/Connection/StdIoKernelConnector.cs index 051eb54c12..d2a268cbc4 100644 --- a/src/dotnet-interactive/Connection/StdIoKernelConnector.cs +++ b/src/dotnet-interactive/Connection/StdIoKernelConnector.cs @@ -11,7 +11,9 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Text; +using System.Threading; using System.Threading.Tasks; +using Microsoft.DotNet.Interactive.Commands; using Microsoft.DotNet.Interactive.Connection; using Microsoft.DotNet.Interactive.Events; using Microsoft.DotNet.Interactive.Utility; @@ -24,12 +26,12 @@ public class StdIoKernelConnector : IKernelConnector, IDisposable private KernelCommandAndEventReceiver? _receiver; private KernelCommandAndEventSender? _sender; private Process? _process; - private Uri? _remoteHostUri; private RefCountDisposable? _refCountDisposable = null; - public StdIoKernelConnector(string[] command, DirectoryInfo? workingDirectory = null) + public StdIoKernelConnector(string[] command, Uri kernelHostUri, DirectoryInfo? workingDirectory = null) { Command = command; + KernelHostUri = kernelHostUri; WorkingDirectory = workingDirectory ?? new DirectoryInfo(Environment.CurrentDirectory); } @@ -37,6 +39,8 @@ public StdIoKernelConnector(string[] command, DirectoryInfo? workingDirectory = public DirectoryInfo WorkingDirectory { get; } + public Uri KernelHostUri { get; } + public async Task CreateKernelAsync(string kernelName) { ProxyKernel? proxyKernel; @@ -44,14 +48,22 @@ public async Task CreateKernelAsync(string kernelName) if (_receiver is null) { var command = Command[0]; - var arguments = string.Join(" ", Command.Skip(1)); + var arguments = Command.Skip(1).ToArray(); + if (KernelHostUri is { }) + { + arguments = arguments.Concat(new[] + { + "--kernel-host", + KernelHostUri.Authority + }).ToArray(); + } _process = new Process { StartInfo = new ProcessStartInfo { FileName = command, - Arguments = arguments, + Arguments = string.Join(" ", arguments), EnvironmentVariables = { ["DOTNET_INTERACTIVE_SKIP_FIRST_TIME_EXPERIENCE"] = "1", @@ -85,9 +97,6 @@ public async Task CreateKernelAsync(string kernelName) await Task.Yield(); _process.Start(); - _process.BeginOutputReadLine(); - _process.BeginErrorReadLine(); - _remoteHostUri = KernelHost.CreateHostUriForProcessId(_process.Id); _receiver = KernelCommandAndEventReceiver.FromObservable(stdOutObservable); @@ -95,14 +104,18 @@ public async Task CreateKernelAsync(string kernelName) _receiver.Select(coe => coe.Event) .OfType() .Take(1) - .Subscribe(e => kernelReadyReceived = true); + .Subscribe(e => + { + kernelReadyReceived = true; + }); _sender = KernelCommandAndEventSender.FromTextWriter( - _process.StandardInput, - _remoteHostUri); + _process.StandardInput, + KernelHostUri); _refCountDisposable = new RefCountDisposable(new CompositeDisposable { + SendQuitCommand, KillRemoteKernelProcess, _receiver.Dispose }); @@ -111,10 +124,13 @@ public async Task CreateKernelAsync(string kernelName) kernelName, _sender, _receiver, - new Uri(_remoteHostUri, kernelName)); + KernelHostUri); proxyKernel.RegisterForDisposal(_refCountDisposable); + _process.BeginOutputReadLine(); + _process.BeginErrorReadLine(); + while (!kernelReadyReceived) { await Task.Delay(200); @@ -140,7 +156,7 @@ public async Task CreateKernelAsync(string kernelName) kernelName, _sender, _receiver, - new Uri(_remoteHostUri!, kernelName)); + KernelHostUri); proxyKernel.RegisterForDisposal(_refCountDisposable!.GetDisposable()); } @@ -148,6 +164,14 @@ public async Task CreateKernelAsync(string kernelName) return proxyKernel; } + private void SendQuitCommand() + { + if (_sender is not null) + { + var _ = _sender.SendAsync(new Quit(), CancellationToken.None); + } + } + private void KillRemoteKernelProcess() { if (_process is { HasExited: false }) @@ -160,4 +184,4 @@ private void KillRemoteKernelProcess() } public void Dispose() => _refCountDisposable?.Dispose(); -} \ No newline at end of file +} diff --git a/src/interface-generator/InterfaceGenerator.cs b/src/interface-generator/InterfaceGenerator.cs index 5a67a3adc8..8c730bd86d 100644 --- a/src/interface-generator/InterfaceGenerator.cs +++ b/src/interface-generator/InterfaceGenerator.cs @@ -35,6 +35,11 @@ public class InterfaceGenerator { typeof(Uri), "string" }, }; + private static readonly Dictionary TypeNameOverrides = new() + { + { typeof(Documents.KernelInfo), "DocumentKernelInfo" } + }; + private static readonly HashSet AlwaysEmitTypes = new() { typeof(KernelCommand), @@ -43,6 +48,7 @@ public class InterfaceGenerator typeof(ReturnValueElement), typeof(TextElement), typeof(ErrorElement), + typeof(Documents.KernelInfo), }; private static readonly HashSet ParserServerTypes = new() @@ -246,9 +252,14 @@ void HandlePropertyType(Type propertyType) private static string TypeName(Type type) { - if (WellKnownTypes.TryGetValue(type, out var name)) + if (WellKnownTypes.TryGetValue(type, out var wellKnownName)) + { + return wellKnownName; + } + + if (TypeNameOverrides.TryGetValue(type, out var overrideName)) { - return name; + return overrideName; } return type.Name; diff --git a/src/interface-generator/StaticContents.ts b/src/interface-generator/StaticContents.ts index 195b9c06ef..78a846a8c7 100644 --- a/src/interface-generator/StaticContents.ts +++ b/src/interface-generator/StaticContents.ts @@ -1,4 +1,9 @@ -export interface KernelEventEnvelope { +export interface DocumentKernelInfoCollection { + defaultKernelName: string; + items: DocumentKernelInfo[]; +} + +export interface KernelEventEnvelope { eventType: KernelEventType; event: KernelEvent; command?: KernelCommandEnvelope; @@ -19,4 +24,4 @@ export interface KernelEventEnvelopeObserver { export interface KernelCommandEnvelopeHandler { (eventEnvelope: KernelCommandEnvelope): Promise; -} \ No newline at end of file +} diff --git a/src/microsoft-dotnet-interactive-browser/tests/kernel-client.test.ts b/src/microsoft-dotnet-interactive-browser/tests/kernel-client.test.ts index 78ab6d211b..04c4fd5ed4 100644 --- a/src/microsoft-dotnet-interactive-browser/tests/kernel-client.test.ts +++ b/src/microsoft-dotnet-interactive-browser/tests/kernel-client.test.ts @@ -233,9 +233,10 @@ describe("dotnet-interactive", () => { event: eventIn }; - expect(transport!.eventsPublished.length).to.be.equal(2); - expect(transport!.eventsPublished[1].eventType).to.be.equal(eventEnvelopeIn.eventType); - let eventPublished = transport!.eventsPublished[1].event; + const publishedEvents = transport!.eventsPublished.filter(e => e.eventType === eventEnvelopeIn.eventType); + expect(publishedEvents.length).to.equal(1); + expect(publishedEvents[0].eventType).to.be.equal(eventEnvelopeIn.eventType); + const eventPublished = publishedEvents[0].event; expect(eventPublished.code).to.be.equal(eventIn.code); }); diff --git a/src/microsoft-dotnet-interactive/src/connection.ts b/src/microsoft-dotnet-interactive/src/connection.ts index 7bb8193438..531bd1540c 100644 --- a/src/microsoft-dotnet-interactive/src/connection.ts +++ b/src/microsoft-dotnet-interactive/src/connection.ts @@ -8,7 +8,6 @@ import * as disposables from './disposables'; import { Disposable } from './disposables'; import { KernelType } from './kernel'; import { Logger } from './logger'; -import { URI } from 'vscode-uri'; export type KernelCommandOrEventEnvelope = contracts.KernelCommandEnvelope | contracts.KernelEventEnvelope; @@ -21,7 +20,6 @@ export function isKernelEventEnvelope(commandOrEvent: KernelCommandOrEventEnvelo } export interface IKernelCommandAndEventReceiver extends rxjs.Subscribable { - } export interface IKernelCommandAndEventSender { @@ -117,15 +115,17 @@ export function isArrayOfString(collection: any): collection is string[] { return Array.isArray(collection) && collection.length > 0 && typeof (collection[0]) === typeof (""); } +export const onKernelInfoUpdates: ((compositeKernel: CompositeKernel) => void)[] = []; export function ensureOrUpdateProxyForKernelInfo(kernelInfoProduced: contracts.KernelInfoProduced, compositeKernel: CompositeKernel) { - const uriToLookup = kernelInfoProduced.kernelInfo.remoteUri ?? kernelInfoProduced.kernelInfo.uri; + const uriToLookup = kernelInfoProduced.kernelInfo.uri ?? kernelInfoProduced.kernelInfo.remoteUri; if (uriToLookup) { let kernel = compositeKernel.findKernelByUri(uriToLookup); if (!kernel) { // add if (compositeKernel.host) { - Logger.default.info(`creating proxy for uri[${uriToLookup}]with info ${JSON.stringify(kernelInfoProduced)} `); + Logger.default.info(`creating proxy for uri[${uriToLookup}]with info ${JSON.stringify(kernelInfoProduced)}`); + // check for clash with `kernelInfo.localName` kernel = compositeKernel.host.connectProxyKernel(kernelInfoProduced.kernelInfo.localName, uriToLookup, kernelInfoProduced.kernelInfo.aliases); } else { throw new Error('no kernel host found'); @@ -138,11 +138,13 @@ export function ensureOrUpdateProxyForKernelInfo(kernelInfoProduced: contracts.K // patch updateKernelInfo(kernel.kernelInfo, kernelInfoProduced.kernelInfo); } + + for (const updater of onKernelInfoUpdates) { + updater(compositeKernel); + } } } - - export function isKernelInfoForProxy(kernelInfo: contracts.KernelInfo): boolean { const hasUri = !!kernelInfo.uri; const hasRemoteUri = !!kernelInfo.remoteUri; @@ -152,6 +154,7 @@ export function isKernelInfoForProxy(kernelInfo: contracts.KernelInfo): boolean export function updateKernelInfo(destination: contracts.KernelInfo, incoming: contracts.KernelInfo) { destination.languageName = incoming.languageName ?? destination.languageName; destination.languageVersion = incoming.languageVersion ?? destination.languageVersion; + destination.displayName = incoming.displayName; const supportedDirectives = new Set(); const supportedCommands = new Set(); diff --git a/src/microsoft-dotnet-interactive/src/contracts.ts b/src/microsoft-dotnet-interactive/src/contracts.ts index c4c0814999..6595336b5a 100644 --- a/src/microsoft-dotnet-interactive/src/contracts.ts +++ b/src/microsoft-dotnet-interactive/src/contracts.ts @@ -1,6 +1,6 @@ -// 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. - +// 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. + // Generated TypeScript interfaces and types. // --------------------------------------------- Kernel Commands @@ -122,7 +122,7 @@ export interface RequestValueInfos extends KernelCommand { } export interface SendEditableCode extends KernelCommand { - language: string; + kernelName: string; code: string; } @@ -169,6 +169,12 @@ export interface ErrorElement extends InteractiveDocumentOutputElement { stackTrace: Array; } +export interface DocumentKernelInfo { + name: string; + languageName?: string; + aliases: Array; +} + export interface NotebookParseRequest extends NotebookParseOrSerializeRequest { type: RequestType; rawData: Uint8Array; @@ -459,6 +465,7 @@ export interface KernelInfo { aliases: Array; languageName?: string; languageVersion?: string; + displayName: string; localName: string; uri: string; remoteUri?: string; @@ -527,25 +534,30 @@ export enum SubmissionType { Diagnose = "diagnose", } -export interface KernelEventEnvelope { - eventType: KernelEventType; - event: KernelEvent; - command?: KernelCommandEnvelope; - routingSlip?: string[]; -} - -export interface KernelCommandEnvelope { - token?: string; - id?: string; - commandType: KernelCommandType; - command: KernelCommand; - routingSlip?: string[]; -} - -export interface KernelEventEnvelopeObserver { - (eventEnvelope: KernelEventEnvelope): void; -} - -export interface KernelCommandEnvelopeHandler { - (eventEnvelope: KernelCommandEnvelope): Promise; -} \ No newline at end of file +export interface DocumentKernelInfoCollection { + defaultKernelName: string; + items: DocumentKernelInfo[]; +} + +export interface KernelEventEnvelope { + eventType: KernelEventType; + event: KernelEvent; + command?: KernelCommandEnvelope; + routingSlip?: string[]; +} + +export interface KernelCommandEnvelope { + token?: string; + id?: string; + commandType: KernelCommandType; + command: KernelCommand; + routingSlip?: string[]; +} + +export interface KernelEventEnvelopeObserver { + (eventEnvelope: KernelEventEnvelope): void; +} + +export interface KernelCommandEnvelopeHandler { + (eventEnvelope: KernelCommandEnvelope): Promise; +} diff --git a/src/microsoft-dotnet-interactive/src/kernel.ts b/src/microsoft-dotnet-interactive/src/kernel.ts index 0a89b48191..f9e3ce1b31 100644 --- a/src/microsoft-dotnet-interactive/src/kernel.ts +++ b/src/microsoft-dotnet-interactive/src/kernel.ts @@ -59,13 +59,14 @@ export class Kernel { return this._eventSubject.asObservable(); } - constructor(readonly name: string, languageName?: string, languageVersion?: string) { + constructor(readonly name: string, languageName?: string, languageVersion?: string, displayName?: string) { this._kernelInfo = { localName: name, languageName: languageName, aliases: [], uri: routingslip.createKernelUri(`kernel://local/${name}`), languageVersion: languageVersion, + displayName: displayName ?? name, supportedDirectives: [], supportedKernelCommands: [] }; diff --git a/src/microsoft-dotnet-interactive/src/proxyKernel.ts b/src/microsoft-dotnet-interactive/src/proxyKernel.ts index 0021dbe3a3..fe6f99d83a 100644 --- a/src/microsoft-dotnet-interactive/src/proxyKernel.ts +++ b/src/microsoft-dotnet-interactive/src/proxyKernel.ts @@ -11,8 +11,8 @@ import { KernelInvocationContext } from "./kernelInvocationContext"; export class ProxyKernel extends Kernel { - constructor(override readonly name: string, private readonly _sender: connection.IKernelCommandAndEventSender, private readonly _receiver: connection.IKernelCommandAndEventReceiver) { - super(name); + constructor(override readonly name: string, private readonly _sender: connection.IKernelCommandAndEventSender, private readonly _receiver: connection.IKernelCommandAndEventReceiver, languageName?: string, languageVersion?: string) { + super(name, languageName, languageVersion); this.kernelType = KernelType.proxy; } diff --git a/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts b/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts index ff34fe6cdb..1c3137508d 100644 --- a/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts +++ b/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts @@ -96,7 +96,7 @@ function setDataRows(container: HTMLElement, rows: VariableGridRow[]): Displayed const dataShare = document.createElement('td'); dataShare.classList.add('share-data'); - dataShare.innerHTML = ``; + dataShare.innerHTML = ``; dataRow.appendChild(dataShare); displayedRows.push({ diff --git a/src/microsoft-dotnet-interactive/tests/frontEndHost.test.ts b/src/microsoft-dotnet-interactive/tests/frontEndHost.test.ts index da75b25e60..9c2c5ef195 100644 --- a/src/microsoft-dotnet-interactive/tests/frontEndHost.test.ts +++ b/src/microsoft-dotnet-interactive/tests/frontEndHost.test.ts @@ -64,6 +64,7 @@ describe("frontEndHost", () => { kernelInfo: { aliases: [], + displayName: 'testKernel', localName: 'testKernel', supportedDirectives: [], supportedKernelCommands: [{ name: 'RequestKernelInfo' }], @@ -80,6 +81,7 @@ describe("frontEndHost", () => { { aliases: ['js'], languageName: 'JavaScript', + displayName: 'javascript', localName: 'javascript', supportedDirectives: [], supportedKernelCommands: @@ -111,6 +113,7 @@ describe("frontEndHost", () => { aliases: [], languageName: 'SQL', languageVersion: '10', + displayName: 'SQL', supportedDirectives: [], supportedKernelCommands: [ { @@ -137,6 +140,7 @@ describe("frontEndHost", () => { languageVersion: '10', localName: 'sql', remoteUri: 'kernel://remote/sql', + displayName: 'SQL', supportedDirectives: [], supportedKernelCommands: [{ name: 'RequestKernelInfo' }, @@ -160,6 +164,7 @@ describe("frontEndHost", () => { localName: 'sql', uri: 'kernel://remote/sql', aliases: [], + displayName: 'SQL', supportedDirectives: [], supportedKernelCommands: [ { @@ -178,6 +183,7 @@ describe("frontEndHost", () => { aliases: [], languageName: 'SQL', languageVersion: '10', + displayName: 'SQL', supportedDirectives: [], supportedKernelCommands: [ { @@ -204,6 +210,7 @@ describe("frontEndHost", () => { languageVersion: '10', localName: 'sql', remoteUri: 'kernel://remote/sql', + displayName: 'SQL', supportedDirectives: [], supportedKernelCommands: [{ name: 'RequestKernelInfo' }, diff --git a/src/microsoft-dotnet-interactive/tests/kernelHost.test.ts b/src/microsoft-dotnet-interactive/tests/kernelHost.test.ts index 5a8560e3f5..a0c5e056e6 100644 --- a/src/microsoft-dotnet-interactive/tests/kernelHost.test.ts +++ b/src/microsoft-dotnet-interactive/tests/kernelHost.test.ts @@ -40,6 +40,7 @@ describe("kernelHost", kernelInfo: { aliases: [], + displayName: 'vscode', localName: 'vscode', supportedDirectives: [], supportedKernelCommands: [{ name: 'RequestKernelInfo' }], @@ -56,6 +57,7 @@ describe("kernelHost", { aliases: ['test1', 'test2'], languageName: 'customLanguage', + displayName: 'test', localName: 'test', supportedDirectives: [], supportedKernelCommands: [{ name: 'RequestKernelInfo' }, { name: 'customCommand' }], diff --git a/src/microsoft-dotnet-interactive/tests/kernelInfo.test.ts b/src/microsoft-dotnet-interactive/tests/kernelInfo.test.ts index bed5f03830..a5fc9c34e3 100644 --- a/src/microsoft-dotnet-interactive/tests/kernelInfo.test.ts +++ b/src/microsoft-dotnet-interactive/tests/kernelInfo.test.ts @@ -31,6 +31,7 @@ describe("kernelInfo", () => { languageName: undefined, languageVersion: undefined, localName: 'root', + displayName: 'root', supportedDirectives: [], supportedKernelCommands: [{ name: 'RequestKernelInfo' }], uri: 'kernel://local/root' @@ -39,6 +40,7 @@ describe("kernelInfo", () => { aliases: ['child1Js'], languageName: 'JavaScript', languageVersion: undefined, + displayName: 'child1', localName: 'child1', supportedDirectives: [], supportedKernelCommands: @@ -53,6 +55,7 @@ describe("kernelInfo", () => { aliases: ['child2Js'], languageName: 'JavaScript', languageVersion: undefined, + displayName: 'child2', localName: 'child2', supportedDirectives: [], supportedKernelCommands: @@ -106,6 +109,7 @@ describe("kernelInfo", () => { aliases: ['child1Js'], languageName: 'JavaScript', languageVersion: undefined, + displayName: 'child1', localName: 'child1', supportedDirectives: [], supportedKernelCommands: @@ -171,6 +175,7 @@ describe("kernelInfo", () => { aliases: [], languageName: 'customLanguage', languageVersion: undefined, + displayName: 'child1', localName: 'child1', supportedDirectives: [], supportedKernelCommands: @@ -217,6 +222,7 @@ describe("kernelInfo", () => { aliases: [], languageName: 'customLanguage', languageVersion: undefined, + displayName: 'child1', localName: 'child1', supportedDirectives: [], supportedKernelCommands: [{ name: 'RequestKernelInfo' }, { name: 'SubmitCode' }], @@ -292,6 +298,7 @@ describe("kernelInfo", () => { languageName: 'customLanguage', languageVersion: undefined, localName: 'child2', + displayName: 'child2', supportedDirectives: [], supportedKernelCommands: [{ name: 'RequestKernelInfo' }], uri: 'kernel://local/root/child2' diff --git a/src/microsoft-dotnet-interactive/tests/proxykernel.test.ts b/src/microsoft-dotnet-interactive/tests/proxykernel.test.ts index 854a77e311..ec13c1580b 100644 --- a/src/microsoft-dotnet-interactive/tests/proxykernel.test.ts +++ b/src/microsoft-dotnet-interactive/tests/proxykernel.test.ts @@ -225,6 +225,7 @@ describe("proxyKernel", () => { uri: 'kernel://local/remoteKernel', languageName: "gsharp", languageVersion: "1.2.3", + displayName: "G#", supportedKernelCommands: [{ name: "customCommand1" }, { name: "customCommand2" }], supportedDirectives: [] } @@ -255,6 +256,7 @@ describe("proxyKernel", () => { aliases: [], languageName: 'gsharp', languageVersion: '1.2.3', + displayName: 'G#', localName: 'proxy', remoteUri: 'kernel://local/remoteKernel', supportedDirectives: [], @@ -266,5 +268,3 @@ describe("proxyKernel", () => { }); }); }); - - From 5ab8ca0110b37ac0738248101222c872dc9d36dc Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Wed, 23 Nov 2022 12:28:40 +0000 Subject: [PATCH 13/14] use button fix aria label attribute --- .../src/variableExplorer.ts | 11 +++++++++-- .../src/webview/variableGrid.ts | 18 ++++++++++++------ .../src/webview/variableGridInterfaces.ts | 3 ++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/dotnet-interactive-vscode-common/src/variableExplorer.ts b/src/dotnet-interactive-vscode-common/src/variableExplorer.ts index 19e8766d63..1299f97404 100644 --- a/src/dotnet-interactive-vscode-common/src/variableExplorer.ts +++ b/src/dotnet-interactive-vscode-common/src/variableExplorer.ts @@ -78,6 +78,9 @@ class WatchWindowTableViewProvider implements vscode.WebviewViewProvider { }; this.webview.onDidReceiveMessage(message => { const x = message; + if (message.command === 'shareValueWith') { + vscode.commands.executeCommand('polyglot-notebook.shareValueWith', message.variableInfo); + } }); // only load this once const apiFileUri = this.webview.asWebviewUri(vscode.Uri.file(path.join(this.extensionPath, 'resources', 'variableGrid.js'))); @@ -117,7 +120,10 @@ class WatchWindowTableViewProvider implements vscode.WebviewViewProvider { button[hover] { background-color: var(--vscode-button-hoverBackground); } - + button.share { + background-color: transparent; + border: 0px; + } .name-column { width: 20%; } @@ -249,7 +255,8 @@ class WatchWindowTableViewProvider implements vscode.WebviewViewProvider { rows.push({ name: valueName, value: valueValue, - kernel: displayName, + kernelDisplayName: displayName, + kernelName: kernel.name, link: commandUrl, }); } catch (e) { diff --git a/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts b/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts index 1c3137508d..589b011e2f 100644 --- a/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts +++ b/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts @@ -26,7 +26,7 @@ window.addEventListener('DOMContentLoaded', () => { row.element.style.display = 'none'; if (contains(row.row.name, filterElement.value) || contains(row.row.value, filterElement.value) || - contains(row.row.kernel, filterElement.value)) { + contains(row.row.kernelDisplayName, filterElement.value)) { row.element.style.display = ''; } } @@ -91,12 +91,12 @@ function setDataRows(container: HTMLElement, rows: VariableGridRow[]): Displayed dataRow.appendChild(dataValue); const dataKernel = document.createElement('td'); - dataKernel.innerText = truncateValue(row.kernel); + dataKernel.innerText = truncateValue(row.kernelDisplayName); dataRow.appendChild(dataKernel); const dataShare = document.createElement('td'); dataShare.classList.add('share-data'); - dataShare.innerHTML = ``; + dataShare.innerHTML = ``; dataRow.appendChild(dataShare); displayedRows.push({ @@ -124,6 +124,12 @@ function truncateValue(value: string): string { // @ts-ignore const vscode = acquireVsCodeApi(); -function doTheThing(kernelName: string, valueName: string) { - vscode.postMessage({ kernelName, valueName }); -} +(window).shareValueWith = function (sourceKernelName: string, valueName: string) { + vscode.postMessage({ + command: 'shareValueWith', + variableInfo: { + kernelName: sourceKernelName, + valueName: valueName + } + }); +}; diff --git a/src/microsoft-dotnet-interactive/src/webview/variableGridInterfaces.ts b/src/microsoft-dotnet-interactive/src/webview/variableGridInterfaces.ts index d2a583a7f3..da49c61635 100644 --- a/src/microsoft-dotnet-interactive/src/webview/variableGridInterfaces.ts +++ b/src/microsoft-dotnet-interactive/src/webview/variableGridInterfaces.ts @@ -4,6 +4,7 @@ export interface VariableGridRow { name: string; value: string; - kernel: string; + kernelDisplayName: string; + kernelName: string; link: string; } From 4cc470effb98c68f8420b9f429492a7ea789657c Mon Sep 17 00:00:00 2001 From: Diego Colombo Date: Wed, 23 Nov 2022 16:14:02 +0000 Subject: [PATCH 14/14] refactor --- .../src/variableExplorer.ts | 8 ++++---- .../src/webview/variableGrid.ts | 11 ++++------- .../src/webview/variableGridInterfaces.ts | 5 +++++ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/dotnet-interactive-vscode-common/src/variableExplorer.ts b/src/dotnet-interactive-vscode-common/src/variableExplorer.ts index 1299f97404..de904535b2 100644 --- a/src/dotnet-interactive-vscode-common/src/variableExplorer.ts +++ b/src/dotnet-interactive-vscode-common/src/variableExplorer.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { ClientMapper } from './clientMapper'; import * as contracts from './dotnet-interactive/contracts'; -import { VariableGridRow } from './dotnet-interactive/webview/variableGridInterfaces'; +import { VariableGridRow, VariableInfo } from './dotnet-interactive/webview/variableGridInterfaces'; import * as utilities from './utilities'; import * as versionSpecificFunctions from '../versionSpecificFunctions'; import { DisposableSubscription } from './dotnet-interactive/disposables'; @@ -17,7 +17,7 @@ function debounce(callback: () => void) { } export function registerVariableExplorer(context: vscode.ExtensionContext, clientMapper: ClientMapper) { - context.subscriptions.push(vscode.commands.registerCommand('polyglot-notebook.shareValueWith', async (variableInfo: { kernelName: string, valueName: string } | undefined) => { + context.subscriptions.push(vscode.commands.registerCommand('polyglot-notebook.shareValueWith', async (variableInfo: VariableInfo | undefined) => { const activeNotebookEditor = vscode.window.activeNotebookEditor; if (variableInfo && activeNotebookEditor) { const notebookDocument = versionSpecificFunctions.getNotebookDocumentFromEditor(activeNotebookEditor); @@ -25,13 +25,13 @@ export function registerVariableExplorer(context: vscode.ExtensionContext, clien if (client) { const kernelSelectorOptions = kernelSelectorUtilities.getKernelSelectorOptions(client.kernel, notebookDocument, contracts.SendValueType); const kernelDisplayValues = kernelSelectorOptions.map(k => k.displayValue); - const selectedKernelDisplayName = await vscode.window.showQuickPick(kernelDisplayValues, { title: `Share value [${variableInfo.valueName}] from [${variableInfo.kernelName}] to ...` }); + const selectedKernelDisplayName = await vscode.window.showQuickPick(kernelDisplayValues, { title: `Share value [${variableInfo.valueName}] from [${variableInfo.sourceKernelName}] to ...` }); if (selectedKernelDisplayName) { const targetKernelIndex = kernelDisplayValues.indexOf(selectedKernelDisplayName); if (targetKernelIndex >= 0) { const targetKernelSelectorOption = kernelSelectorOptions[targetKernelIndex]; // ends with newline to make adding code easier - const code = `#!share --from ${variableInfo.kernelName} ${variableInfo.valueName}\n`; + const code = `#!share --from ${variableInfo.sourceKernelName} ${variableInfo.valueName}\n`; const command: contracts.SendEditableCode = { kernelName: targetKernelSelectorOption.kernelName, code, diff --git a/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts b/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts index 589b011e2f..530408bc2e 100644 --- a/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts +++ b/src/microsoft-dotnet-interactive/src/webview/variableGrid.ts @@ -1,7 +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. -import { VariableGridRow } from './variableGridInterfaces'; +import { VariableGridRow, VariableInfo } from './variableGridInterfaces'; interface DisplayedVariableGridRow { row: VariableGridRow; @@ -96,7 +96,7 @@ function setDataRows(container: HTMLElement, rows: VariableGridRow[]): Displayed const dataShare = document.createElement('td'); dataShare.classList.add('share-data'); - dataShare.innerHTML = ``; + dataShare.innerHTML = ``; dataRow.appendChild(dataShare); displayedRows.push({ @@ -124,12 +124,9 @@ function truncateValue(value: string): string { // @ts-ignore const vscode = acquireVsCodeApi(); -(window).shareValueWith = function (sourceKernelName: string, valueName: string) { +(window).shareValueWith = function (variableInfo: VariableInfo) { vscode.postMessage({ command: 'shareValueWith', - variableInfo: { - kernelName: sourceKernelName, - valueName: valueName - } + variableInfo: variableInfo }); }; diff --git a/src/microsoft-dotnet-interactive/src/webview/variableGridInterfaces.ts b/src/microsoft-dotnet-interactive/src/webview/variableGridInterfaces.ts index da49c61635..8d488acdd8 100644 --- a/src/microsoft-dotnet-interactive/src/webview/variableGridInterfaces.ts +++ b/src/microsoft-dotnet-interactive/src/webview/variableGridInterfaces.ts @@ -8,3 +8,8 @@ export interface VariableGridRow { kernelName: string; link: string; } + +export interface VariableInfo { + sourceKernelName: string; + valueName: string; +}