From 5fe4981756372a8167327f572c852a4bedb6ba5b Mon Sep 17 00:00:00 2001 From: Jon Sequeira Date: Fri, 3 Feb 2023 17:19:10 -0800 Subject: [PATCH] improve dictionary and sequence rendering, open top level of treeview --- .../HtmlFormatterTests.Objects_TableView.cs | 1 + .../HtmlFormatterTests.Objects_TreeView.cs | 66 +- .../HtmlFormatterTests.Sequences.cs | 603 ++++++++++++------ ...ractive.Formatting.Tests.v3.ncrunchproject | 5 + .../PocketViewTests.cs | 18 +- .../HtmlFormatter{T}.cs | 56 +- .../PlainTextFormatter.cs | 2 - .../PocketView.cs | 19 +- .../TypeExtensions.cs | 92 ++- .../LanguageKernelFormattingTests.cs | 6 +- .../ValueSharing/JavaScriptValueDeclarer.cs | 2 - 11 files changed, 561 insertions(+), 309 deletions(-) diff --git a/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Objects_TableView.cs b/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Objects_TableView.cs index e875fa91eb..b1bc7b3997 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Objects_TableView.cs +++ b/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Objects_TableView.cs @@ -16,6 +16,7 @@ public partial class HtmlFormatterTests { public class Objects_TableView : FormatterTestBase { + // FIX: (Objects_TableView) delete? [Fact] public void Formatters_are_generated_on_the_fly_when_HTML_mime_type_is_requested() { diff --git a/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Objects_TreeView.cs b/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Objects_TreeView.cs index 0888b0cd3f..9f8c66859e 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Objects_TreeView.cs +++ b/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Objects_TreeView.cs @@ -32,7 +32,7 @@ public void Formatters_are_generated_on_the_fly_for_anonymous_types() .RemoveStyleElement() .Should() .BeEquivalentHtmlTo($$""" -
+
{{Tags.SummaryTextBegin}}{ a = 123 }{{Tags.SummaryTextEnd}}
@@ -112,8 +112,8 @@ public void It_formats_objects_as_tree() html .Should() .BeEquivalentHtmlTo($""" -
- {Tags.SummaryTextBegin}Microsoft.DotNet.Interactive.Formatting.Tests.EntityId{Tags.SummaryTextEnd} +
+ {Tags.SummaryTextBegin}{typeof(EntityId).FullName}{Tags.SummaryTextEnd}
@@ -166,7 +166,7 @@ public void It_formats_value_tuples_as_tree() .RemoveStyleElement() .Should() .BeEquivalentHtmlTo($""" -
+
{Tags.SummaryTextBegin}(123, hello){Tags.SummaryTextEnd}
@@ -206,13 +206,9 @@ public void Object_properties_are_formatted_using_nested_trees() formatter.Format(instance, writer); - writer.ToString() - .RemoveStyleElement() - .Should() - .BeEquivalentHtmlTo( - $$""" -
- {{Tags.SummaryTextBegin}}{ A = 123, B = { BA = 456 } }{{Tags.SummaryTextEnd}} + var expected = $$""" +
+ { A = 123, B = { BA = 456 } }
@@ -230,8 +226,8 @@ public void Object_properties_are_formatted_using_nested_trees()
B -
- {{Tags.SummaryTextBegin}}{ BA = 456 }{{Tags.SummaryTextEnd}} +
+ { BA = 456 }
@@ -256,8 +252,14 @@ public void Object_properties_are_formatted_using_nested_trees()
- - """); + """; + + var actual = writer.ToString().RemoveStyleElement(); + + actual + .Should() + .BeEquivalentHtmlTo( + expected); } [Fact] @@ -275,12 +277,13 @@ public void Scalar_sequence_properties_are_formatted_using_plain_text_formatter( formatter.Format(instance, writer); - writer.ToString() - .RemoveStyleElement() - .Should() - .BeEquivalentHtmlTo( - $$""" -
+ var html = writer.ToString().RemoveStyleElement(); + + html + .Should() + .BeEquivalentHtmlTo( + $$""" +
{{Tags.SummaryTextBegin}}{ PropertyA = 123, PropertyB = System.Linq.Enumerable+RangeIterator }{{Tags.SummaryTextEnd}}
@@ -311,25 +314,6 @@ public void Scalar_sequence_properties_are_formatted_using_plain_text_formatter( """); } - [Fact] - public void Collection_properties_are_formatted_using_plain_text_formatting() - { - var writer = new StringWriter(); - - var instance = new - { - PropertyA = Enumerable.Range(1, 3) - }; - - var formatter = HtmlFormatter.GetPreferredFormatterFor(instance.GetType()); - - formatter.Format(instance, writer); - - writer.ToString() - .Should() - .Contain("[ 1, 2, 3 ]"); - } - [Fact] public void It_displays_exceptions_thrown_by_properties_in_the_property_value_cell() { @@ -349,7 +333,7 @@ public void It_displays_exceptions_thrown_by_properties_in_the_property_value_ce
NotOk -
+
System.Exception: not ok """.Crunch()); } diff --git a/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Sequences.cs b/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Sequences.cs index 7f9250c5d7..38a532bcc7 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Sequences.cs +++ b/src/Microsoft.DotNet.Interactive.Formatting.Tests/HtmlFormatterTests.Sequences.cs @@ -31,20 +31,76 @@ public void It_formats_sequences_as_tables_with_an_index_on_the_y_axis() formatter.Format(instance, writer); - writer.ToString() - .RemoveStyleElement() - .Should() - .BeEquivalentHtmlTo( - @$" - - - - - - - - -
indexTypeNameId
0entity one123
1entity two456
"); + var html = writer.ToString().RemoveStyleElement(); + + html.Should() + .BeEquivalentHtmlTo( + """ + + + + + + + + + + + + + + + + + +
indexvalue
0 +
+ Microsoft.DotNet.Interactive.Formatting.Tests.EntityId + +
+ + + + + + + + + + + + + + +
TypeNameentity one
Id123
+
+
+
1 +
+ Microsoft.DotNet.Interactive.Formatting.Tests.EntityId + +
+ + + + + + + + + + + + + + +
TypeNameentity two
Id456
+
+
+
+ """); } [Fact] @@ -72,7 +128,7 @@ public void It_formats_sequence_properties_using_plain_text_formatting() } [Fact] - public void It_formats_generic_dictionaries_that_arent_non_generic_as_tables_with_the_key_on_the_y_axis() + public void It_formats_generic_dictionaries_with_tree_views_in_the_value_column() { var writer = new StringWriter(); @@ -86,15 +142,80 @@ public void It_formats_generic_dictionaries_that_arent_non_generic_as_tables_wit formatter.Format(instance, writer); - writer.ToString() - .RemoveStyleElement() - .Should() - .BeEquivalentHtmlTo( - "
keyTypeNameId
firstentity one123
secondentity two456
"); + var html = writer.ToString().RemoveStyleElement(); + + html.Should() + .BeEquivalentHtmlTo( + """ + + + + + + + + + + + + + + + + + +
keyvalue
first +
+ Microsoft.DotNet.Interactive.Formatting.Tests.EntityId + +
+ + + + + + + + + + + + + + +
TypeNameentity one
Id123
+
+
+
second +
+ Microsoft.DotNet.Interactive.Formatting.Tests.EntityId + +
+ + + + + + + + + + + + + + +
TypeNameentity two
Id456
+
+
+
+ """); } [Fact] - public void It_formats_non_generic_dictionaries_that_arent_generic_as_tables_with_the_key_on_the_y_axis() + public void It_formats_non_generic_dictionaries_with_tree_views_in_the_value_column() { var writer = new StringWriter(); @@ -112,15 +233,72 @@ public void It_formats_non_generic_dictionaries_that_arent_generic_as_tables_wit .RemoveStyleElement() .Should() .BeEquivalentHtmlTo( - $@" - - - - - - - -
keyTypeNameId
firstentity one123
secondentity two456
"); + """ + + + + + + + + + + + + + + + + + +
keyvalue
first +
+ Microsoft.DotNet.Interactive.Formatting.Tests.EntityId + +
+ + + + + + + + + + + + + + +
TypeNameentity one
Id123
+
+
+
second +
+ Microsoft.DotNet.Interactive.Formatting.Tests.EntityId + +
+ + + + + + + + + + + + + + +
TypeNameentity two
Id456
+
+
+
+ """); } [Fact] @@ -175,17 +353,11 @@ public void It_formats_string_arrays_correctly() .RemoveStyleElement() .Should() .BeEquivalentHtmlTo( - $@" - - - - - - - - - -
indexvalue
0apple
1banana
2cherry
"); + """ +
+
[ apple, banana, cherry ]
+
+ """); } [Fact] @@ -198,17 +370,11 @@ public void It_formats_ordered_enumerables_correctly() var html = sorted.ToDisplayString("text/html").RemoveStyleElement(); html.Should() - .BeEquivalentHtmlTo($@" - - - - - - - - - -
indexvalue
0kiwi
1apple
2plantain
"); + .BeEquivalentHtmlTo(""" +
+
[ kiwi, apple, plantain ]
+
+ """); } [Fact] @@ -218,7 +384,12 @@ public void Empty_sequences_are_indicated() var html = list.ToDisplayString("text/html").RemoveStyleElement(); - html.Should().BeEquivalentHtmlTo("(empty)"); + html.Should().BeEquivalentHtmlTo( + """ +
+
[  ]
+
+ """); } [Fact] @@ -249,7 +420,7 @@ public void Formatter_truncates_expansion_of_ICollection() formatted.Should().Contain("number 1"); formatted.Should().Contain("number 4"); formatted.Should().NotContain("number 5"); - formatted.Should().Contain("(6 more)"); + formatted.Should().Contain("(6 more)"); } [Fact] @@ -284,7 +455,7 @@ public void Formatter_truncates_expansion_of_IEnumerable() var formatted = InfiniteSequence().ToDisplayString(formatter); formatted.Should().Contain("number 9"); - formatted.Should().Contain("... (more)"); + formatted.Should().Contain("... (more)"); static IEnumerable InfiniteSequence() { @@ -387,17 +558,11 @@ public void ReadOnlyMemory_of_int_is_formatted_like_a_int_array() writer.ToString().RemoveStyleElement() .Should() .BeEquivalentHtmlTo( - $@" - - - - - - - - - -
indexvalue
0{Tags.PlainTextBegin}7{Tags.PlainTextEnd}
1{Tags.PlainTextBegin}8{Tags.PlainTextEnd}
2{Tags.PlainTextBegin}9{Tags.PlainTextEnd}
"); + """ +
+
[ 7, 8, 9 ]
+
+ """); } [Fact] @@ -490,84 +655,109 @@ public void All_properties_are_shown_when_sequences_contain_different_types() { var objects = new object[] { - 1, - (2, "two"), + 1, + (2, "two"), Enumerable.Range(1, 3), new { name = "apple", color = "green" } }; - var result= objects.ToDisplayString("text/html").RemoveStyleElement(); + var result = objects.ToDisplayString("text/html").RemoveStyleElement(); result .Should() .BeEquivalentHtmlTo( - $@" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- index - - type - valueItem1Item2namecolor
0 - - System.Int32 - - {Tags.PlainTextBegin}1{Tags.PlainTextEnd}
1 - - System.ValueTuple<System.Int32,System.String> - - {Tags.PlainTextBegin}2{Tags.PlainTextEnd}two
2 - - System.Linq.Enumerable+RangeIterator - - {Tags.PlainTextBegin}[ 1, 2, 3 ]{Tags.PlainTextEnd}
3(anonymous)applegreen
"); + """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
indextypevalue
0System.Int32 + +
+
1
+
+
1System.ValueTuple<System.Int32,System.String> + +
+ (2, two) +
+ + + + + + + + + + + + + + +
Item1 +
+
2
+
+
Item2two
+
+
+
2System.Linq.Enumerable+RangeIterator + +
+
[ 1, 2, 3 ]
+
+
3(anonymous) +
+ { name = apple, color = green } +
+ + + + + + + + + + + + + + +
nameapple
colorgreen
+
+
+
+ """); } [Fact] @@ -585,76 +775,81 @@ public void Respective_HTML_formatters_are_used_when_sequences_contain_different new [] { 7, 8, 9 } }; - var result = objects.ToDisplayString("text/html").RemoveStyleElement(); + var html = objects.ToDisplayString("text/html").RemoveStyleElement(); - result - .Should() + html.Should() .BeEquivalentHtmlTo( - $@" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- index - - type - valueItem1Item2
0(anonymous) - {{ name = apple, color = green }} -
1 - - System.ValueTuple<System.Int32,System.String> - - - {Tags.PlainTextBegin}123{Tags.PlainTextEnd} - two
2 - - System.Int32 - - - {Tags.PlainTextBegin}456{Tags.PlainTextEnd} -
3 - - System.Int32[] - - - {Tags.PlainTextBegin}[ 7, 8, 9 ]{Tags.PlainTextEnd} -
"); + $$""" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
indextypevalue
0(anonymous){ name = apple, color = green }
1System.ValueTuple<System.Int32,System.String> + +
+ (123, two) +
+ + + + + + + + + + + + + + +
Item1 +
+
123
+
+
Item2two
+
+
+
2System.Int32 + +
+
456
+
+
3System.Int32[] + +
+
[ 7, 8, 9 ]
+
+
+ """); } class SomeDictUsingInterfaceImpls : IDictionary diff --git a/src/Microsoft.DotNet.Interactive.Formatting.Tests/Microsoft.DotNet.Interactive.Formatting.Tests.v3.ncrunchproject b/src/Microsoft.DotNet.Interactive.Formatting.Tests/Microsoft.DotNet.Interactive.Formatting.Tests.v3.ncrunchproject index 67a3727f41..fd562ae7f5 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting.Tests/Microsoft.DotNet.Interactive.Formatting.Tests.v3.ncrunchproject +++ b/src/Microsoft.DotNet.Interactive.Formatting.Tests/Microsoft.DotNet.Interactive.Formatting.Tests.v3.ncrunchproject @@ -1,5 +1,10 @@  10000 + + + Microsoft.DotNet.Interactive.Formatting.Tests.HtmlFormatterTests+Objects_TableView + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.Formatting.Tests/PocketViewTests.cs b/src/Microsoft.DotNet.Interactive.Formatting.Tests/PocketViewTests.cs index 35e410b6b2..e83b53c4bb 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting.Tests/PocketViewTests.cs +++ b/src/Microsoft.DotNet.Interactive.Formatting.Tests/PocketViewTests.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.Collections.Generic; using FluentAssertions; using System.Linq; @@ -251,7 +252,7 @@ public void attributes_can_be_written_without_a_value() output.Should().Be("
heading

some content

"); } - + [Fact] public void multiple_attributes_can_be_written_without_a_value() { @@ -260,6 +261,21 @@ public void multiple_attributes_can_be_written_without_a_value() output.Should().Be("
heading

some content

"); } + [Fact] + public void Attributes_without_a_value_can_be_combined_with_named_attributes() + { + PocketView view = details["open", @class: "the-class"](summary("expand for more"), div("more")); + + var html = view.ToString(); + + html.Should().Be(""" +
+ expand for more +
more
+
+ """.Crunch()); + } + [Fact] public void Method_and_property_calls_return_the_same_result_when_no_attributes_are_present() { diff --git a/src/Microsoft.DotNet.Interactive.Formatting/HtmlFormatter{T}.cs b/src/Microsoft.DotNet.Interactive.Formatting/HtmlFormatter{T}.cs index 3a22a49fcf..3402250a22 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting/HtmlFormatter{T}.cs +++ b/src/Microsoft.DotNet.Interactive.Formatting/HtmlFormatter{T}.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text.Encodings.Web; using Microsoft.AspNetCore.Html; using Microsoft.DotNet.Interactive.Utility; using static Microsoft.DotNet.Interactive.Formatting.PocketViewTags; @@ -103,32 +102,36 @@ internal static HtmlFormatter CreateTableFormatterForAnyEnumerable() { Func getKeys = null; Func getValues = instance => (IEnumerable)instance; - - var dictType = - typeof(T).GetAllInterfaces() - .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)) - ?? - typeof(T).GetAllInterfaces() - .FirstOrDefault(i => i == typeof(IDictionary)); - - if (dictType is not null) + bool flatten = false; + + if (typeof(T).IsDictionary( + out var getKeys1, + out var getValues1, + out var keyType1, + out var valueType1)) + { + getKeys = instance => getKeys1(instance); + getValues = instance => getValues1(instance); + } + else { - var keysProperty = dictType.GetProperty("Keys"); - getKeys = instance => (IEnumerable)keysProperty.GetValue(instance, null); + var elementType = typeof(T).GetElementTypeIfEnumerable(); - var valuesProperty = dictType.GetProperty("Values"); - getValues = instance => (IEnumerable)valuesProperty.GetValue(instance, null); + if (elementType?.IsScalar() is true) + { + flatten = true; + } } - return new HtmlFormatter(BuildTable); + return new HtmlFormatter((value, context) => BuildTable(value, context, flatten)); - bool BuildTable(T source, FormatContext context) + bool BuildTable(T source, FormatContext context, bool summarize) { context.RequireDefaultStyles(); using var _ = context.IncrementTableDepth(); - if (context.TableDepth > 1 || !context.AllowRecursion) + if (summarize || !context.AllowRecursion) { HtmlFormatter.FormatAndStyleAsPlainText(source, context); return true; @@ -156,6 +159,8 @@ bool BuildTable(T source, FormatContext context) { IDictionary keysAndValues; + // FIX: (CreateTableFormatterForAnyEnumerable) +#if false if (value is { } && Formatter.GetPreferredFormatterFor(value.GetType(), HtmlFormatter.MimeType) is { } formatter && formatter.Type == typeof(object)) @@ -168,6 +173,9 @@ bool BuildTable(T source, FormatContext context) { keysAndValues = NonDestructurer.Instance.Destructure(value); } +#else + keysAndValues = NonDestructurer.Instance.Destructure(value); +#endif if (value is not null) { @@ -296,9 +304,6 @@ static bool BuildTreeView(T source, FormatContext context, MemberAccessor[] m { context.RequireDefaultStyles(); - using var _ = context.IncrementTableDepth(); - using var __ = context.IncrementDepth(); - if (!context.AllowRecursion) { HtmlFormatter.FormatAndStyleAsPlainText(source, context); @@ -314,7 +319,16 @@ static bool BuildTreeView(T source, FormatContext context, MemberAccessor[] m formatter.Format(source, context); }); - view = details[@class: "dni-treeview"]( + var attributes = new HtmlAttributes(); + + if (context.Depth < 2) + { + attributes.Add("open", "open"); + } + + attributes.AddCssClass("dni-treeview"); + + view = details[attributes]( summary( span[@class: "dni-code-hint"](code)), div( diff --git a/src/Microsoft.DotNet.Interactive.Formatting/PlainTextFormatter.cs b/src/Microsoft.DotNet.Interactive.Formatting/PlainTextFormatter.cs index a52a6e25c3..61f6023e1f 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting/PlainTextFormatter.cs +++ b/src/Microsoft.DotNet.Interactive.Formatting/PlainTextFormatter.cs @@ -319,8 +319,6 @@ bool FormatTuple(T target, FormatContext context) var formatter = GetDefaultFormatterForAnyObject(type); return formatter.Format(obj, context); }) - - }; private static string IndentAtNewLines(this string s, FormatContext context) => diff --git a/src/Microsoft.DotNet.Interactive.Formatting/PocketView.cs b/src/Microsoft.DotNet.Interactive.Formatting/PocketView.cs index e9366d7dbd..96984e1b82 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting/PocketView.cs +++ b/src/Microsoft.DotNet.Interactive.Formatting/PocketView.cs @@ -143,27 +143,28 @@ public override bool TryGetIndex( object[] values, out object result) { - var argumentNameIndex = 0; + var offset = values.Length - binder.CallInfo.ArgumentNames.Count; for (var i = 0; i < values.Length; i++) { - var att = values[i]; + var value = values[i]; - if (att is IDictionary dict) + if (value is IDictionary dict) { HtmlAttributes.MergeWith(dict); } else { - if (binder.CallInfo.ArgumentNames.Count > 0) + if (i >= offset) { var key = binder.CallInfo - .ArgumentNames - .ElementAt(argumentNameIndex++) - .Replace("_", "-"); - HtmlAttributes[key] = values[i]; + .ArgumentNames + .ElementAt(i - offset) + .Replace("_", "-"); + + HtmlAttributes[key] = value; } - else if (att is string s) + else if (value is string s) { HtmlAttributes[s] = null; } diff --git a/src/Microsoft.DotNet.Interactive.Formatting/TypeExtensions.cs b/src/Microsoft.DotNet.Interactive.Formatting/TypeExtensions.cs index edac55217a..632a064beb 100644 --- a/src/Microsoft.DotNet.Interactive.Formatting/TypeExtensions.cs +++ b/src/Microsoft.DotNet.Interactive.Formatting/TypeExtensions.cs @@ -51,26 +51,6 @@ public static string MemberName(this Expression> expr throw new ArgumentException($"Expression {expression} does not specify a member."); } - public static IEnumerable GetMembers( - this Type type, - params Expression>[] forProperties) - { - var allMembers = typeof(T).GetMembersToFormat().ToArray(); - - if (forProperties is null || !forProperties.Any()) - { - return allMembers; - } - - return - forProperties - .Select(p => - { - var memberName = p.MemberName(); - return allMembers.Single(m => m.Name == memberName); - }); - } - public static MemberAccessor[] GetMemberAccessors(this IEnumerable forMembers) => forMembers .Select(MemberAccessor.CreateMemberAccessor) @@ -217,7 +197,7 @@ internal static Type GetElementTypeIfEnumerable(this Type type) Type enumerableInterface; - if (type.IsEnumerable()) + if (type.IsEnumerable() ) { enumerableInterface = type; } @@ -233,11 +213,23 @@ internal static Type GetElementTypeIfEnumerable(this Type type) return null; } - return enumerableInterface.GenericTypeArguments switch + if (enumerableInterface.GenericTypeArguments is { Length: 1 } genericTypeArguments) { - { Length: 1 } genericTypeArguments => genericTypeArguments[0], - _ => null - }; + return genericTypeArguments[0]; + } + + var enumerableTInterfaces = enumerableInterface + .GetInterfaces() + .Where(i => i.IsGenericType) + .Where(i => i.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + .ToArray(); + + if (enumerableTInterfaces is { Length: 1 } x) + { + return x[0].GenericTypeArguments[0]; + } + + return null; } internal static bool IsEnumerable(this Type type) @@ -246,10 +238,58 @@ internal static bool IsEnumerable(this Type type) { return false; } - + return type.IsArray || typeof(IEnumerable).IsAssignableFrom(type); } + + public static bool IsDictionary( + this Type type, + out Func getKeys, + out Func getValues, + out Type keyType, + out Type valueType) + { + var dictType = + type.GetAllInterfaces() + .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IDictionary<,>)) + ?? + type.GetAllInterfaces() + .FirstOrDefault(i => i == typeof(IDictionary)); + + if (dictType is null) + { + getKeys = null; + getValues = null; + keyType = null; + valueType = null; + + return false; + } + + var keysProperty = dictType.GetProperty("Keys"); + getKeys = instance => (IEnumerable)keysProperty.GetValue(instance, null); + + var valuesProperty = dictType.GetProperty("Values"); + getValues = instance => (IEnumerable)valuesProperty.GetValue(instance, null); + + if (type.GetElementTypeIfEnumerable() is { } keyValuePairType && + keyValuePairType.IsConstructedGenericType && + keyValuePairType.GetGenericTypeDefinition() is { } genericTypeDefinition && + genericTypeDefinition == typeof(KeyValuePair<,>)) + { + keyType = keyValuePairType.GetGenericArguments()[0]; + valueType = keyValuePairType.GetGenericArguments()[1]; + } + else + { + keyType = null; + + valueType = null; + } + + return true; + } } \ No newline at end of file diff --git a/src/Microsoft.DotNet.Interactive.Tests/LanguageKernelFormattingTests.cs b/src/Microsoft.DotNet.Interactive.Tests/LanguageKernelFormattingTests.cs index 6cfb27611f..dcc5dd3308 100644 --- a/src/Microsoft.DotNet.Interactive.Tests/LanguageKernelFormattingTests.cs +++ b/src/Microsoft.DotNet.Interactive.Tests/LanguageKernelFormattingTests.cs @@ -41,11 +41,11 @@ protected override CSharpKernel CreateCSharpKernel() [InlineData(Language.CSharp, "b(123)", $"{PlainTextBegin}123{PlainTextEnd}")] [InlineData(Language.FSharp, "b [] [str \"123\" ]", "123")] // sequence - [InlineData(Language.CSharp, "new[] { 1, 2, 3, 4 }", "")] - [InlineData(Language.FSharp, "[1; 2; 3; 4]", "
")] + [InlineData(Language.CSharp, "new[] { 1, 2, 3, 4 }", "
")]
+    [InlineData(Language.FSharp, "[1; 2; 3; 4]", "
")]
     // sequence of anonymous objects
     [InlineData(Language.CSharp, "new[] { new { a = 123 }, new { a = 456 } }", "
")] - [InlineData(Language.FSharp, "[{| a = 123 |}; {| a = 456 |}]", "
")] + [InlineData(Language.FSharp, "[{| a = 123 |}; {| a = 456 |}]", "
")] public async Task Default_formatting_is_HTML( Language language, string submission, diff --git a/src/Microsoft.DotNet.Interactive/ValueSharing/JavaScriptValueDeclarer.cs b/src/Microsoft.DotNet.Interactive/ValueSharing/JavaScriptValueDeclarer.cs index b9004b04b3..0cdf9f1797 100644 --- a/src/Microsoft.DotNet.Interactive/ValueSharing/JavaScriptValueDeclarer.cs +++ b/src/Microsoft.DotNet.Interactive/ValueSharing/JavaScriptValueDeclarer.cs @@ -35,8 +35,6 @@ public static bool TryGetValueDeclaration(object referenceValue, string declareA return true; } - // FIX: (TryGetValueDeclaration) handle application/json - code = null; return false; }