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

Global indentation options - take 2 #60565

Merged
merged 3 commits into from
Apr 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Indentation;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;

#if CODE_STYLE
using OptionSet = Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions;
#else
using Microsoft.CodeAnalysis.Options;
#endif

namespace Microsoft.CodeAnalysis.CSharp.ConvertNamespace
{
using static ConvertNamespaceAnalysis;
Expand Down Expand Up @@ -60,7 +69,14 @@ protected override async Task FixAllAsync(
var diagnostic = diagnostics.First();

var namespaceDecl = (BaseNamespaceDeclarationSyntax)diagnostic.AdditionalLocations[0].FindNode(cancellationToken);
var converted = await ConvertAsync(document, namespaceDecl, cancellationToken).ConfigureAwait(false);

#if CODE_STYLE
var configOptions = document.Project.AnalyzerOptions.GetAnalyzerOptionSet(namespaceDecl.SyntaxTree, cancellationToken);
var options = CSharpSyntaxFormattingOptions.Create(configOptions);
#else
var options = await SyntaxFormattingOptions.FromDocumentAsync(document, cancellationToken).ConfigureAwait(false);
#endif
var converted = await ConvertAsync(document, namespaceDecl, options, cancellationToken).ConfigureAwait(false);

editor.ReplaceNode(
editor.OriginalRoot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,29 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertNamespace
{
internal static class ConvertNamespaceTransform
{
public static async Task<Document> ConvertAsync(Document document, BaseNamespaceDeclarationSyntax baseNamespace, CancellationToken cancellationToken)
public static async Task<Document> ConvertAsync(Document document, BaseNamespaceDeclarationSyntax baseNamespace, SyntaxFormattingOptions options, CancellationToken cancellationToken)
{
switch (baseNamespace)
{
case FileScopedNamespaceDeclarationSyntax fileScopedNamespace:
return await ConvertFileScopedNamespaceAsync(document, fileScopedNamespace, cancellationToken).ConfigureAwait(false);

case NamespaceDeclarationSyntax namespaceDeclaration:
var (doc, _) = await ConvertNamespaceDeclarationAsync(document, namespaceDeclaration, cancellationToken).ConfigureAwait(false);
var (doc, _) = await ConvertNamespaceDeclarationAsync(document, namespaceDeclaration, options, cancellationToken).ConfigureAwait(false);
return doc;

default:
throw ExceptionUtilities.UnexpectedValue(baseNamespace.Kind());
}
}

public static async Task<(Document document, TextSpan semicolonSpan)> ConvertNamespaceDeclarationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, CancellationToken cancellationToken)
public static async Task<(Document document, TextSpan semicolonSpan)> ConvertNamespaceDeclarationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken)
{
// First, determine how much indentation we had inside the original block namespace. We'll attempt to remove
// that much indentation from each applicable line after we conver the block namespace to a file scoped
// namespace.
var indentation = await GetIndentationAsync(document, namespaceDeclaration, cancellationToken).ConfigureAwait(false);

var indentation = await GetIndentationAsync(document, namespaceDeclaration, options, cancellationToken).ConfigureAwait(false);

// Next, actually replace the block namespace with the file scoped namespace.
var annotation = new SyntaxAnnotation();
Expand All @@ -77,7 +78,7 @@ public static async Task<Document> ConvertAsync(Document document, BaseNamespace
return (document.WithSyntaxRoot(updatedRoot), fileScopedNamespace.SemicolonToken.Span);
}

private static async Task<string?> GetIndentationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, CancellationToken cancellationToken)
private static async Task<string?> GetIndentationAsync(Document document, NamespaceDeclarationSyntax namespaceDeclaration, SyntaxFormattingOptions options, CancellationToken cancellationToken)
{
var indentationService = document.GetRequiredLanguageService<IIndentationService>();
var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
Expand All @@ -87,19 +88,12 @@ public static async Task<Document> ConvertAsync(Document document, BaseNamespace
if (openBraceLine == closeBraceLine)
return null;

#if CODE_STYLE
var options = document.Project.AnalyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(namespaceDeclaration.SyntaxTree!);
#else
var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
#endif
var style = options.GetOption(FormattingOptions2.SmartIndent, document.Project.Language);

var indentation = indentationService.GetIndentation(document, openBraceLine + 1, (FormattingOptions.IndentStyle)style, cancellationToken);
// Auto-formatting options are not relevant since they only control behavior on typing.
var indentationOptions = new IndentationOptions(options, AutoFormattingOptions.Default);

var useTabs = options.GetOption(FormattingOptions2.UseTabs);
var tabSize = options.GetOption(FormattingOptions2.TabSize);
var indentation = indentationService.GetIndentation(document, openBraceLine + 1, indentationOptions, cancellationToken);

return indentation.GetIndentationString(sourceText, useTabs, tabSize);
return indentation.GetIndentationString(sourceText, options.UseTabs, options.TabSize);
}

private static async Task<(Document document, TextSpan semicolonSpan)> DedentNamespaceAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ public void ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler,
if (!ConvertNamespaceAnalysis.CanOfferUseFileScoped(s_optionSet, root, namespaceDecl, forAnalyzer: true, LanguageVersion.CSharp10))
return default;

var (converted, semicolonSpan) = ConvertNamespaceTransform.ConvertNamespaceDeclarationAsync(document, namespaceDecl, cancellationToken).WaitAndGetResult(cancellationToken);
var formattingOptions = SyntaxFormattingOptions.FromDocumentAsync(document, cancellationToken).WaitAndGetResult(cancellationToken);
var (converted, semicolonSpan) = ConvertNamespaceTransform.ConvertNamespaceDeclarationAsync(document, namespaceDecl, formattingOptions, cancellationToken).WaitAndGetResult(cancellationToken);
var text = converted.GetTextSynchronously(cancellationToken);
return (text, semicolonSpan);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using Microsoft.VisualStudio.Text.Operations;
Expand All @@ -20,16 +21,17 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.DocumentationComments
[Name(PredefinedCommandHandlerNames.DocumentationComments)]
[Order(After = PredefinedCommandHandlerNames.Rename)]
[Order(After = PredefinedCompletionNames.CompletionCommandHandler)]
internal class DocumentationCommentCommandHandler
internal sealed class DocumentationCommentCommandHandler
: AbstractDocumentationCommentCommandHandler
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public DocumentationCommentCommandHandler(
IUIThreadOperationExecutor uiThreadOperationExecutor,
ITextUndoHistoryRegistry undoHistoryRegistry,
IEditorOperationsFactoryService editorOperationsFactoryService)
: base(uiThreadOperationExecutor, undoHistoryRegistry, editorOperationsFactoryService)
IEditorOperationsFactoryService editorOperationsFactoryService,
IGlobalOptionService globalOptions)
: base(uiThreadOperationExecutor, undoHistoryRegistry, editorOperationsFactoryService, globalOptions)
{
}

Expand Down
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 @@ -97,9 +97,10 @@ SyntaxKind.InterpolatedSingleLineRawStringStartToken or
return false;
}

var indentation = token.GetPreferredIndentation(document, cancellationToken);
var indentationOptions = _globalOptions.GetIndentationOptionsAsync(document, cancellationToken).WaitAndGetResult(cancellationToken);
var indentation = token.GetPreferredIndentation(document, indentationOptions, cancellationToken);

var newLine = document.Project.Solution.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp);
var newLine = indentationOptions.FormattingOptions.NewLine;

using var transaction = CaretPreservingEditTransaction.TryCreate(
CSharpEditorResources.Split_string, textView, _undoHistoryRegistry, _editorOperationsFactoryService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Indentation;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Commanding;
Expand Down Expand Up @@ -120,12 +121,12 @@ private bool SplitString(ITextView textView, ITextBuffer subjectBuffer, int posi
}

// TODO: read option from textView.Options (https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1412138)
var indentStyle = document.Project.Solution.Options.GetOption(FormattingOptions.SmartIndent, LanguageNames.CSharp);
var options = _globalOptions.GetIndentationOptionsAsync(document, cancellationToken).WaitAndGetResult(cancellationToken);

using var transaction = CaretPreservingEditTransaction.TryCreate(
CSharpEditorResources.Split_string, textView, _undoHistoryRegistry, _editorOperationsFactoryService);

var splitter = StringSplitter.TryCreate(document, position, useTabs, tabSize, indentStyle, cancellationToken);
var splitter = StringSplitter.TryCreate(document, position, options, useTabs, tabSize, cancellationToken);
if (splitter?.TrySplit(out var newDocument, out var newPosition) != true)
{
return false;
Expand Down
Loading