diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsService.cs new file mode 100644 index 0000000000..c0cdbfeed8 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsService.cs @@ -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 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 children = new List(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; + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsServiceFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsServiceFactory.cs new file mode 100644 index 0000000000..a7418928fd --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsServiceFactory.cs @@ -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(); + } + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorSyntaxFactsService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorSyntaxFactsService.cs index 1f25923d7f..96590bb165 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorSyntaxFactsService.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorSyntaxFactsService.cs @@ -134,209 +134,5 @@ namespace Microsoft.CodeAnalysis.Razor return results; } - - public override int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, int previousLineEndIndex, Func 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 children = new List(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; - } } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorCodeStyleFactsService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorCodeStyleFactsService.cs new file mode 100644 index 0000000000..045c6d8ca3 --- /dev/null +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorCodeStyleFactsService.cs @@ -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 lineProvider, int indentSize, int tabSize); + } +} diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorSyntaxFactsService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorSyntaxFactsService.cs index ded5a194b0..d63a34cda7 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorSyntaxFactsService.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorSyntaxFactsService.cs @@ -1,7 +1,6 @@ // 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.CodeAnalysis.Host; @@ -13,7 +12,5 @@ namespace Microsoft.CodeAnalysis.Razor public abstract IReadOnlyList GetClassifiedSpans(RazorSyntaxTree syntaxTree); public abstract IReadOnlyList GetTagHelperSpans(RazorSyntaxTree syntaxTree); - - public abstract int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, int previousLineEnd, Func lineProvider, int indentSize, int tabSize); } } diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorSyntaxFactsServiceExtensions.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorIndentationFactsServiceExtensions.cs similarity index 90% rename from src/Microsoft.VisualStudio.LanguageServices.Razor/RazorSyntaxFactsServiceExtensions.cs rename to src/Microsoft.VisualStudio.LanguageServices.Razor/RazorIndentationFactsServiceExtensions.cs index bf180e8293..607fce125b 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorSyntaxFactsServiceExtensions.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorIndentationFactsServiceExtensions.cs @@ -7,10 +7,10 @@ using Microsoft.VisualStudio.Text; namespace Microsoft.CodeAnalysis.Razor { - public static class RazorSyntaxFactsServiceExtensions + public static class RazorIndentationFactsServiceExtensions { public static int? GetDesiredIndentation( - this RazorSyntaxFactsService service, + this RazorIndentationFactsService service, RazorSyntaxTree syntaxTree, ITextSnapshot syntaxTreeSnapshot, ITextSnapshotLine line,