Moved GetDesiredIndentation to a new service
This commit is contained in:
parent
f6f8c15f2e
commit
32d5391ff0
|
|
@ -0,0 +1,217 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||||
|
|
||||||
|
namespace Microsoft.CodeAnalysis.Razor
|
||||||
|
{
|
||||||
|
internal class DefaultRazorIndentationFactsService : RazorIndentationFactsService
|
||||||
|
{
|
||||||
|
public override int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, int previousLineEndIndex, Func<int, string> getLineContent, int indentSize, int tabSize)
|
||||||
|
{
|
||||||
|
if (syntaxTree == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(syntaxTree));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousLineEndIndex < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(previousLineEndIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getLineContent == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(getLineContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (indentSize < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(indentSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabSize < 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(tabSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
var simulatedChange = new SourceChange(previousLineEndIndex, 0, string.Empty);
|
||||||
|
var owningSpan = LocateOwner(syntaxTree.Root, simulatedChange);
|
||||||
|
|
||||||
|
int? desiredIndentation = null;
|
||||||
|
|
||||||
|
if (owningSpan.Kind != SpanKindInternal.Code)
|
||||||
|
{
|
||||||
|
SyntaxTreeNode owningChild = owningSpan;
|
||||||
|
while ((owningChild.Parent != null) && !desiredIndentation.HasValue)
|
||||||
|
{
|
||||||
|
Block owningParent = owningChild.Parent;
|
||||||
|
List<SyntaxTreeNode> children = new List<SyntaxTreeNode>(owningParent.Children);
|
||||||
|
for (int i = 0; i < children.Count; i++)
|
||||||
|
{
|
||||||
|
SyntaxTreeNode curChild = children[i];
|
||||||
|
if (!curChild.IsBlock)
|
||||||
|
{
|
||||||
|
Span curSpan = curChild as Span;
|
||||||
|
if (curSpan.Kind == SpanKindInternal.MetaCode)
|
||||||
|
{
|
||||||
|
var extraIndent = 0;
|
||||||
|
|
||||||
|
// Dev11 337312: Only indent one level deeper if the item after the metacode is a markup block
|
||||||
|
if (i < children.Count - 1)
|
||||||
|
{
|
||||||
|
SyntaxTreeNode nextChild = children[i + 1];
|
||||||
|
if (nextChild.IsBlock && ((nextChild as Block).Type == BlockKindInternal.Markup))
|
||||||
|
{
|
||||||
|
extraIndent = indentSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't rely on the syntax trees representation of the source document because partial parses may have mutated
|
||||||
|
// the underlying SyntaxTree text buffer. Because of this, if we want to provide accurate indentations we need to
|
||||||
|
// operate on the current line representation as indicated by the provider.
|
||||||
|
var line = getLineContent(curSpan.Start.LineIndex);
|
||||||
|
desiredIndentation = GetIndentLevelOfLine(line, tabSize) + indentSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curChild == owningChild)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
owningChild = owningParent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return desiredIndentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Span LocateOwner(Block root, SourceChange change)
|
||||||
|
{
|
||||||
|
// Ask each child recursively
|
||||||
|
Span owner = null;
|
||||||
|
foreach (SyntaxTreeNode element in root.Children)
|
||||||
|
{
|
||||||
|
if (element.Start.AbsoluteIndex > change.Span.AbsoluteIndex)
|
||||||
|
{
|
||||||
|
// too far
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int elementLen = element.Length;
|
||||||
|
if (element.Start.AbsoluteIndex + elementLen < change.Span.AbsoluteIndex)
|
||||||
|
{
|
||||||
|
// not far enough
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.IsBlock)
|
||||||
|
{
|
||||||
|
Block block = element as Block;
|
||||||
|
|
||||||
|
if (element.Start.AbsoluteIndex + elementLen == change.Span.AbsoluteIndex)
|
||||||
|
{
|
||||||
|
Span lastDescendant = block.FindLastDescendentSpan();
|
||||||
|
if ((lastDescendant == null) && (block is TagHelperBlock))
|
||||||
|
{
|
||||||
|
TagHelperBlock tagHelperBlock = (TagHelperBlock)block;
|
||||||
|
if (tagHelperBlock.SourceEndTag != null)
|
||||||
|
{
|
||||||
|
lastDescendant = tagHelperBlock.SourceEndTag.FindLastDescendentSpan();
|
||||||
|
}
|
||||||
|
else if (tagHelperBlock.SourceStartTag != null)
|
||||||
|
{
|
||||||
|
lastDescendant = tagHelperBlock.SourceStartTag.FindLastDescendentSpan();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conceptually, lastDescendant should always be non-null, but runtime errs on some
|
||||||
|
// cases and makes empty blocks. Runtime will fix these issues as we find them, but make
|
||||||
|
// no guarantee that they catch them all.
|
||||||
|
if (lastDescendant == null)
|
||||||
|
{
|
||||||
|
owner = LocateOwner(block, change);
|
||||||
|
if (owner != null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (lastDescendant.EditHandler.OwnsChange(lastDescendant, change))
|
||||||
|
{
|
||||||
|
owner = lastDescendant;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
owner = LocateOwner(block, change);
|
||||||
|
if (owner != null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Span span = element as Span;
|
||||||
|
if (span.EditHandler.OwnsChange(span, change))
|
||||||
|
{
|
||||||
|
owner = span;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (owner == null)
|
||||||
|
{
|
||||||
|
TagHelperBlock tagHelperNode = root as TagHelperBlock;
|
||||||
|
if (tagHelperNode != null)
|
||||||
|
{
|
||||||
|
Block sourceStartTag = tagHelperNode.SourceStartTag;
|
||||||
|
Block sourceEndTag = tagHelperNode.SourceEndTag;
|
||||||
|
if ((sourceStartTag.Start.AbsoluteIndex <= change.Span.AbsoluteIndex) &&
|
||||||
|
(sourceStartTag.Start.AbsoluteIndex + sourceStartTag.Length >= change.Span.AbsoluteIndex))
|
||||||
|
{
|
||||||
|
// intersects the start tag
|
||||||
|
return LocateOwner(sourceStartTag, change);
|
||||||
|
}
|
||||||
|
else if ((sourceEndTag.Start.AbsoluteIndex <= change.Span.AbsoluteIndex) &&
|
||||||
|
(sourceEndTag.Start.AbsoluteIndex + sourceEndTag.Length >= change.Span.AbsoluteIndex))
|
||||||
|
{
|
||||||
|
// intersects the end tag
|
||||||
|
return LocateOwner(sourceEndTag, change);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetIndentLevelOfLine(string line, int tabSize)
|
||||||
|
{
|
||||||
|
var indentLevel = 0;
|
||||||
|
|
||||||
|
foreach (var c in line)
|
||||||
|
{
|
||||||
|
if (!char.IsWhiteSpace(c))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (c == '\t')
|
||||||
|
{
|
||||||
|
indentLevel += tabSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
indentLevel++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return indentLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Composition;
|
||||||
|
using Microsoft.CodeAnalysis.Host;
|
||||||
|
using Microsoft.CodeAnalysis.Host.Mef;
|
||||||
|
|
||||||
|
namespace Microsoft.CodeAnalysis.Razor
|
||||||
|
{
|
||||||
|
[Shared]
|
||||||
|
[ExportLanguageServiceFactory(typeof(RazorIndentationFactsService), RazorLanguage.Name, ServiceLayer.Default)]
|
||||||
|
internal class DefaultRazorIndentationFactsServiceFactory : ILanguageServiceFactory
|
||||||
|
{
|
||||||
|
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
|
||||||
|
{
|
||||||
|
return new DefaultRazorIndentationFactsService();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -134,209 +134,5 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, int previousLineEndIndex, Func<int, string> getLineContent, int indentSize, int tabSize)
|
|
||||||
{
|
|
||||||
if (syntaxTree == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(syntaxTree));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previousLineEndIndex < 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(previousLineEndIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getLineContent == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(getLineContent));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indentSize < 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(indentSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tabSize < 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(tabSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
var simulatedChange = new SourceChange(previousLineEndIndex, 0, string.Empty);
|
|
||||||
var owningSpan = LocateOwner(syntaxTree.Root, simulatedChange);
|
|
||||||
|
|
||||||
int? desiredIndentation = null;
|
|
||||||
|
|
||||||
if (owningSpan.Kind != SpanKindInternal.Code)
|
|
||||||
{
|
|
||||||
SyntaxTreeNode owningChild = owningSpan;
|
|
||||||
while ((owningChild.Parent != null) && !desiredIndentation.HasValue)
|
|
||||||
{
|
|
||||||
Block owningParent = owningChild.Parent;
|
|
||||||
List<SyntaxTreeNode> children = new List<SyntaxTreeNode>(owningParent.Children);
|
|
||||||
for (int i = 0; i < children.Count; i++)
|
|
||||||
{
|
|
||||||
SyntaxTreeNode curChild = children[i];
|
|
||||||
if (!curChild.IsBlock)
|
|
||||||
{
|
|
||||||
Span curSpan = curChild as Span;
|
|
||||||
if (curSpan.Kind == SpanKindInternal.MetaCode)
|
|
||||||
{
|
|
||||||
var extraIndent = 0;
|
|
||||||
|
|
||||||
// Dev11 337312: Only indent one level deeper if the item after the metacode is a markup block
|
|
||||||
if (i < children.Count - 1)
|
|
||||||
{
|
|
||||||
SyntaxTreeNode nextChild = children[i + 1];
|
|
||||||
if (nextChild.IsBlock && ((nextChild as Block).Type == BlockKindInternal.Markup))
|
|
||||||
{
|
|
||||||
extraIndent = indentSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't rely on the syntax trees representation of the source document because partial parses may have mutated
|
|
||||||
// the underlying SyntaxTree text buffer. Because of this, if we want to provide accurate indentations we need to
|
|
||||||
// operate on the current line representation as indicated by the provider.
|
|
||||||
var line = getLineContent(curSpan.Start.LineIndex);
|
|
||||||
desiredIndentation = GetIndentLevelOfLine(line, tabSize) + indentSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curChild == owningChild)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
owningChild = owningParent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return desiredIndentation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Span LocateOwner(Block root, SourceChange change)
|
|
||||||
{
|
|
||||||
// Ask each child recursively
|
|
||||||
Span owner = null;
|
|
||||||
foreach (SyntaxTreeNode element in root.Children)
|
|
||||||
{
|
|
||||||
if (element.Start.AbsoluteIndex > change.Span.AbsoluteIndex)
|
|
||||||
{
|
|
||||||
// too far
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int elementLen = element.Length;
|
|
||||||
if (element.Start.AbsoluteIndex + elementLen < change.Span.AbsoluteIndex)
|
|
||||||
{
|
|
||||||
// not far enough
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.IsBlock)
|
|
||||||
{
|
|
||||||
Block block = element as Block;
|
|
||||||
|
|
||||||
if (element.Start.AbsoluteIndex + elementLen == change.Span.AbsoluteIndex)
|
|
||||||
{
|
|
||||||
Span lastDescendant = block.FindLastDescendentSpan();
|
|
||||||
if ((lastDescendant == null) && (block is TagHelperBlock))
|
|
||||||
{
|
|
||||||
TagHelperBlock tagHelperBlock = (TagHelperBlock)block;
|
|
||||||
if (tagHelperBlock.SourceEndTag != null)
|
|
||||||
{
|
|
||||||
lastDescendant = tagHelperBlock.SourceEndTag.FindLastDescendentSpan();
|
|
||||||
}
|
|
||||||
else if (tagHelperBlock.SourceStartTag != null)
|
|
||||||
{
|
|
||||||
lastDescendant = tagHelperBlock.SourceStartTag.FindLastDescendentSpan();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conceptually, lastDescendant should always be non-null, but runtime errs on some
|
|
||||||
// cases and makes empty blocks. Runtime will fix these issues as we find them, but make
|
|
||||||
// no guarantee that they catch them all.
|
|
||||||
if (lastDescendant == null)
|
|
||||||
{
|
|
||||||
owner = LocateOwner(block, change);
|
|
||||||
if (owner != null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (lastDescendant.EditHandler.OwnsChange(lastDescendant, change))
|
|
||||||
{
|
|
||||||
owner = lastDescendant;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
owner = LocateOwner(block, change);
|
|
||||||
if (owner != null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Span span = element as Span;
|
|
||||||
if (span.EditHandler.OwnsChange(span, change))
|
|
||||||
{
|
|
||||||
owner = span;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (owner == null)
|
|
||||||
{
|
|
||||||
TagHelperBlock tagHelperNode = root as TagHelperBlock;
|
|
||||||
if (tagHelperNode != null)
|
|
||||||
{
|
|
||||||
Block sourceStartTag = tagHelperNode.SourceStartTag;
|
|
||||||
Block sourceEndTag = tagHelperNode.SourceEndTag;
|
|
||||||
if ((sourceStartTag.Start.AbsoluteIndex <= change.Span.AbsoluteIndex) &&
|
|
||||||
(sourceStartTag.Start.AbsoluteIndex + sourceStartTag.Length >= change.Span.AbsoluteIndex))
|
|
||||||
{
|
|
||||||
// intersects the start tag
|
|
||||||
return LocateOwner(sourceStartTag, change);
|
|
||||||
}
|
|
||||||
else if ((sourceEndTag.Start.AbsoluteIndex <= change.Span.AbsoluteIndex) &&
|
|
||||||
(sourceEndTag.Start.AbsoluteIndex + sourceEndTag.Length >= change.Span.AbsoluteIndex))
|
|
||||||
{
|
|
||||||
// intersects the end tag
|
|
||||||
return LocateOwner(sourceEndTag, change);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int GetIndentLevelOfLine(string line, int tabSize)
|
|
||||||
{
|
|
||||||
var indentLevel = 0;
|
|
||||||
|
|
||||||
foreach (var c in line)
|
|
||||||
{
|
|
||||||
if (!char.IsWhiteSpace(c))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (c == '\t')
|
|
||||||
{
|
|
||||||
indentLevel += tabSize;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
indentLevel++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return indentLevel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.CodeAnalysis.Host;
|
||||||
|
|
||||||
|
namespace Microsoft.CodeAnalysis.Razor
|
||||||
|
{
|
||||||
|
public abstract class RazorIndentationFactsService : ILanguageService
|
||||||
|
{
|
||||||
|
public abstract int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, int previousLineEnd, Func<int, string> lineProvider, int indentSize, int tabSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.AspNetCore.Razor.Language;
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
using Microsoft.CodeAnalysis.Host;
|
using Microsoft.CodeAnalysis.Host;
|
||||||
|
|
@ -13,7 +12,5 @@ namespace Microsoft.CodeAnalysis.Razor
|
||||||
public abstract IReadOnlyList<ClassifiedSpan> GetClassifiedSpans(RazorSyntaxTree syntaxTree);
|
public abstract IReadOnlyList<ClassifiedSpan> GetClassifiedSpans(RazorSyntaxTree syntaxTree);
|
||||||
|
|
||||||
public abstract IReadOnlyList<TagHelperSpan> GetTagHelperSpans(RazorSyntaxTree syntaxTree);
|
public abstract IReadOnlyList<TagHelperSpan> GetTagHelperSpans(RazorSyntaxTree syntaxTree);
|
||||||
|
|
||||||
public abstract int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, int previousLineEnd, Func<int, string> lineProvider, int indentSize, int tabSize);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ using Microsoft.VisualStudio.Text;
|
||||||
|
|
||||||
namespace Microsoft.CodeAnalysis.Razor
|
namespace Microsoft.CodeAnalysis.Razor
|
||||||
{
|
{
|
||||||
public static class RazorSyntaxFactsServiceExtensions
|
public static class RazorIndentationFactsServiceExtensions
|
||||||
{
|
{
|
||||||
public static int? GetDesiredIndentation(
|
public static int? GetDesiredIndentation(
|
||||||
this RazorSyntaxFactsService service,
|
this RazorIndentationFactsService service,
|
||||||
RazorSyntaxTree syntaxTree,
|
RazorSyntaxTree syntaxTree,
|
||||||
ITextSnapshot syntaxTreeSnapshot,
|
ITextSnapshot syntaxTreeSnapshot,
|
||||||
ITextSnapshotLine line,
|
ITextSnapshotLine line,
|
||||||
Loading…
Reference in New Issue