Skip to content

Commit

Permalink
Merge pull request #2023 from 333fred/auto-doc-comment
Browse files Browse the repository at this point in the history
Add documentation comment creation to the FormatAfterKeystrokeService
  • Loading branch information
filipw authored Dec 1, 2020
2 parents 495bcc2 + 26e2308 commit 5a64da0
Show file tree
Hide file tree
Showing 4 changed files with 597 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#nullable enable

using System;
using System.Reflection;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;

namespace OmniSharp.Roslyn.DocumentationComments
{
/// <summary>
/// Proxy service for Microsoft.CodeAnalysis.DocumentationComments.IDocumentationCommentSnippetService.
/// Implementation was based on the service as of this commit: 2834b74995bb66a7cb19cb09069c17812819afdc
/// See: https://github.com/dotnet/roslyn/blob/2834b74995bb66a7cb19cb09069c17812819afdc/src/Features/Core/Portable/DocumentationComments/IDocumentationCommentSnippetService.cs
/// </summary>
public struct DocumentationCommentSnippetService
{
/// <summary>
/// IDocumentationCommentService HostLanguageServices.GetRequiredService<IDocumentationCommentService>()
/// </summary>
private static MethodInfo s_getRequiredService;
/// <summary>
/// DocumentationCommentSnippet IDocumentationCommentService.GetDocumentationCommentSnippetOnCharacterTyped(SyntaxTree, SourceText, int, DocumentOptionSet, CancellationToken)
/// </summary>
private static MethodInfo s_getDocumentationCommentSnippetOnCharacterTyped;
/// <summary>
/// DocumentationCommentSnippet IDocumentationCommentService.GetDocumentationCommentSnippetOnEnterTyped(SyntaxTree, SourceText, int, DocumentOptionSet, CancellationToken)
/// </summary>
private static MethodInfo s_getDocumentationCommentSnippetOnEnterTyped;
/// <summary>
/// TextSpan DocumentationCommentSnippet.SpanToReplace
/// </summary>
private static PropertyInfo s_spanToReplace;
/// <summary>
/// string DocumentationCommentSnippet.SnippetText
/// </summary>
private static PropertyInfo s_snippetText;

static DocumentationCommentSnippetService()
{
var iDocumentationCommentSnippetServiceType = typeof(CompletionItem).Assembly.GetType("Microsoft.CodeAnalysis.DocumentationComments.IDocumentationCommentSnippetService");
s_getDocumentationCommentSnippetOnCharacterTyped = iDocumentationCommentSnippetServiceType.GetMethod(nameof(GetDocumentationCommentSnippetOnCharacterTyped));
s_getDocumentationCommentSnippetOnEnterTyped = iDocumentationCommentSnippetServiceType.GetMethod(nameof(GetDocumentationCommentSnippetOnEnterTyped));

var documentationCommentSnippet = typeof(CompletionItem).Assembly.GetType("Microsoft.CodeAnalysis.DocumentationComments.DocumentationCommentSnippet");
s_spanToReplace = documentationCommentSnippet.GetProperty(nameof(DocumentationCommentSnippet.SpanToReplace));
s_snippetText = documentationCommentSnippet.GetProperty(nameof(DocumentationCommentSnippet.SnippetText));

s_getRequiredService = typeof(HostLanguageServices).GetMethod(nameof(HostLanguageServices.GetRequiredService)).MakeGenericMethod(iDocumentationCommentSnippetServiceType);
}

public static DocumentationCommentSnippetService GetDocumentationCommentSnippetService(Document document)
{
var service = s_getRequiredService.Invoke(document.Project.LanguageServices, Array.Empty<object>());
return new DocumentationCommentSnippetService(service);
}

private object _underlying;

private DocumentationCommentSnippetService(object underlying)
{
_underlying = underlying;
}

public DocumentationCommentSnippet? GetDocumentationCommentSnippetOnCharacterTyped(SyntaxTree syntaxTree, SourceText text, int position, DocumentOptionSet options, CancellationToken cancellationToken)
{
var originalSnippet = s_getDocumentationCommentSnippetOnCharacterTyped.Invoke(_underlying, new object[] { syntaxTree, text, position, options, cancellationToken });
return ConvertSnippet(originalSnippet);
}

public DocumentationCommentSnippet? GetDocumentationCommentSnippetOnEnterTyped(SyntaxTree syntaxTree, SourceText text, int position, DocumentOptionSet options, CancellationToken cancellationToken)
{
var originalSnippet = s_getDocumentationCommentSnippetOnEnterTyped.Invoke(_underlying, new object[] { syntaxTree, text, position, options, cancellationToken });
return ConvertSnippet(originalSnippet);
}

private static DocumentationCommentSnippet? ConvertSnippet(object? originalSnippet)
{
if (originalSnippet == null)
{
return null;
}
else
{
return new DocumentationCommentSnippet((TextSpan)s_spanToReplace.GetValue(originalSnippet), (string)s_snippetText.GetValue(originalSnippet));
}
}
}

public struct DocumentationCommentSnippet
{
public TextSpan SpanToReplace { get; }
public string SnippetText { get; }

public DocumentationCommentSnippet(TextSpan spanToReplace, string snippetText)
{
SpanToReplace = spanToReplace;
SnippetText = snippetText;
}
}

public static class WorkspaceExtensions
{
public static DocumentationCommentSnippetService GetDocumentationCommentSnippetService(this Document document)
=> DocumentationCommentSnippetService.GetDocumentationCommentSnippetService(document);
}
}
54 changes: 51 additions & 3 deletions src/OmniSharp.Roslyn.CSharp/Workers/Formatting/FormattingWorker.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
#nullable enable

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
using Microsoft.Extensions.Logging;
using OmniSharp.Extensions;
using OmniSharp.Models;
using OmniSharp.Options;
using OmniSharp.Roslyn.DocumentationComments;
using OmniSharp.Roslyn.Utilities;

namespace OmniSharp.Roslyn.CSharp.Workers.Formatting
Expand All @@ -16,13 +22,19 @@ public static class FormattingWorker
{
public static async Task<IEnumerable<LinePositionSpanTextChange>> GetFormattingChangesAfterKeystroke(Document document, int position, char character, OmniSharpOptions omnisharpOptions, ILoggerFactory loggerFactory)
{
if (await GetDocumentationCommentChanges(document, position, character, omnisharpOptions) is LinePositionSpanTextChange change)
{
return new[] { change };
}

if (character == '\n')
{
// format previous line on new line
var text = await document.GetTextAsync();
var lines = text.Lines;
var targetLine = lines[lines.GetLineFromPosition(position).LineNumber - 1];
if (!string.IsNullOrWhiteSpace(targetLine.Text.ToString(targetLine.Span)))
Debug.Assert(targetLine.Text != null);
if (!string.IsNullOrWhiteSpace(targetLine.Text!.ToString(targetLine.Span)))
{
return await GetFormattingChanges(document, targetLine.Start, targetLine.End, omnisharpOptions, loggerFactory);
}
Expand All @@ -31,7 +43,8 @@ public static async Task<IEnumerable<LinePositionSpanTextChange>> GetFormattingC
{
// format after ; and }
var root = await document.GetSyntaxRootAsync();
var node = FindFormatTarget(root, position);
Debug.Assert(root != null);
var node = FindFormatTarget(root!, position);
if (node != null)
{
return await GetFormattingChanges(document, node.FullSpan.Start, node.FullSpan.End, omnisharpOptions, loggerFactory);
Expand All @@ -41,7 +54,7 @@ public static async Task<IEnumerable<LinePositionSpanTextChange>> GetFormattingC
return Enumerable.Empty<LinePositionSpanTextChange>();
}

public static SyntaxNode FindFormatTarget(SyntaxNode root, int position)
public static SyntaxNode? FindFormatTarget(SyntaxNode root, int position)
{
// todo@jo - refine this
var token = root.FindToken(position);
Expand Down Expand Up @@ -112,5 +125,40 @@ private static async Task<Document> FormatDocument(Document document, OmniSharpO

return newDocument;
}

private static async Task<LinePositionSpanTextChange?> GetDocumentationCommentChanges(Document document, int position, char character, OmniSharpOptions omnisharpOptions)
{
if (character != '\n' && character != '/')
{
return null;
}

var text = await document.GetTextAsync();
var syntaxTree = await document.GetSyntaxTreeAsync();

var optionSet = await document.GetOptionsAsync();

var documentationService = document.GetDocumentationCommentSnippetService();
var snippet = character == '\n' ?
documentationService.GetDocumentationCommentSnippetOnEnterTyped(syntaxTree!, text, position, optionSet, CancellationToken.None) :
documentationService.GetDocumentationCommentSnippetOnCharacterTyped(syntaxTree!, text, position, optionSet, CancellationToken.None);

if (snippet == null)
{
return null;
}
else
{
var range = text.GetRangeFromSpan(snippet.Value.SpanToReplace);
return new LinePositionSpanTextChange
{
NewText = snippet.Value.SnippetText,
StartLine = range.Start.Line,
StartColumn = range.Start.Column,
EndLine = range.End.Line,
EndColumn = range.End.Column
};
}
}
}
}
Loading

0 comments on commit 5a64da0

Please sign in to comment.