Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor brace completion to split into Features vs EditorFeatures #48468

Merged
merged 37 commits into from
Dec 7, 2020

Conversation

dibarbet
Copy link
Member

@dibarbet dibarbet commented Oct 9, 2020

Commit at a time is recommended, though the first commit is the bulk of the work :(

The first commit moves brace completion implementations from IEditorBraceCompletionSession to a new IBraceCompletionService that is available in the features layer (instead of editor features). This allows us to use it in LSP.
There were ~3 main issues with the existing impl

  1. All the IEditorBraceCompletionSession implementations start off with editor concepts (text buffers, snapshots, IBraceCompletionSession) - So I can't really use it in LSP. Luckily in most of the cases (like the above) they just need some info that can be found from the document as well. This enables nullable and makes things async where possible.
  2. The API shape for IEditorBraceCompletionSession isn't very LSP friendly - the AfterStart and AfterReturn methods apply the changes to the workspace rather than giving back edits. Additionally there's no way to return the actual brace completions or check if the caret is in a valid brace completion location.

The second commit uses the implementation to format braces after completion in razor via OnAutoInsert. We will eventually want to use LSP for brace completion in liveshare/codespaces C# files (since we require semantics for <>), but since they don't yet support over-typing, regular .cs files will use the same local implementation that they do now (in liveshare and codespaces).

Additionally, we use OnAutoInsert instead of OnTypeFormatting since OnAutoInsert supports snippets (moving the caret location). I previously spoke with @NTaylorMullen and the 'solution' for having OnTypeFormatting support snippets was OnAutoInsert.

@ajaybhargavb @NTaylorMullen there will be work on your side I believe to consume onautoinsert for this.

@dibarbet dibarbet force-pushed the refactor_brace_completion branch 3 times, most recently from f13f9ff to ca0e4e5 Compare October 9, 2020 07:28
@dibarbet dibarbet force-pushed the refactor_brace_completion branch 2 times, most recently from 70da75b to ff3ba2e Compare October 15, 2020 02:30
@dibarbet dibarbet force-pushed the refactor_brace_completion branch 2 times, most recently from 55ccc64 to cb4c677 Compare October 16, 2020 06:22
@dibarbet dibarbet force-pushed the refactor_brace_completion branch 3 times, most recently from 92b2f21 to c68ae3e Compare October 27, 2020 01:26
return ImmutableArray.Create(BraceCompletionFormattingRule.ForIndentStyle(indentStyle)).AddRange(Formatter.GetDefaultFormattingRules(document));
}

private sealed class BraceCompletionFormattingRule : BaseFormattingRule
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this type was just moved from the original curlybracecompletionsession

@dibarbet dibarbet changed the title [WIP] Refactor brace completion to split into Features vs EditorFeatures Refactor brace completion to split into Features vs EditorFeatures Oct 27, 2020
@dibarbet dibarbet marked this pull request as ready for review October 27, 2020 01:37
@dibarbet dibarbet requested a review from a team as a code owner October 27, 2020 01:37
@dibarbet
Copy link
Member Author

dibarbet commented Oct 27, 2020

@CyrusNajmabadi I think I'm ready for this to be looked at now - happy to hear your thoughts! I put the overall ideas in the pr description

Copy link
Member

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LSP part of this makes sense to me (2nd commit). I don't feel too qualified to
comment on the first commit too much :P

_undoManager = undoManager;
}

protected override bool IsSupportedOpeningBrace(char openingBrace)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code in here was moved to IBraceCompletionService.IsValidForBraceCompletion


namespace Microsoft.CodeAnalysis.Editor.Implementation.AutomaticCompletion.Sessions
{
internal abstract class AbstractTokenBraceCompletionSession : IEditorBraceCompletionSession
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to AbstractTokenBraceCompletionService

_undoHistoryRegistry = undoHistoryRegistry;
}

public virtual void AfterReturn(IBraceCompletionSession session, CancellationToken cancellationToken)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is code originally from CurlyBraceCompletionSession.AfterReturn, but heavily modified to generically apply any IBraceCompletionService.GetTextChangesAfterReturn

Currently the only implementation though is still CurlyBraceCompletionService

&& await CheckClosingTokenKindAsync(context.Document, context.ClosingPoint, cancellationToken).ConfigureAwait(false);
}

public override async Task<BraceCompletionResult?> GetBraceCompletionAsync(BraceCompletionContext braceCompletionContext, CancellationToken cancellationToken)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this code implements the CurlyBraceCompletionSession.AfterStart functionality (along with providing the actual closing brace edit)

}
}

public override async Task<BraceCompletionResult?> GetTextChangeAfterReturnAsync(BraceCompletionContext context, CancellationToken cancellationToken, bool supportsVirtualSpace = true)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comes from CurlyBraceCompletionSession.AfterStart, with modifications to use documents and supporting non-virtual spaces for the caret location

return Collapse(newDocumentText, changes.ToImmutableArray());
}

private static async Task<SourceText> FormatTrackingSpanAsync(Document document, int openingPoint, int closingPoint, bool shouldHonorAutoFormattingOnCloseBraceOption, CancellationToken cancellationToken, ImmutableArray<AbstractFormattingRule>? rules = null)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is from the original method in CurlyBraceCompletionSession.FormatTrackingSpan, with extra logic added to apply the formatting to the document instead of editor subject buffer at the end.


namespace Microsoft.CodeAnalysis.BraceCompletion
{
internal abstract class AbstractTokenBraceCompletionService : AbstractBraceCompletionService
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from abstracttokenbracecompletionsession

{
internal class BraceCompletionOptions
{
public static readonly PerLanguageOption2<bool> AutoFormattingOnCloseBrace = new(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to move these options down to the features layer

Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

<Export(LanguageNames.VisualBasic, GetType(IBraceCompletionService)), [Shared]>
Friend Class BracketBraceCompletionService
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much all the VB implementation was just a straight move.


// We can be starting an interpolation expression if we're inside an interpolated string.
return token.Parent.IsKind(SyntaxKind.InterpolatedStringExpression) || token.Parent.IsParentKind(SyntaxKind.InterpolatedStringExpression);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as before, i don't understand most of this. so primary question is if this is tested well. if so, i'm ok.

Copy link
Member

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. handle nits as you see fit.

please confirm appropriate testing of some of hte hairier sections of code. if not covered well, please beef things up :)

@dibarbet
Copy link
Member Author

dibarbet commented Dec 4, 2020

@dotnet/roslyn-compiler / @RikkiGibson Need a review on the compiler changes in 4429701 (should include all compiler changes).

This extracts out the textchangerange merging logic into a shared helper. Normally the merging of text changes between versions is handled internally in SourceText (via ChangedText). However, the documents in the IDE are based on ChangedSourceText which is a custom implementation that uses VS editor snapshots to merge text changes between versions. Unfortunately I need to merge a set of text changes originally created from a VS editor snapshot, but the intermediate source text versions do not have corresponding VS editor snapshots. So ChangedSourceText is not able to accurately compute the merged text changes and always returns the whole file as the diff.

To resolve this, I extracted out the merge logic from ChangedText so that I could manually merge the text changes between versions myself. I did not want to copy the VS snapshot text into a new SourceText either as that could be expensive. Note the changes to be merged are relatively simple - a set of formatting changes around a span being merged into a text change that only inserts a new line.

@RikkiGibson RikkiGibson self-assigned this Dec 4, 2020
/// <remarks>
/// Both `oldChanges` and `newChanges` must contain non-overlapping spans in ascending order.
/// </remarks>
public static ImmutableArray<TextChangeRange> Merge(ImmutableArray<TextChangeRange> oldChanges, ImmutableArray<TextChangeRange> newChanges)
Copy link
Contributor

@RikkiGibson RikkiGibson Dec 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider moving back to the original location and just changing the accessibility to internal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are not any IVTs to Features.CSharp from MS.CA, and adding IVTs causes tons of build errors as now it thinks there are duplicate types (e.g. ArrayBuilder, DeclarationInfo, nullable attributes).

@dibarbet
Copy link
Member Author

dibarbet commented Dec 4, 2020

@dotnet/roslyn-compiler could use one more review (thanks Rikki!). Context is here - #48468 (comment)

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compiler changes lgtm (commit 37)

@dibarbet dibarbet merged commit 85bb2da into dotnet:master Dec 7, 2020
@ghost ghost added this to the Next milestone Dec 7, 2020
@dibarbet dibarbet deleted the refactor_brace_completion branch December 7, 2020 22:48
@dibarbet dibarbet modified the milestones: Next, 16.9.P3 Dec 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.