206 lines
9.2 KiB
C#
206 lines
9.2 KiB
C#
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System.Collections.Generic;
|
|
using System.Composition;
|
|
using System.Threading;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Extensions;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
using Microsoft.CodeAnalysis.Formatting;
|
|
using Microsoft.CodeAnalysis.Formatting.Rules;
|
|
using Microsoft.CodeAnalysis.Host.Mef;
|
|
using Microsoft.CodeAnalysis.LanguageServices;
|
|
using Microsoft.CodeAnalysis.Options;
|
|
using Microsoft.CodeAnalysis.Shared.Extensions;
|
|
using Microsoft.CodeAnalysis.Text;
|
|
using Roslyn.Utilities;
|
|
|
|
namespace dnSpy.Roslyn.Internal.SmartIndent.CSharp
|
|
{
|
|
[ExportLanguageService(typeof(ISynchronousIndentationService), LanguageNames.CSharp), Shared]
|
|
internal partial class CSharpIndentationService : AbstractIndentationService
|
|
{
|
|
private static readonly IFormattingRule s_instance = new FormattingRule();
|
|
|
|
protected override IFormattingRule GetSpecializedIndentationFormattingRule()
|
|
{
|
|
return s_instance;
|
|
}
|
|
|
|
protected override AbstractIndenter GetIndenter(
|
|
ISyntaxFactsService syntaxFacts, SyntaxTree syntaxTree, TextLine lineToBeIndented, IEnumerable<IFormattingRule> formattingRules, OptionSet optionSet, CancellationToken cancellationToken)
|
|
{
|
|
return new Indenter(
|
|
syntaxFacts, syntaxTree, formattingRules,
|
|
optionSet, lineToBeIndented, cancellationToken);
|
|
}
|
|
|
|
protected override bool ShouldUseSmartTokenFormatterInsteadOfIndenter(
|
|
IEnumerable<IFormattingRule> formattingRules,
|
|
SyntaxNode root,
|
|
TextLine line,
|
|
OptionSet optionSet,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
return ShouldUseSmartTokenFormatterInsteadOfIndenter(
|
|
formattingRules, (CompilationUnitSyntax)root, line, optionSet, cancellationToken);
|
|
}
|
|
|
|
public static bool ShouldUseSmartTokenFormatterInsteadOfIndenter(
|
|
IEnumerable<IFormattingRule> formattingRules,
|
|
CompilationUnitSyntax root,
|
|
TextLine line,
|
|
OptionSet optionSet,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
Contract.ThrowIfNull(formattingRules);
|
|
Contract.ThrowIfNull(root);
|
|
|
|
//if (!optionSet.GetOption(FeatureOnOffOptions.AutoFormattingOnReturn, LanguageNames.CSharp))
|
|
//{
|
|
// return false;
|
|
//}
|
|
|
|
if (optionSet.GetOption(FormattingOptions.SmartIndent, LanguageNames.CSharp) != FormattingOptions.IndentStyle.Smart)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var firstNonWhitespacePosition = line.GetFirstNonWhitespacePosition();
|
|
if (!firstNonWhitespacePosition.HasValue)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var token = root.FindToken(firstNonWhitespacePosition.Value);
|
|
if (token.IsKind(SyntaxKind.None) ||
|
|
token.SpanStart != firstNonWhitespacePosition)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// first see whether there is a line operation for current token
|
|
var previousToken = token.GetPreviousToken(includeZeroWidth: true);
|
|
|
|
// only use smart token formatter when we have two visible tokens.
|
|
if (previousToken.Kind() == SyntaxKind.None || previousToken.IsMissing)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var lineOperation = FormattingOperations.GetAdjustNewLinesOperation(formattingRules, previousToken, token, optionSet);
|
|
if (lineOperation == null || lineOperation.Option == AdjustNewLinesOption.ForceLinesIfOnSingleLine)
|
|
{
|
|
// no indentation operation, nothing to do for smart token formatter
|
|
return false;
|
|
}
|
|
|
|
// We're pressing enter between two tokens, have the formatter figure out hte appropriate
|
|
// indentation.
|
|
return true;
|
|
}
|
|
|
|
private class FormattingRule : AbstractFormattingRule
|
|
{
|
|
public override void AddIndentBlockOperations(List<IndentBlockOperation> list, SyntaxNode node, OptionSet optionSet, NextAction<IndentBlockOperation> nextOperation)
|
|
{
|
|
// these nodes should be from syntax tree from ITextSnapshot.
|
|
Contract.Requires(node.SyntaxTree != null);
|
|
Contract.Requires(node.SyntaxTree.GetText() != null);
|
|
|
|
nextOperation.Invoke(list);
|
|
|
|
ReplaceCaseIndentationRules(list, node);
|
|
|
|
if (node is BaseParameterListSyntax ||
|
|
node is TypeArgumentListSyntax ||
|
|
node is TypeParameterListSyntax ||
|
|
node.IsKind(SyntaxKind.Interpolation))
|
|
{
|
|
AddIndentBlockOperations(list, node);
|
|
return;
|
|
}
|
|
|
|
if (node is BaseArgumentListSyntax argument &&
|
|
argument.Parent.Kind() != SyntaxKind.ThisConstructorInitializer &&
|
|
!IsBracketedArgumentListMissingBrackets(argument as BracketedArgumentListSyntax))
|
|
{
|
|
AddIndentBlockOperations(list, argument);
|
|
return;
|
|
}
|
|
|
|
// only valid if the user has started to actually type a constructor initializer
|
|
if (node is ConstructorInitializerSyntax constructorInitializer &&
|
|
constructorInitializer.ArgumentList.OpenParenToken.Kind() != SyntaxKind.None &&
|
|
!constructorInitializer.ThisOrBaseKeyword.IsMissing)
|
|
{
|
|
var text = node.SyntaxTree.GetText();
|
|
|
|
// 3 different cases
|
|
// first case : this or base is the first token on line
|
|
// second case : colon is the first token on line
|
|
var colonIsFirstTokenOnLine = !constructorInitializer.ColonToken.IsMissing && constructorInitializer.ColonToken.IsFirstTokenOnLine(text);
|
|
var thisOrBaseIsFirstTokenOnLine = !constructorInitializer.ThisOrBaseKeyword.IsMissing && constructorInitializer.ThisOrBaseKeyword.IsFirstTokenOnLine(text);
|
|
|
|
if (colonIsFirstTokenOnLine || thisOrBaseIsFirstTokenOnLine)
|
|
{
|
|
list.Add(FormattingOperations.CreateRelativeIndentBlockOperation(
|
|
constructorInitializer.ThisOrBaseKeyword,
|
|
constructorInitializer.ArgumentList.OpenParenToken.GetNextToken(includeZeroWidth: true),
|
|
constructorInitializer.ArgumentList.CloseParenToken.GetPreviousToken(includeZeroWidth: true),
|
|
indentationDelta: 1,
|
|
option: IndentBlockOption.RelativePosition));
|
|
}
|
|
else
|
|
{
|
|
// third case : none of them are the first token on the line
|
|
AddIndentBlockOperations(list, constructorInitializer.ArgumentList);
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool IsBracketedArgumentListMissingBrackets(BracketedArgumentListSyntax node)
|
|
{
|
|
return node != null && node.OpenBracketToken.IsMissing && node.CloseBracketToken.IsMissing;
|
|
}
|
|
|
|
private void ReplaceCaseIndentationRules(List<IndentBlockOperation> list, SyntaxNode node)
|
|
{
|
|
var section = node as SwitchSectionSyntax;
|
|
if (section == null || section.Statements.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var startToken = section.Statements.First().GetFirstToken(includeZeroWidth: true);
|
|
var endToken = section.Statements.Last().GetLastToken(includeZeroWidth: true);
|
|
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
var operation = list[i];
|
|
if (operation.StartToken == startToken && operation.EndToken == endToken)
|
|
{
|
|
// replace operation
|
|
list[i] = FormattingOperations.CreateIndentBlockOperation(startToken, endToken, indentationDelta: 1, option: IndentBlockOption.RelativePosition);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddIndentBlockOperations(List<IndentBlockOperation> list, SyntaxNode node)
|
|
{
|
|
// only add indent block operation if the base token is the first token on line
|
|
var text = node.SyntaxTree.GetText();
|
|
var baseToken = node.Parent.GetFirstToken(includeZeroWidth: true);
|
|
|
|
list.Add(FormattingOperations.CreateRelativeIndentBlockOperation(
|
|
baseToken,
|
|
node.GetFirstToken(includeZeroWidth: true).GetNextToken(includeZeroWidth: true),
|
|
node.GetLastToken(includeZeroWidth: true).GetPreviousToken(includeZeroWidth: true),
|
|
indentationDelta: 1,
|
|
option: IndentBlockOption.RelativeToFirstTokenOnBaseTokenLine));
|
|
}
|
|
}
|
|
}
|
|
}
|