diff --git a/.pipelines/init.yml b/.pipelines/init.yml
index 18ea38537b..56b43d7843 100644
--- a/.pipelines/init.yml
+++ b/.pipelines/init.yml
@@ -1,7 +1,7 @@
parameters:
# Configuration: Release
Verbosity: Normal
- DotNetVersion: "3.1.201"
+ DotNetVersion: "3.1.302"
CakeVersion: "0.32.1"
NuGetVersion: "4.9.2"
steps:
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 3405336a87..f06fe4f0ae 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -22,7 +22,7 @@ resources:
variables:
Verbosity: Diagnostic
- DotNetVersion: "3.1.201"
+ DotNetVersion: "3.1.302"
CakeVersion: "0.32.1"
NuGetVersion: "4.9.2"
GitVersionVersion: "5.0.1"
diff --git a/build.json b/build.json
index 69eece7ac1..d5a5937666 100644
--- a/build.json
+++ b/build.json
@@ -2,7 +2,7 @@
"DotNetInstallScriptURL": "https://dot.net/v1",
"DotNetChannel": "Preview",
"DotNetVersions": [
- "3.1.201",
+ "3.1.302",
"5.0.100-preview.7.20366.6"
],
"RequiredMonoVersion": "6.6.0",
diff --git a/global.json b/global.json
index 4f07b54704..fcfbc1acf6 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
"sdk": {
- "version": "3.1.201"
+ "version": "3.1.302"
}
}
diff --git a/src/OmniSharp.Abstractions/Models/v1/QuickInfoRequest.cs b/src/OmniSharp.Abstractions/Models/v1/QuickInfoRequest.cs
new file mode 100644
index 0000000000..884e9eb855
--- /dev/null
+++ b/src/OmniSharp.Abstractions/Models/v1/QuickInfoRequest.cs
@@ -0,0 +1,9 @@
+using OmniSharp.Mef;
+
+namespace OmniSharp.Models
+{
+ [OmniSharpEndpoint(OmniSharpEndpoints.QuickInfo, typeof(QuickInfoRequest), typeof(QuickInfoResponse))]
+ public class QuickInfoRequest : Request
+ {
+ }
+}
diff --git a/src/OmniSharp.Abstractions/Models/v1/QuickInfoResponse.cs b/src/OmniSharp.Abstractions/Models/v1/QuickInfoResponse.cs
new file mode 100644
index 0000000000..c356af97dd
--- /dev/null
+++ b/src/OmniSharp.Abstractions/Models/v1/QuickInfoResponse.cs
@@ -0,0 +1,13 @@
+#nullable enable
+using System.Collections.Immutable;
+
+namespace OmniSharp.Models
+{
+ public class QuickInfoResponse
+ {
+ ///
+ /// QuickInfo for the given position, rendered as markdown.
+ ///
+ public string Markdown { get; set; } = string.Empty;
+ }
+}
diff --git a/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs b/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs
index 58f82bb2f8..141768c42e 100644
--- a/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs
+++ b/src/OmniSharp.Abstractions/OmniSharpEndpoints.cs
@@ -44,6 +44,7 @@ public static class OmniSharpEndpoints
public const string Diagnostics = "/diagnostics";
public const string ReAnalyze = "/reanalyze";
+ public const string QuickInfo = "/quickinfo";
public static class V2
{
@@ -65,6 +66,7 @@ public static class V2
public const string CodeStructure = "/v2/codestructure";
public const string Highlight = "/v2/highlight";
+
}
}
}
diff --git a/src/OmniSharp.Roslyn.CSharp/Services/QuickInfoProvider.cs b/src/OmniSharp.Roslyn.CSharp/Services/QuickInfoProvider.cs
new file mode 100644
index 0000000000..9d828220cd
--- /dev/null
+++ b/src/OmniSharp.Roslyn.CSharp/Services/QuickInfoProvider.cs
@@ -0,0 +1,275 @@
+using System.Composition;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.QuickInfo;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.Extensions.Logging;
+using OmniSharp.Mef;
+using OmniSharp.Models;
+using OmniSharp.Options;
+
+#nullable enable
+
+namespace OmniSharp.Roslyn.CSharp.Services
+{
+ [OmniSharpHandler(OmniSharpEndpoints.QuickInfo, LanguageNames.CSharp)]
+ public class QuickInfoProvider : IRequestHandler
+ {
+ // Based on https://github.com/dotnet/roslyn/blob/7dc32a952e77c96c31cae6a2ba6d253a558fc7ff/src/Features/LanguageServer/Protocol/Handler/Hover/HoverHandler.cs
+
+ // These are internal tag values taken from https://github.com/dotnet/roslyn/blob/master/src/Features/Core/Portable/Common/TextTags.cs
+ // They're copied here so that we can ensure we render blocks correctly in the markdown
+ // https://github.com/dotnet/roslyn/issues/46254 tracks making these public
+
+ ///
+ /// Indicates the start of a text container. The elements after through (but not
+ /// including) the matching are rendered in a rectangular block which is positioned
+ /// as an inline element relative to surrounding elements. The text of the element
+ /// itself precedes the content of the container, and is typically a bullet or number header for an item in a
+ /// list.
+ ///
+ private const string ContainerStart = nameof(ContainerStart);
+ ///
+ /// Indicates the end of a text container. See .
+ ///
+ private const string ContainerEnd = nameof(ContainerEnd);
+ ///
+ /// Section kind for nullability analysis.
+ ///
+ internal const string NullabilityAnalysis = nameof(NullabilityAnalysis);
+
+ private readonly OmniSharpWorkspace _workspace;
+ private readonly FormattingOptions _formattingOptions;
+ private readonly ILogger? _logger;
+
+ [ImportingConstructor]
+ public QuickInfoProvider(OmniSharpWorkspace workspace, FormattingOptions formattingOptions, ILoggerFactory? loggerFactory)
+ {
+ _workspace = workspace;
+ _formattingOptions = formattingOptions;
+ _logger = loggerFactory?.CreateLogger();
+ }
+
+ public async Task Handle(QuickInfoRequest request)
+ {
+ var document = _workspace.GetDocument(request.FileName);
+ var response = new QuickInfoResponse();
+
+ if (document is null)
+ {
+ return response;
+ }
+
+ var quickInfoService = QuickInfoService.GetService(document);
+ if (quickInfoService is null)
+ {
+ _logger?.LogWarning($"QuickInfo service was null for {document.FilePath}");
+ return response;
+ }
+
+ var sourceText = await document.GetTextAsync();
+ var position = sourceText.Lines.GetPosition(new LinePosition(request.Line, request.Column));
+
+ var quickInfo = await quickInfoService.GetQuickInfoAsync(document, position);
+ if (quickInfo is null)
+ {
+ _logger?.LogTrace($"No QuickInfo found for {document.FilePath}:{request.Line},{request.Column}");
+ return response;
+ }
+
+ var finalTextBuilder = new StringBuilder();
+ var sectionTextBuilder = new StringBuilder();
+
+ var description = quickInfo.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.Description);
+ if (description is object)
+ {
+ appendSectionAsCsharp(description, finalTextBuilder, _formattingOptions, includeSpaceAtStart: false);
+ }
+
+ var summary = quickInfo.Sections.FirstOrDefault(s => s.Kind == QuickInfoSectionKinds.DocumentationComments);
+ if (summary is object)
+ {
+ buildSectionAsMarkdown(summary, sectionTextBuilder, _formattingOptions, out _);
+ appendBuiltSection(finalTextBuilder, sectionTextBuilder, _formattingOptions);
+ }
+
+ foreach (var section in quickInfo.Sections)
+ {
+ switch (section.Kind)
+ {
+ case QuickInfoSectionKinds.Description:
+ case QuickInfoSectionKinds.DocumentationComments:
+ continue;
+
+ case QuickInfoSectionKinds.TypeParameters:
+ appendSectionAsCsharp(section, finalTextBuilder, _formattingOptions);
+ break;
+
+ case QuickInfoSectionKinds.AnonymousTypes:
+ // The first line is "Anonymous Types:"
+ buildSectionAsMarkdown(section, sectionTextBuilder, _formattingOptions, out int lastIndex, untilLineBreak: true);
+ appendBuiltSection(finalTextBuilder, sectionTextBuilder, _formattingOptions);
+
+ // Then we want all anonymous types to be C# highlighted
+ appendSectionAsCsharp(section, finalTextBuilder, _formattingOptions, lastIndex + 1);
+ break;
+
+ case NullabilityAnalysis:
+ // Italicize the nullable analysis for emphasis.
+ buildSectionAsMarkdown(section, sectionTextBuilder, _formattingOptions, out _);
+ appendBuiltSection(finalTextBuilder, sectionTextBuilder, _formattingOptions, italicize: true);
+ break;
+
+ default:
+ buildSectionAsMarkdown(section, sectionTextBuilder, _formattingOptions, out _);
+ appendBuiltSection(finalTextBuilder, sectionTextBuilder, _formattingOptions);
+ break;
+ }
+ }
+
+ response.Markdown = finalTextBuilder.ToString().Trim();
+
+ return response;
+
+ static void appendBuiltSection(StringBuilder finalTextBuilder, StringBuilder stringBuilder, FormattingOptions formattingOptions, bool italicize = false)
+ {
+ // Two newlines to trigger a markdown new paragraph
+ finalTextBuilder.Append(formattingOptions.NewLine);
+ finalTextBuilder.Append(formattingOptions.NewLine);
+ if (italicize)
+ {
+ finalTextBuilder.Append("_");
+ }
+ finalTextBuilder.Append(stringBuilder);
+ if (italicize)
+ {
+ finalTextBuilder.Append("_");
+ }
+ stringBuilder.Clear();
+ }
+
+ static void appendSectionAsCsharp(QuickInfoSection section, StringBuilder builder, FormattingOptions formattingOptions, int startingIndex = 0, bool includeSpaceAtStart = true)
+ {
+ if (includeSpaceAtStart)
+ {
+ builder.Append(formattingOptions.NewLine);
+ }
+ builder.Append("```csharp");
+ builder.Append(formattingOptions.NewLine);
+ for (int i = startingIndex; i < section.TaggedParts.Length; i++)
+ {
+ TaggedText part = section.TaggedParts[i];
+ if (part.Tag == TextTags.LineBreak && i + 1 != section.TaggedParts.Length)
+ {
+ builder.Append(formattingOptions.NewLine);
+ }
+ else
+ {
+ builder.Append(part.Text);
+ }
+ }
+ builder.Append(formattingOptions.NewLine);
+ builder.Append("```");
+ }
+
+ static void buildSectionAsMarkdown(QuickInfoSection section, StringBuilder stringBuilder, FormattingOptions formattingOptions, out int lastIndex, bool untilLineBreak = false)
+ {
+ bool isInCodeBlock = false;
+ lastIndex = 0;
+ for (int i = 0; i < section.TaggedParts.Length; i++)
+ {
+ var current = section.TaggedParts[i];
+ lastIndex = i;
+
+ switch (current.Tag)
+ {
+ case TextTags.Text when !isInCodeBlock:
+ stringBuilder.Append(current.Text);
+ break;
+
+ case TextTags.Text:
+ endBlock();
+ stringBuilder.Append(current.Text);
+ break;
+
+ case TextTags.Space when isInCodeBlock:
+ if (nextIsTag(i, TextTags.Text))
+ {
+ endBlock();
+ }
+
+ stringBuilder.Append(current.Text);
+ break;
+
+ case TextTags.Space:
+ case TextTags.Punctuation:
+ stringBuilder.Append(current.Text);
+ break;
+
+ case ContainerStart:
+ addNewline();
+ stringBuilder.Append(current.Text);
+ break;
+
+ case ContainerEnd:
+ addNewline();
+ break;
+
+ case TextTags.LineBreak when untilLineBreak && stringBuilder.Length != 0:
+ // The section will end and another newline will be appended, no need to add yet another newline.
+ return;
+
+ case TextTags.LineBreak:
+ if (stringBuilder.Length != 0 && !nextIsTag(i, ContainerStart, ContainerEnd) && i + 1 != section.TaggedParts.Length)
+ {
+ addNewline();
+ }
+ break;
+
+ default:
+ if (!isInCodeBlock)
+ {
+ isInCodeBlock = true;
+ stringBuilder.Append('`');
+ }
+ stringBuilder.Append(current.Text);
+ break;
+ }
+ }
+
+ if (isInCodeBlock)
+ {
+ endBlock();
+ }
+
+ return;
+
+ void addNewline()
+ {
+ if (isInCodeBlock)
+ {
+ endBlock();
+ }
+
+ // Markdown needs 2 linebreaks to make a new paragraph
+ stringBuilder.Append(formattingOptions.NewLine);
+ stringBuilder.Append(formattingOptions.NewLine);
+ }
+
+ void endBlock()
+ {
+ stringBuilder.Append('`');
+ isInCodeBlock = false;
+ }
+
+ bool nextIsTag(int i, params string[] tags)
+ {
+ int nextI = i + 1;
+ return nextI < section.TaggedParts.Length && tags.Contains(section.TaggedParts[nextI].Tag);
+ }
+ }
+ }
+ }
+}
diff --git a/test-assets/test-projects/global.json b/test-assets/test-projects/global.json
index 4f07b54704..fcfbc1acf6 100644
--- a/test-assets/test-projects/global.json
+++ b/test-assets/test-projects/global.json
@@ -1,5 +1,5 @@
{
"sdk": {
- "version": "3.1.201"
+ "version": "3.1.302"
}
}
diff --git a/tests/OmniSharp.MSBuild.Tests/ProjectLoadListenerTests.cs b/tests/OmniSharp.MSBuild.Tests/ProjectLoadListenerTests.cs
index b6961521da..74460e8e91 100644
--- a/tests/OmniSharp.MSBuild.Tests/ProjectLoadListenerTests.cs
+++ b/tests/OmniSharp.MSBuild.Tests/ProjectLoadListenerTests.cs
@@ -224,7 +224,7 @@ public async Task The_correct_sdk_version_is_emitted()
using (var host = CreateMSBuildTestHost(testProject.Directory, emitter.AsExportDescriptionProvider(LoggerFactory)))
{
Assert.Single(emitter.ReceivedMessages);
- Assert.Equal(GetHashedFileExtension("3.1.201"), emitter.ReceivedMessages[0].SdkVersion);
+ Assert.Equal(GetHashedFileExtension("3.1.302"), emitter.ReceivedMessages[0].SdkVersion);
}
}
diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/QuickInfoProviderFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/QuickInfoProviderFacts.cs
new file mode 100644
index 0000000000..42cef77a57
--- /dev/null
+++ b/tests/OmniSharp.Roslyn.CSharp.Tests/QuickInfoProviderFacts.cs
@@ -0,0 +1,715 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Threading.Tasks;
+using OmniSharp.Models;
+using OmniSharp.Options;
+using OmniSharp.Roslyn.CSharp.Services;
+using TestUtility;
+using Xunit;
+using Xunit.Abstractions;
+
+#nullable enable
+
+namespace OmniSharp.Roslyn.CSharp.Tests
+{
+ public class QuickInfoProviderFacts : AbstractSingleRequestHandlerTestFixture
+ {
+ protected override string EndpointName => OmniSharpEndpoints.QuickInfo;
+
+ public QuickInfoProviderFacts(ITestOutputHelper output, SharedOmniSharpHostFixture sharedOmniSharpHostFixture)
+ : base(output, sharedOmniSharpHostFixture)
+ { }
+
+ [Fact]
+ public async Task ParameterDocumentation()
+ {
+ const string source = @"namespace N
+{
+ class C
+ {
+ /// Some content
+ public void M(int i)
+ {
+ _ = i;
+ }
+ }
+}";
+
+ var testFile = new TestFile("dummy.cs", source);
+ SharedOmniSharpTestHost.AddFilesToWorkspace(testFile);
+ var requestHandler = GetRequestHandler(SharedOmniSharpTestHost);
+
+ var request = new QuickInfoRequest { FileName = testFile.FileName, Line = 7, Column = 17 };
+ var response = await requestHandler.Handle(request);
+
+ Assert.Equal("```csharp\n(parameter) int i\n```\n\nSome content `C`", response.Markdown);
+ }
+
+ [Fact]
+ public async Task OmitsNamespaceForNonRegularCSharpSyntax()
+ {
+ var source = @"class Foo {}";
+
+ var testFile = new TestFile("dummy.csx", source);
+ var workspace = TestHelpers.CreateCsxWorkspace(testFile);
+
+ var controller = new QuickInfoProvider(workspace, new FormattingOptions(), null);
+ var response = await controller.Handle(new QuickInfoRequest { FileName = testFile.FileName, Line = 0, Column = 7 });
+
+ Assert.Equal("```csharp\nclass Foo\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task TypesFromInlineAssemlbyReferenceContainDocumentation()
+ {
+ var testAssemblyPath = Path.Combine(TestAssets.Instance.TestBinariesFolder, "ClassLibraryWithDocumentation.dll");
+ var source =
+ $@"#r ""{testAssemblyPath}""
+ using ClassLibraryWithDocumentation;
+ Documented$$Class c;
+ ";
+
+ var testFile = new TestFile("dummy.csx", source);
+ var position = testFile.Content.GetPointFromPosition();
+ var workspace = TestHelpers.CreateCsxWorkspace(testFile);
+
+ var controller = new QuickInfoProvider(workspace, new FormattingOptions(), null);
+ var response = await controller.Handle(new QuickInfoRequest { FileName = testFile.FileName, Line = position.Line, Column = position.Offset });
+
+ Assert.Equal("```csharp\nclass ClassLibraryWithDocumentation.DocumentedClass\n```\n\nThis class performs an important function.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task OmitsNamespaceForTypesInGlobalNamespace()
+ {
+ const string source = @"namespace Bar {
+ class Foo {}
+ }
+ class Baz {}";
+
+ var testFile = new TestFile("dummy.cs", source);
+ SharedOmniSharpTestHost.AddFilesToWorkspace(testFile);
+ var requestHandler = GetRequestHandler(SharedOmniSharpTestHost);
+
+ var requestInNormalNamespace = new QuickInfoRequest { FileName = testFile.FileName, Line = 1, Column = 19 };
+ var responseInNormalNamespace = await requestHandler.Handle(requestInNormalNamespace);
+
+ var requestInGlobalNamespace = new QuickInfoRequest { FileName = testFile.FileName, Line = 3, Column = 19 };
+ var responseInGlobalNamespace = await requestHandler.Handle(requestInGlobalNamespace);
+
+ Assert.Equal("```csharp\nclass Bar.Foo\n```", responseInNormalNamespace.Markdown);
+ Assert.Equal("```csharp\nclass Baz\n```", responseInGlobalNamespace.Markdown);
+ }
+
+ [Fact]
+ public async Task IncludesNamespaceForRegularCSharpSyntax()
+ {
+ const string source = @"namespace Bar {
+ class Foo {}
+ }";
+
+ var testFile = new TestFile("dummy.cs", source);
+ SharedOmniSharpTestHost.AddFilesToWorkspace(testFile);
+ var requestHandler = GetRequestHandler(SharedOmniSharpTestHost);
+
+ var request = new QuickInfoRequest { FileName = testFile.FileName, Line = 1, Column = 19 };
+ var response = await requestHandler.Handle(request);
+
+ Assert.Equal("```csharp\nclass Bar.Foo\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task IncludesContainingTypeFoNestedTypesForRegularCSharpSyntax()
+ {
+ var source = @"namespace Bar {
+ class Foo {
+ class Xyz {}
+ }
+ }";
+
+ var testFile = new TestFile("dummy.cs", source);
+ SharedOmniSharpTestHost.AddFilesToWorkspace(testFile);
+ var requestHandler = GetRequestHandler(SharedOmniSharpTestHost);
+
+ var request = new QuickInfoRequest { FileName = testFile.FileName, Line = 2, Column = 27 };
+ var response = await requestHandler.Handle(request);
+
+ Assert.Equal("```csharp\nclass Bar.Foo.Xyz\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task IncludesContainingTypeFoNestedTypesForNonRegularCSharpSyntax()
+ {
+ var source = @"class Foo {
+ class Bar {}
+ }";
+
+ var testFile = new TestFile("dummy.csx", source);
+ var workspace = TestHelpers.CreateCsxWorkspace(testFile);
+
+ var controller = new QuickInfoProvider(workspace, new FormattingOptions(), null);
+ var request = new QuickInfoRequest { FileName = testFile.FileName, Line = 1, Column = 23 };
+ var response = await controller.Handle(request);
+
+ Assert.Equal("```csharp\nclass Foo.Bar\n```", response.Markdown);
+ }
+
+ private static TestFile s_testFile = new TestFile("dummy.cs",
+ @"using System;
+ using Bar2;
+ using System.Collections.Generic;
+ namespace Bar {
+ class Foo {
+ public Foo() {
+ Console.WriteLine(""abc"");
+ }
+
+ public void MyMethod(string name, Foo foo, Foo2 foo2) { };
+
+ private Foo2 _someField = new Foo2();
+
+ public Foo2 SomeProperty { get; }
+
+ public IDictionary> SomeDict { get; }
+
+ public void Compute(int index = 2) { }
+
+ private const int foo = 1;
+ }
+ }
+
+ namespace Bar2 {
+ class Foo2 {
+ }
+ }
+
+ namespace Bar3 {
+ enum Foo3 {
+ Val1 = 1,
+ Val2
+ }
+ }
+ ");
+
+ [Fact]
+ public async Task DisplayFormatForMethodSymbol_Invocation()
+ {
+ var response = await GetTypeLookUpResponse(line: 6, column: 35);
+
+ Assert.Equal("```csharp\nvoid Console.WriteLine(string value) (+ 18 overloads)\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatForMethodSymbol_Declaration()
+ {
+ var response = await GetTypeLookUpResponse(line: 9, column: 35);
+ Assert.Equal("```csharp\nvoid Foo.MyMethod(string name, Foo foo, Foo2 foo2)\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_TypeSymbol_Primitive()
+ {
+ var response = await GetTypeLookUpResponse(line: 9, column: 46);
+ Assert.Equal("```csharp\nclass System.String\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_TypeSymbol_ComplexType_SameNamespace()
+ {
+ var response = await GetTypeLookUpResponse(line: 9, column: 56);
+ Assert.Equal("```csharp\nclass Bar.Foo\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_TypeSymbol_ComplexType_DifferentNamespace()
+ {
+ var response = await GetTypeLookUpResponse(line: 9, column: 67);
+ Assert.Equal("```csharp\nclass Bar2.Foo2\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_TypeSymbol_WithGenerics()
+ {
+ var response = await GetTypeLookUpResponse(line: 15, column: 36);
+ Assert.Equal("```csharp\ninterface System.Collections.Generic.IDictionary\n```\n```csharp\n\nTKey is string\nTValue is IEnumerable\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatForParameterSymbol_Name_Primitive()
+ {
+ var response = await GetTypeLookUpResponse(line: 9, column: 51);
+ Assert.Equal("```csharp\n(parameter) string name\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_ParameterSymbol_ComplexType_SameNamespace()
+ {
+ var response = await GetTypeLookUpResponse(line: 9, column: 60);
+ Assert.Equal("```csharp\n(parameter) Foo foo\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_ParameterSymbol_Name_ComplexType_DifferentNamespace()
+ {
+ var response = await GetTypeLookUpResponse(line: 9, column: 71);
+ Assert.Equal("```csharp\n(parameter) Foo2 foo2\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_ParameterSymbol_Name_WithDefaultValue()
+ {
+ var response = await GetTypeLookUpResponse(line: 17, column: 48);
+ Assert.Equal("```csharp\n(parameter) int index = 2\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_FieldSymbol()
+ {
+ var response = await GetTypeLookUpResponse(line: 11, column: 38);
+ Assert.Equal("```csharp\n(field) Foo2 Foo._someField\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_FieldSymbol_WithConstantValue()
+ {
+ var response = await GetTypeLookUpResponse(line: 19, column: 41);
+ Assert.Equal("```csharp\n(constant) int Foo.foo = 1\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_EnumValue()
+ {
+ var response = await GetTypeLookUpResponse(line: 31, column: 23);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_PropertySymbol()
+ {
+ var response = await GetTypeLookUpResponse(line: 13, column: 38);
+ Assert.Equal("```csharp\nFoo2 Foo.SomeProperty { get; }\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task DisplayFormatFor_PropertySymbol_WithGenerics()
+ {
+ var response = await GetTypeLookUpResponse(line: 15, column: 70);
+ Assert.Equal("```csharp\nIDictionary> Foo.SomeDict { get; }\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationRemarksText()
+ {
+ string content = @"
+class testissue
+{
+ ///You may have some additional information about this class here.
+ public static bool C$$ompare(int gameObject, string tagName)
+ {
+ return gameObject.TagifyCompareTag(tagName);
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nbool testissue.Compare(int gameObject, string tagName)\n```\n\nYou may have some additional information about this class here.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationSummaryText()
+ {
+ string content = @"
+class testissue
+{
+ ///Checks if object is tagged with the tag.
+ public static bool C$$ompare(int gameObject, string tagName)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nbool testissue.Compare(int gameObject, string tagName)\n```\n\nChecks if object is tagged with the tag.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationReturnsText()
+ {
+ string content = @"
+class testissue
+{
+ ///Returns true if object is tagged with tag.
+ public static bool C$$ompare(int gameObject, string tagName)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nbool testissue.Compare(int gameObject, string tagName)\n```\n\nReturns:\n\n Returns true if object is tagged with tag.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationExampleText()
+ {
+ string content = @"
+class testissue
+{
+ ///Checks if object is tagged with the tag.
+ public static bool C$$ompare(int gameObject, string tagName)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nbool testissue.Compare(int gameObject, string tagName)\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationExceptionText()
+ {
+ string content = @"
+class testissue
+{
+ ///A description
+ ///B description
+ public static bool C$$ompare(int gameObject, string tagName)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nbool testissue.Compare(int gameObject, string tagName)\n```\n\nExceptions:\n\n A\n\n B", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationParameter()
+ {
+ string content = @"
+class testissue
+{
+ /// The game object.
+ /// Name of the tag.
+ public static bool C$$ompare(int gameObject, string tagName)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nbool testissue.Compare(int gameObject, string tagName)\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationTypeParameter()
+ {
+ string content = @"
+public class TestClass
+{
+ ///
+ /// Creates a new array of arbitrary type and adds the elements of incoming list to it if possible
+ ///
+ /// The element type of the array
+ /// The element type of the list
+ public static T[] m$$kArray(int n, List list)
+ {
+ return new T[n];
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nT[] TestClass.mkArray(int n, List list)\n```\n\nCreates a new array of arbitrary type `T` and adds the elements of incoming list to it if possible", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationTypeParameter_TypeParam1()
+ {
+ string content = @"
+public class TestClass
+{
+ ///
+ /// Creates a new array of arbitrary type and adds the elements of incoming list to it if possible
+ ///
+ /// The element type of the array
+ /// The element type of the list
+ public static T[] mkArray(int n, List list)
+ {
+ return new T[n];
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nT in TestClass.mkArray\n```\n\nThe element type of the array", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationTypeParameter_TypeParam2()
+ {
+ string content = @"
+public class TestClass
+{
+ ///
+ /// Creates a new array of arbitrary type and adds the elements of incoming list to it if possible
+ ///
+ /// The element type of the array
+ /// The element type of the list
+ public static T[] mkArray(int n, List list)
+ {
+ return new T[n];
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Empty(response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationValueText()
+ {
+ string content =
+@"public class Employee
+{
+ private string _name;
+
+ /// The Name property represents the employee's name.
+ /// The Name property gets/sets the value of the string field, _name.
+ public string Na$$me
+ {
+ }
+}
+";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nstring Employee.Name { }\n```\n\nThe Name property represents the employee's name.\n\nValue:\n\n The Name property gets/sets the value of the string field, _name.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationNestedTagSee()
+ {
+ string content = @"
+public class TestClass
+{
+ /// DoWork is a method in the TestClass class. for information about output statements.
+ public static void Do$$Work(int Int1)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nvoid TestClass.DoWork(int Int1)\n```\n\nDoWork is a method in the TestClass class. `System.Console.WriteLine(string)` for information about output statements.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationNestedTagParamRef()
+ {
+ string content = @"
+public class TestClass
+{
+ /// Creates a new array of arbitrary type
+ /// The element type of the array
+ public static T[] mk$$Array(int n)
+ {
+ return new T[n];
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nT[] TestClass.mkArray(int n)\n```\n\nCreates a new array of arbitrary type `T`", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationNestedTagCode()
+ {
+ string content = @"
+public class TestClass
+{
+ /// This sample shows how to call the method.
+ ///
+ /// class TestClass
+ /// {
+ /// static int Main()
+ /// {
+ /// return GetZero();
+ /// }
+ /// }
+ ///
+ ///
+ public static int $$GetZero()
+ {
+ return 0;
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nint TestClass.GetZero()\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationNestedTagPara()
+ {
+ string content = @"
+public class TestClass
+{
+ /// DoWork is a method in the TestClass class.
+ /// Here's how you could make a second paragraph in a description.
+ ///
+ public static void Do$$Work(int Int1)
+ {
+ }
+}
+ ";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nvoid TestClass.DoWork(int Int1)\n```\n\nDoWork is a method in the TestClass class.\n\n\n\nHere's how you could make a second paragraph in a description.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationNestedTagSeeAlso()
+ {
+ string content = @"
+public class TestClass
+{
+ /// DoWork is a method in the TestClass class.
+ ///
+ ///
+ public static void Do$$Work(int Int1)
+ {
+ }
+
+ static void Main()
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nvoid TestClass.DoWork(int Int1)\n```\n\nDoWork is a method in the TestClass class. `TestClass.Main()`", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationSummaryAndParam()
+ {
+ string content = @"
+class testissue
+{
+ ///Checks if object is tagged with the tag.
+ /// The game object.
+ /// Name of the tag.
+ public static bool C$$ompare(int gameObject, string tagName)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nbool testissue.Compare(int gameObject, string tagName)\n```\n\nChecks if object is tagged with the tag.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationManyTags()
+ {
+ string content = @"
+class testissue
+{
+ ///Checks if object is tagged with the tag.
+ ///The game object.
+ ///Invoke using A.Compare(5) where A is an instance of the class testissue.
+ ///The element type of the array
+ ///Thrown when something goes wrong
+ ///You may have some additional information about this class here.
+ ///Returns an array of type .
+ public static T[] C$$ompare(int gameObject)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal(
+ "```csharp\nT[] testissue.Compare(int gameObject)\n```\n\nChecks if object is tagged with the tag.\n\nYou may have some additional information about this class here.\n\nReturns:\n\n Returns an array of type `T`.\n\n\n\nExceptions:\n\n `System.Exception`",
+ response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationSpaceBeforeText()
+ {
+ string content = @"
+public class TestClass
+{
+ /// DoWork is a method in the TestClass class.
+ public static void Do$$Work(int Int1)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nvoid TestClass.DoWork(int Int1)\n```\n\nDoWork is a method in the TestClass class.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationForParameters1()
+ {
+ string content = @"
+class testissue
+{
+ /// The game object.
+ /// Name of the tag.
+ public static bool Compare(int gam$$eObject, string tagName)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\n(parameter) int gameObject\n```\n\nThe game object.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task StructuredDocumentationForParameters2()
+ {
+ string content = @"
+class testissue
+{
+ /// The game object.
+ /// Name of the tag.
+ public static bool Compare(int gameObject, string tag$$Name)
+ {
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\n(parameter) string tagName\n```\n\nName of the tag.", response.Markdown);
+ }
+
+ [Fact]
+ public async Task AnonymousTypeSubstitution()
+ {
+ string content = @"
+class C
+{
+ void M1(T t) {}
+ void M2()
+ {
+ var a = new { X = 1, Y = 2 };
+ M$$1(a);
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nvoid C.M1<'a>('a t)\n```\n\nAnonymous Types:\n```csharp\n 'a is new { int X, int Y }\n```", response.Markdown);
+ }
+
+ [Fact]
+ public async Task InheritDoc()
+ {
+ string content = @"
+class Program
+{
+ /// Hello World
+ public static void A() { }
+
+ ///
+ public static void B() { }
+
+ public static void Main()
+ {
+ A();
+ B$$();
+ }
+}";
+ var response = await GetTypeLookUpResponse(content);
+ Assert.Equal("```csharp\nvoid Program.B()\n```\n\nHello World", response.Markdown);
+ }
+
+ private async Task GetTypeLookUpResponse(string content)
+ {
+ TestFile testFile = new TestFile("dummy.cs", content);
+ SharedOmniSharpTestHost.AddFilesToWorkspace(testFile);
+ var requestHandler = GetRequestHandler(SharedOmniSharpTestHost);
+ var point = testFile.Content.GetPointFromPosition();
+ var request = new QuickInfoRequest { FileName = testFile.FileName, Line = point.Line, Column = point.Offset };
+
+ return await requestHandler.Handle(request);
+ }
+
+ private async Task GetTypeLookUpResponse(int line, int column)
+ {
+ SharedOmniSharpTestHost.AddFilesToWorkspace(s_testFile);
+ var requestHandler = GetRequestHandler(SharedOmniSharpTestHost);
+ var request = new QuickInfoRequest { FileName = s_testFile.FileName, Line = line, Column = column };
+
+ return await requestHandler.Handle(request);
+ }
+ }
+}
diff --git a/tests/OmniSharp.Tests/DotNetCliServiceFacts.cs b/tests/OmniSharp.Tests/DotNetCliServiceFacts.cs
index 0793d67891..f8fd369545 100644
--- a/tests/OmniSharp.Tests/DotNetCliServiceFacts.cs
+++ b/tests/OmniSharp.Tests/DotNetCliServiceFacts.cs
@@ -23,7 +23,7 @@ public void GetVersion()
Assert.Equal(3, version.Major);
Assert.Equal(1, version.Minor);
- Assert.Equal(201, version.Patch);
+ Assert.Equal(302, version.Patch);
Assert.Equal("", version.Release);
}
}
@@ -39,7 +39,7 @@ public void GetInfo()
Assert.Equal(3, info.Version.Major);
Assert.Equal(1, info.Version.Minor);
- Assert.Equal(201, info.Version.Patch);
+ Assert.Equal(302, info.Version.Patch);
Assert.Equal("", info.Version.Release);
}
}