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
This commit is contained in:
N. Taylor Mullen 2017-11-13 12:19:28 -08:00
parent 44a47182b2
commit de23788019
6 changed files with 96 additions and 127 deletions

View File

@ -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();
}
}
}

View File

@ -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<int, string> 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;
}
}

View File

@ -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<int, string> lineProvider, int indentSize, int tabSize);
public abstract int? GetDesiredIndentation(RazorSyntaxTree syntaxTree, ITextSnapshot syntaxTreeSnapshot, ITextSnapshotLine line, int indentSize, int tabSize);
}
}

View File

@ -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<int, string> getLineContentDelegate = (lineIndex) => line.Snapshot.GetLineFromLineNumber(lineIndex).GetText();
return service.GetDesiredIndentation(syntaxTree, previousLineEnd, getLineContentDelegate, indentSize, tabSize);
}
}
}

View File

@ -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();
}
}

View File

@ -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($@"@{{
<div>
";
");
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
{{
<div>
}}";
}}");
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($@"
<div>
@{{
<span>
}}
</div>
";
");
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
{{
@{{
<div>
}}
}}";
}}");
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<DirectiveDescriptor> directives = null)
private static RazorSyntaxTree GetSyntaxTree(StringTextSnapshot source, IEnumerable<DirectiveDescriptor> directives = null)
{
directives = directives ?? Enumerable.Empty<DirectiveDescriptor>();
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;
}
}
}