Skip to content

Commit

Permalink
Move IFormattingInteractionService to Editor Features and add inferre…
Browse files Browse the repository at this point in the history
…d indentation detection to its implementation.

Move GetFormattingChangesOnTypedCharacterAsync, GetFormattingChangesOnPasteAsync to ISyntaxFormattingService - these do not depend on the editor.
  • Loading branch information
tmat committed Apr 8, 2022
1 parent 99cadf4 commit ac8185a
Show file tree
Hide file tree
Showing 18 changed files with 559 additions and 546 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Indentation;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Text.Editor;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Formatting
{
[ExportLanguageService(typeof(IFormattingInteractionService), LanguageNames.CSharp), Shared]
internal partial class CSharpFormattingInteractionService : IFormattingInteractionService
{
// All the characters that might potentially trigger formatting when typed
private static readonly char[] _supportedChars = ";{}#nte:)".ToCharArray();

private readonly IIndentationManagerService _indentationManager;
private readonly IGlobalOptionService _globalOptions;

[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CSharpFormattingInteractionService(IIndentationManagerService indentationManager, IGlobalOptionService globalOptions)
{
_indentationManager = indentationManager;
_globalOptions = globalOptions;
}

public bool SupportsFormatDocument => true;
public bool SupportsFormatOnPaste => true;
public bool SupportsFormatSelection => true;
public bool SupportsFormatOnReturn => false;

public bool SupportsFormattingOnTypedCharacter(Document document, char ch)
{
var isSmartIndent = _globalOptions.GetOption(IndentationOptionsStorage.SmartIndent, LanguageNames.CSharp) == FormattingOptions2.IndentStyle.Smart;

// We consider the proper placement of a close curly or open curly when it is typed at
// the start of the line to be a smart-indentation operation. As such, even if "format
// on typing" is off, if "smart indent" is on, we'll still format this. (However, we
// won't touch anything else in the block this close curly belongs to.).
//
// See extended comment in GetFormattingChangesAsync for more details on this.
if (isSmartIndent && ch is '{' or '}')
{
return true;
}

var options = _globalOptions.GetAutoFormattingOptions(LanguageNames.CSharp);

// If format-on-typing is not on, then we don't support formatting on any other characters.
var autoFormattingOnTyping = options.FormatOnTyping;
if (!autoFormattingOnTyping)
{
return false;
}

if (ch == '}' && !options.FormatOnCloseBrace)
{
return false;
}

if (ch == ';' && !options.FormatOnSemicolon)
{
return false;
}

// don't auto format after these keys if smart indenting is not on.
if (ch is '#' or 'n' && !isSmartIndent)
{
return false;
}

return _supportedChars.Contains(ch);
}

public async Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(
Document document,
TextSpan? textSpan,
CancellationToken cancellationToken)
{
var options = await _indentationManager.GetInferredFormattingOptionsAsync(document, explicitFormat: true, cancellationToken).ConfigureAwait(false);

var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var span = textSpan ?? new TextSpan(0, root.FullSpan.Length);
var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(root, span);

var services = document.Project.Solution.Workspace.Services;
return Formatter.GetFormattedTextChanges(root, SpecializedCollections.SingletonEnumerable(formattingSpan), services, options, cancellationToken).ToImmutableArray();
}

public async Task<ImmutableArray<TextChange>> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken)
{
var service = document.GetRequiredLanguageService<ISyntaxFormattingService>();
var options = await _indentationManager.GetInferredFormattingOptionsAsync(document, explicitFormat: true, cancellationToken).ConfigureAwait(false);
return await service.GetFormattingChangesOnPasteAsync(document, textSpan, options, cancellationToken).ConfigureAwait(false);
}

Task<ImmutableArray<TextChange>> IFormattingInteractionService.GetFormattingChangesOnReturnAsync(
Document document, int caretPosition, CancellationToken cancellationToken)
=> SpecializedTasks.EmptyImmutableArray<TextChange>();

public async Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(Document document, char typedChar, int position, CancellationToken cancellationToken)
{
var service = document.GetRequiredLanguageService<ISyntaxFormattingService>();

if (await service.ShouldFormatOnTypedCharacterAsync(document, typedChar, position, cancellationToken).ConfigureAwait(false))
{
var formattingOptions = await _indentationManager.GetInferredFormattingOptionsAsync(document, explicitFormat: false, cancellationToken).ConfigureAwait(false);
var autoFormattingOptions = _globalOptions.GetAutoFormattingOptions(LanguageNames.CSharp);
var indentStyle = _globalOptions.GetOption(IndentationOptionsStorage.SmartIndent, LanguageNames.CSharp);
var indentationOptions = new IndentationOptions(formattingOptions, autoFormattingOptions, indentStyle);

return await service.GetFormattingChangesOnTypedCharacterAsync(document, position, indentationOptions, cancellationToken).ConfigureAwait(false);
}

return ImmutableArray<TextChange>.Empty;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1117,7 +1117,6 @@ public void X()
}
}";


using var session = CreateSession(code);
Assert.NotNull(session);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@ public VSTypeScriptFormattingInteractionService(IVSTypeScriptFormattingInteracti
public bool SupportsFormatOnPaste => _implementation.SupportsFormatOnPaste;
public bool SupportsFormatOnReturn => _implementation.SupportsFormatOnReturn;

public bool SupportsFormattingOnTypedCharacter(Document document, AutoFormattingOptions options, FormattingOptions2.IndentStyle indentStyle, char ch)
public bool SupportsFormattingOnTypedCharacter(Document document, char ch)
=> _implementation.SupportsFormattingOnTypedCharacter(document, ch);

public Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(Document document, TextSpan? textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken)
public Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(Document document, TextSpan? textSpan, CancellationToken cancellationToken)
=> _implementation.GetFormattingChangesAsync(document, textSpan, documentOptions: null, cancellationToken);

public Task<ImmutableArray<TextChange>> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken)
public Task<ImmutableArray<TextChange>> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken)
=> _implementation.GetFormattingChangesOnPasteAsync(document, textSpan, documentOptions: null, cancellationToken);

public Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(Document document, char typedChar, int position, IndentationOptions options, CancellationToken cancellationToken)
public Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(Document document, char typedChar, int position, CancellationToken cancellationToken)
=> _implementation.GetFormattingChangesAsync(document, typedChar, position, documentOptions: null, cancellationToken);

public Task<ImmutableArray<TextChange>> GetFormattingChangesOnReturnAsync(Document document, int position, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,8 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos

var trackingSpan = caretPosition.Value.Snapshot.CreateTrackingSpan(caretPosition.Value.Position, 0, SpanTrackingMode.EdgeInclusive);
var span = trackingSpan.GetSpan(args.SubjectBuffer.CurrentSnapshot).Span.ToTextSpan();
var formattingOptions = _indentationManager.GetInferredFormattingOptionsAsync(document, explicitFormat: false, cancellationToken).WaitAndGetResult(cancellationToken);

var changes = formattingService.GetFormattingChangesOnPasteAsync(
document, span, formattingOptions, cancellationToken).WaitAndGetResult(cancellationToken);
var changes = formattingService.GetFormattingChangesOnPasteAsync(document, span, cancellationToken).WaitAndGetResult(cancellationToken);
if (changes.IsEmpty)
{
return;
Expand Down
17 changes: 3 additions & 14 deletions src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Indentation;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
Expand Down Expand Up @@ -45,7 +43,6 @@ internal partial class FormatCommandHandler :
{
private readonly ITextUndoHistoryRegistry _undoHistoryRegistry;
private readonly IEditorOperationsFactoryService _editorOperationsFactoryService;
private readonly IIndentationManagerService _indentationManager;
private readonly IGlobalOptionService _globalOptions;

public string DisplayName => EditorFeaturesResources.Automatic_Formatting;
Expand All @@ -55,12 +52,10 @@ internal partial class FormatCommandHandler :
public FormatCommandHandler(
ITextUndoHistoryRegistry undoHistoryRegistry,
IEditorOperationsFactoryService editorOperationsFactoryService,
IIndentationManagerService indentationManager,
IGlobalOptionService globalOptions)
{
_undoHistoryRegistry = undoHistoryRegistry;
_editorOperationsFactoryService = editorOperationsFactoryService;
_indentationManager = indentationManager;
_globalOptions = globalOptions;
}

Expand All @@ -71,8 +66,7 @@ private void Format(ITextView textView, Document document, TextSpan? selectionOp
using (Logger.LogBlock(FunctionId.CommandHandler_FormatCommand, KeyValueLogMessage.Create(LogType.UserAction, m => m["Span"] = selectionOpt?.Length ?? -1), cancellationToken))
using (var transaction = CreateEditTransaction(textView, EditorFeaturesResources.Formatting))
{
var formattingOptions = _indentationManager.GetInferredFormattingOptionsAsync(document, explicitFormat: true, cancellationToken).WaitAndGetResult(cancellationToken);
var changes = formattingService.GetFormattingChangesAsync(document, selectionOpt, formattingOptions, cancellationToken).WaitAndGetResult(cancellationToken);
var changes = formattingService.GetFormattingChangesAsync(document, selectionOpt, cancellationToken).WaitAndGetResult(cancellationToken);
if (changes.IsEmpty)
{
return;
Expand Down Expand Up @@ -170,18 +164,13 @@ private void ExecuteReturnOrTypeCommandWorker(EditorCommandArgs args, Cancellati
}
else if (args is TypeCharCommandArgs typeCharArgs)
{
var autoFormattingOptions = _globalOptions.GetAutoFormattingOptions(document.Project.Language);
var indentStyle = _globalOptions.GetOption(IndentationOptionsStorage.SmartIndent, document.Project.Language);
if (!service.SupportsFormattingOnTypedCharacter(document, autoFormattingOptions, indentStyle, typeCharArgs.TypedChar))
if (!service.SupportsFormattingOnTypedCharacter(document, typeCharArgs.TypedChar))
{
return;
}

var formattingOptions = _indentationManager.GetInferredFormattingOptionsAsync(document, explicitFormat: false, cancellationToken).WaitAndGetResult(cancellationToken);
var indentationOptions = new IndentationOptions(formattingOptions, autoFormattingOptions, indentStyle);

textChanges = service.GetFormattingChangesAsync(
document, typeCharArgs.TypedChar, caretPosition.Value, indentationOptions, cancellationToken).WaitAndGetResult(cancellationToken);
document, typeCharArgs.TypedChar, caretPosition.Value, cancellationToken).WaitAndGetResult(cancellationToken);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Indentation;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.Formatting
Expand All @@ -23,25 +21,25 @@ internal interface IFormattingInteractionService : ILanguageService
/// True if this service would like to format the document based on the user typing the
/// provided character.
/// </summary>
bool SupportsFormattingOnTypedCharacter(Document document, AutoFormattingOptions options, FormattingOptions2.IndentStyle indentStyle, char ch);
bool SupportsFormattingOnTypedCharacter(Document document, char ch);

/// <summary>
/// Returns the text changes necessary to format the document. If <paramref name="textSpan"/> is provided,
/// only the text changes necessary to format that span are needed.
/// </summary>
Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(Document document, TextSpan? textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken);
Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(Document document, TextSpan? textSpan, CancellationToken cancellationToken);

/// <summary>
/// Returns the text changes necessary to format the document on paste operation.
/// </summary>
Task<ImmutableArray<TextChange>> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken);
Task<ImmutableArray<TextChange>> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, CancellationToken cancellationToken);

/// <summary>
/// Returns the text changes necessary to format the document after the user enters a
/// character. The position provided is the position of the caret in the document after
/// the character been inserted into the document.
/// </summary>
Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(Document document, char typedChar, int position, IndentationOptions options, CancellationToken cancellationToken);
Task<ImmutableArray<TextChange>> GetFormattingChangesAsync(Document document, char typedChar, int position, CancellationToken cancellationToken);

/// <summary>
/// Returns the text changes necessary to format the document after the user enters a Return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ internal sealed class CommitManager : IAsyncCompletionCommitManager

private readonly RecentItemsManager _recentItemsManager;
private readonly ITextView _textView;
private readonly IIndentationManagerService _indentationManager;
private readonly IGlobalOptionService _globalOptions;
private readonly IThreadingContext _threadingContext;

Expand All @@ -59,15 +58,13 @@ public IEnumerable<char> PotentialCommitCharacters
internal CommitManager(
ITextView textView,
RecentItemsManager recentItemsManager,
IIndentationManagerService indentationManager,
IGlobalOptionService globalOptions,
IThreadingContext threadingContext)
{
_globalOptions = globalOptions;
_threadingContext = threadingContext;
_recentItemsManager = recentItemsManager;
_textView = textView;
_indentationManager = indentationManager;
}

/// <summary>
Expand Down Expand Up @@ -290,10 +287,9 @@ private AsyncCompletionData.CommitResult Commit(

if (currentDocument != null && formattingService != null)
{
var formattingOptions = _indentationManager.GetInferredFormattingOptionsAsync(document, explicitFormat: true, cancellationToken).WaitAndGetResult(cancellationToken);
var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive);
var changes = formattingService.GetFormattingChangesAsync(
currentDocument, spanToFormat.Span.ToTextSpan(), formattingOptions, cancellationToken).WaitAndGetResult(cancellationToken);
currentDocument, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken);
currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public CommitManagerProvider(
return null;
}

return new CommitManager(textView, _recentItemsManager, _indentationManager, _globalOptions, _threadingContext);
return new CommitManager(textView, _recentItemsManager, _globalOptions, _threadingContext);
}
}
}
Loading

0 comments on commit ac8185a

Please sign in to comment.