From de23788019fc83400be7f60335eed6032c60162c Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Mon, 13 Nov 2017 12:19:28 -0800 Subject: [PATCH] Move indentation service to VS.Editor.Razor and export it. - Removed indentation service extensions and made `GetDesiredIndentation` understand `ITextSnapshot`s and `ITextLineSnapshot`. - Updated our test `StringTextSnapshot` implementation to have the required features our indentation tests require. - Updated indentation service tests to use new API. #1762 --- ...aultRazorIndentationFactsServiceFactory.cs | 19 ---- .../DefaultRazorIndentationFactsService.cs | 30 +++++-- .../RazorIndentationFactsService.cs | 6 +- .../RazorIndentationFactsServiceExtensions.cs | 30 ------- .../StringTextSnapshot.cs | 49 ++++++++-- ...DefaultRazorIndentationFactsServiceTest.cs | 89 ++++++------------- 6 files changed, 96 insertions(+), 127 deletions(-) delete mode 100644 src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsServiceFactory.cs rename src/{Microsoft.CodeAnalysis.Razor.Workspaces => Microsoft.VisualStudio.Editor.Razor}/DefaultRazorIndentationFactsService.cs (87%) rename src/{Microsoft.CodeAnalysis.Razor.Workspaces => Microsoft.VisualStudio.Editor.Razor}/RazorIndentationFactsService.cs (68%) delete mode 100644 src/Microsoft.VisualStudio.Editor.Razor/RazorIndentationFactsServiceExtensions.cs rename test/{Microsoft.CodeAnalysis.Razor.Workspaces.Test => Microsoft.VisualStudio.Editor.Razor.Test}/DefaultRazorIndentationFactsServiceTest.cs (66%) diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsServiceFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsServiceFactory.cs deleted file mode 100644 index a7418928fd..0000000000 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsServiceFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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/DefaultRazorIndentationFactsService.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorIndentationFactsService.cs similarity index 87% rename from src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsService.cs rename to src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorIndentationFactsService.cs index c8dcf8be28..76f704eb99 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultRazorIndentationFactsService.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultRazorIndentationFactsService.cs @@ -3,28 +3,38 @@ using System; using System.Collections.Generic; +using System.ComponentModel.Composition; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.Legacy; +using Microsoft.VisualStudio.Text; +using Span = Microsoft.AspNetCore.Razor.Language.Legacy.Span; -namespace Microsoft.CodeAnalysis.Razor +namespace Microsoft.VisualStudio.Editor.Razor { + [System.Composition.Shared] + [Export(typeof(RazorIndentationFactsService))] internal class DefaultRazorIndentationFactsService : RazorIndentationFactsService { - public override int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, int previousLineEndIndex, Func getLineContent, int indentSize, int tabSize) + public override int? GetDesiredIndentation( + RazorSyntaxTree syntaxTree, + ITextSnapshot syntaxTreeSnapshot, + ITextSnapshotLine line, + int indentSize, + int tabSize) { if (syntaxTree == null) { throw new ArgumentNullException(nameof(syntaxTree)); } - if (previousLineEndIndex < 0) + if (syntaxTreeSnapshot == null) { - throw new ArgumentOutOfRangeException(nameof(previousLineEndIndex)); + throw new ArgumentNullException(nameof(syntaxTreeSnapshot)); } - if (getLineContent == null) + if (line == null) { - throw new ArgumentNullException(nameof(getLineContent)); + throw new ArgumentNullException(nameof(line)); } if (indentSize < 0) @@ -37,6 +47,10 @@ namespace Microsoft.CodeAnalysis.Razor throw new ArgumentOutOfRangeException(nameof(tabSize)); } + var previousLine = line.Snapshot.GetLineFromLineNumber(line.LineNumber - 1); + var trackingPoint = previousLine.Snapshot.CreateTrackingPoint(previousLine.End, PointTrackingMode.Negative); + var previousLineEndIndex = trackingPoint.GetPosition(syntaxTreeSnapshot); + var simulatedChange = new SourceChange(previousLineEndIndex, 0, string.Empty); var owningSpan = LocateOwner(syntaxTree.Root, simulatedChange); if (owningSpan.Kind == SpanKindInternal.Code) @@ -80,8 +94,8 @@ namespace Microsoft.CodeAnalysis.Razor // 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(currentSpan.Start.LineIndex); - desiredIndentation = GetIndentLevelOfLine(line, tabSize) + indentSize; + var lineText = line.Snapshot.GetLineFromLineNumber(currentSpan.Start.LineIndex).GetText(); + desiredIndentation = GetIndentLevelOfLine(lineText, tabSize) + indentSize; } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorIndentationFactsService.cs b/src/Microsoft.VisualStudio.Editor.Razor/RazorIndentationFactsService.cs similarity index 68% rename from src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorIndentationFactsService.cs rename to src/Microsoft.VisualStudio.Editor.Razor/RazorIndentationFactsService.cs index 045c6d8ca3..01f7e6f126 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorIndentationFactsService.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/RazorIndentationFactsService.cs @@ -1,14 +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; +using Microsoft.VisualStudio.Text; -namespace Microsoft.CodeAnalysis.Razor +namespace Microsoft.VisualStudio.Editor.Razor { public abstract class RazorIndentationFactsService : ILanguageService { - public abstract int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, int previousLineEnd, Func lineProvider, int indentSize, int tabSize); + public abstract int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, ITextSnapshot syntaxTreeSnapshot, ITextSnapshotLine line, int indentSize, int tabSize); } } diff --git a/src/Microsoft.VisualStudio.Editor.Razor/RazorIndentationFactsServiceExtensions.cs b/src/Microsoft.VisualStudio.Editor.Razor/RazorIndentationFactsServiceExtensions.cs deleted file mode 100644 index 607fce125b..0000000000 --- a/src/Microsoft.VisualStudio.Editor.Razor/RazorIndentationFactsServiceExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.VisualStudio.Text; - -namespace Microsoft.CodeAnalysis.Razor -{ - public static class RazorIndentationFactsServiceExtensions - { - public static int? GetDesiredIndentation( - this RazorIndentationFactsService service, - RazorSyntaxTree syntaxTree, - ITextSnapshot syntaxTreeSnapshot, - ITextSnapshotLine line, - int indentSize, - int tabSize) - { - // The tricky thing here is that line.Snapshot is very likely newer - var previousLine = line.Snapshot.GetLineFromLineNumber(line.LineNumber - 1); - var trackingPoint = line.Snapshot.CreateTrackingPoint(line.End, PointTrackingMode.Negative); - var previousLineEnd = trackingPoint.GetPosition(syntaxTreeSnapshot); - - Func getLineContentDelegate = (lineIndex) => line.Snapshot.GetLineFromLineNumber(lineIndex).GetText(); - - return service.GetDesiredIndentation(syntaxTree, previousLineEnd, getLineContentDelegate, indentSize, tabSize); - } - } -} \ No newline at end of file diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/StringTextSnapshot.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/StringTextSnapshot.cs index 16295be713..39ef523739 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/StringTextSnapshot.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/StringTextSnapshot.cs @@ -77,7 +77,20 @@ namespace Microsoft.VisualStudio.Text return matchingLine; } - public ITrackingPoint CreateTrackingPoint(int position, PointTrackingMode trackingMode) => throw new NotImplementedException(); + public ITextSnapshotLine GetLineFromLineNumber(int lineNumber) + { + if (lineNumber < 0 || lineNumber >= _lines.Count) + { + throw new ArgumentOutOfRangeException(nameof(lineNumber)); + } + + return _lines[lineNumber]; + } + + public ITrackingPoint CreateTrackingPoint(int position, PointTrackingMode trackingMode) + { + return new SnapshotTrackingPoint(position); + } public ITrackingPoint CreateTrackingPoint(int position, PointTrackingMode trackingMode, TrackingFidelityMode trackingFidelity) => throw new NotImplementedException(); @@ -89,8 +102,6 @@ namespace Microsoft.VisualStudio.Text public ITrackingSpan CreateTrackingSpan(int start, int length, SpanTrackingMode trackingMode, TrackingFidelityMode trackingFidelity) => throw new NotImplementedException(); - public ITextSnapshotLine GetLineFromLineNumber(int lineNumber) => throw new NotImplementedException(); - public int GetLineNumberFromPosition(int position) => throw new NotImplementedException(); public string GetText(VisualStudio.Text.Span span) => throw new NotImplementedException(); @@ -133,6 +144,30 @@ namespace Microsoft.VisualStudio.Text } } + private class SnapshotTrackingPoint : ITrackingPoint + { + private readonly int _position; + + public SnapshotTrackingPoint(int position) + { + _position = position; + } + + public ITextBuffer TextBuffer => throw new NotImplementedException(); + + public PointTrackingMode TrackingMode => throw new NotImplementedException(); + + public TrackingFidelityMode TrackingFidelity => throw new NotImplementedException(); + + public char GetCharacter(ITextSnapshot snapshot) => throw new NotImplementedException(); + + public SnapshotPoint GetPoint(ITextSnapshot snapshot) => throw new NotImplementedException(); + + public int GetPosition(ITextSnapshot snapshot) => _position; + + public int GetPosition(ITextVersion version) => throw new NotImplementedException(); + } + private class SnapshotLine : ITextSnapshotLine { private readonly string _contentWithLineBreak; @@ -153,7 +188,9 @@ namespace Microsoft.VisualStudio.Text } Start = new SnapshotPoint(owner, start); + End = new SnapshotPoint(owner, start + _content.Length); Snapshot = owner; + LineNumber = (owner as StringTextSnapshot)._lines.Count; } public ITextSnapshot Snapshot { get; } @@ -172,14 +209,14 @@ namespace Microsoft.VisualStudio.Text public string GetTextIncludingLineBreak() => _contentWithLineBreak; - public int LineNumber => throw new NotImplementedException(); + public int LineNumber { get; } + + public SnapshotPoint End { get; } public SnapshotSpan Extent => throw new NotImplementedException(); public SnapshotSpan ExtentIncludingLineBreak => throw new NotImplementedException(); - public SnapshotPoint End => throw new NotImplementedException(); - public SnapshotPoint EndIncludingLineBreak => throw new NotImplementedException(); } } diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultRazorIndentationFactsServiceTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorIndentationFactsServiceTest.cs similarity index 66% rename from test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultRazorIndentationFactsServiceTest.cs rename to test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorIndentationFactsServiceTest.cs index a30aecee7d..3c4f2912c6 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/DefaultRazorIndentationFactsServiceTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultRazorIndentationFactsServiceTest.cs @@ -1,13 +1,13 @@ // 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 System.Linq; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.VisualStudio.Text; using Xunit; -namespace Microsoft.CodeAnalysis.Razor +namespace Microsoft.VisualStudio.Editor.Razor { public class DefaultRazorIndentationFactsServiceTest { @@ -15,17 +15,17 @@ namespace Microsoft.CodeAnalysis.Razor public void GetDesiredIndentation_ReturnsNull_IfOwningSpanIsCode() { // Arrange - var source = $@" + var source = new StringTextSnapshot($@" @{{ -"; +"); var syntaxTree = GetSyntaxTree(source); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, - previousLineEndIndex: GetLineEndIndexForLine(source, 1), - getLineContent: line => GetLineContent(source, line), + source, + source.GetLineFromLineNumber(2), indentSize: 4, tabSize: 1); @@ -38,17 +38,17 @@ namespace Microsoft.CodeAnalysis.Razor { // Arrange var customDirective = DirectiveDescriptor.CreateSingleLineDirective("custom"); - var source = $@" + var source = new StringTextSnapshot($@" @custom -"; +"); var syntaxTree = GetSyntaxTree(source, new[] { customDirective }); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, - previousLineEndIndex: GetLineEndIndexForLine(source, 1), - getLineContent: line => GetLineContent(source, line), + source, + source.GetLineFromLineNumber(2), indentSize: 4, tabSize: 1); @@ -60,17 +60,17 @@ namespace Microsoft.CodeAnalysis.Razor public void GetDesiredIndentation_ReturnsCorrectIndentation_ForMarkupWithinCodeBlock() { // Arrange - var source = $@"@{{ + var source = new StringTextSnapshot($@"@{{
-"; +"); var syntaxTree = GetSyntaxTree(source); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, - previousLineEndIndex: GetLineEndIndexForLine(source, 1), - getLineContent: line => GetLineContent(source, line), + source, + source.GetLineFromLineNumber(2), indentSize: 4, tabSize: 4); @@ -83,18 +83,18 @@ namespace Microsoft.CodeAnalysis.Razor { // Arrange var customDirective = DirectiveDescriptor.CreateRazorBlockDirective("custom"); - var source = $@"@custom + var source = new StringTextSnapshot($@"@custom {{
-}}"; +}}"); var syntaxTree = GetSyntaxTree(source, new[] { customDirective }); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, - previousLineEndIndex: GetLineEndIndexForLine(source, 2), - getLineContent: line => GetLineContent(source, line), + source, + source.GetLineFromLineNumber(3), indentSize: 4, tabSize: 4); @@ -106,21 +106,21 @@ namespace Microsoft.CodeAnalysis.Razor public void GetDesiredIndentation_ReturnsCorrectIndentation_ForNestedMarkupWithinCodeBlock() { // Arrange - var source = $@" + var source = new StringTextSnapshot($@"
@{{ }}
-"; +"); var syntaxTree = GetSyntaxTree(source); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, - previousLineEndIndex: GetLineEndIndexForLine(source, 3), - getLineContent: line => GetLineContent(source, line), + source, + source.GetLineFromLineNumber(4), indentSize: 4, tabSize: 4); @@ -133,20 +133,20 @@ namespace Microsoft.CodeAnalysis.Razor { // Arrange var customDirective = DirectiveDescriptor.CreateRazorBlockDirective("custom"); - var source = $@"@custom + var source = new StringTextSnapshot($@"@custom {{ @{{
}} -}}"; +}}"); var syntaxTree = GetSyntaxTree(source, new[] { customDirective }); var service = new DefaultRazorIndentationFactsService(); // Act var indentation = service.GetDesiredIndentation( syntaxTree, - previousLineEndIndex: GetLineEndIndexForLine(source, 3), - getLineContent: line => GetLineContent(source, line), + source, + source.GetLineFromLineNumber(4), indentSize: 4, tabSize: 4); @@ -154,7 +154,7 @@ namespace Microsoft.CodeAnalysis.Razor Assert.Equal(8, indentation); } - private static RazorSyntaxTree GetSyntaxTree(string source, IEnumerable directives = null) + private static RazorSyntaxTree GetSyntaxTree(StringTextSnapshot source, IEnumerable directives = null) { directives = directives ?? Enumerable.Empty(); var engine = RazorEngine.CreateDesignTime(builder => @@ -165,45 +165,12 @@ namespace Microsoft.CodeAnalysis.Razor } }); - var sourceDocument = RazorSourceDocument.Create(source, "test.cshtml"); + var sourceDocument = RazorSourceDocument.Create(source.GetText(), "test.cshtml"); var codeDocument = RazorCodeDocument.Create(sourceDocument); engine.Process(codeDocument); return codeDocument.GetSyntaxTree(); } - - private static string GetLineContent(string source, int lineIndex) - { - if (string.IsNullOrEmpty(source)) - { - return string.Empty; - } - - var lines = source.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - return lines[lineIndex]; - } - - private static int GetLineEndIndexForLine(string source, int lineIndex) - { - var absoluteIndex = 0; - if (string.IsNullOrEmpty(source)) - { - return absoluteIndex; - } - - var lines = source.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - for (var i = 0; i <= lineIndex; i++) - { - absoluteIndex += lines[i].Length; - - if (i < lineIndex) - { - absoluteIndex += Environment.NewLine.Length; - } - } - - return absoluteIndex; - } } }