Re-introduce RazorEditorParser.

- Revived `RazorEditorParser`.
- Made `PartialParseResult` internal and renamed it to `PartialParseResultInternal`. This fell in line with other syntax tree types.
- Moved the `RazorEditorParser` implementation away from `TextChange` and `ITextBuffer`. Instead it now relies on `SourceChange` and the VS contract `ITextSnapshot`.
- Added `RazorEditorParserTest` to ensure the changes in implementation did not impact previous functionality.
- Removed some obvious tests that unnecessarily re-tested behavior that was already verified.
- Updated tests.
- Moved several Language.Test types to the common test project so they could be reused.

#1259
This commit is contained in:
N. Taylor Mullen 2017-07-10 17:21:15 -07:00
parent 0d69d98933
commit f8d43853f8
30 changed files with 2387 additions and 156 deletions

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Razor.Language
{
}
protected override PartialParseResult CanAcceptChange(Span target, SourceChange change)
protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
{
if (AcceptedCharacters == AcceptedCharactersInternal.NonWhiteSpace)
{
@ -25,11 +25,11 @@ namespace Microsoft.AspNetCore.Razor.Language
// Did not modify whitespace, directive format should be the same.
// Return provisional so extensible IR/code gen pieces can see the full directive text
// once the user stops editing the document.
return PartialParseResult.Accepted | PartialParseResult.Provisional;
return PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional;
}
}
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}

View File

@ -31,16 +31,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public string AutoCompleteString { get; set; }
protected override PartialParseResult CanAcceptChange(Span target, SourceChange change)
protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
{
if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, change)) || IsAtEndOfFirstLine(target, change)) &&
change.IsInsert &&
ParserHelpers.IsNewLine(change.NewText) &&
AutoCompleteString != null)
{
return PartialParseResult.Rejected | PartialParseResult.AutoCompleteBlock;
return PartialParseResultInternal.Rejected | PartialParseResultInternal.AutoCompleteBlock;
}
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}
public override string ToString()

View File

@ -5,13 +5,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class EditResult
{
public EditResult(PartialParseResult result, SpanBuilder editedSpan)
public EditResult(PartialParseResultInternal result, SpanBuilder editedSpan)
{
Result = result;
EditedSpan = editedSpan;
}
public PartialParseResult Result { get; set; }
public PartialParseResultInternal Result { get; set; }
public SpanBuilder EditedSpan { get; set; }
}
}

View File

@ -59,11 +59,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return hashCodeCombiner;
}
protected override PartialParseResult CanAcceptChange(Span target, SourceChange change)
protected override PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
{
if (AcceptedCharacters == AcceptedCharactersInternal.Any)
{
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}
// In some editors intellisense insertions are handled as "dotless commits". If an intellisense selection is confirmed
@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
// Don't support 0->1 length edits
if (lastChar == null)
{
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}
// Accepts cases when insertions are made at the end of a span or '.' is inserted within a span.
@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return HandleDeletion(target, lastChar.Value, change);
}
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}
// A dotless commit is the process of inserting a '.' with an intellisense selection.
@ -249,17 +249,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
return string.IsNullOrWhiteSpace(target.Content.Substring(offset));
}
private PartialParseResult HandleDotlessCommitInsertion(Span target)
private PartialParseResultInternal HandleDotlessCommitInsertion(Span target)
{
var result = PartialParseResult.Accepted;
var result = PartialParseResultInternal.Accepted;
if (!AcceptTrailingDot && target.Content.LastOrDefault() == '.')
{
result |= PartialParseResult.Provisional;
result |= PartialParseResultInternal.Provisional;
}
return result;
}
private PartialParseResult HandleReplacement(Span target, SourceChange change)
private PartialParseResultInternal HandleReplacement(Span target, SourceChange change)
{
// Special Case for IntelliSense commits.
// When IntelliSense commits, we get two changes (for example user typed "Date", then committed "DateTime" by pressing ".")
@ -268,24 +268,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
// We need partial parsing to accept case #2.
var oldText = change.GetOriginalText(target);
var result = PartialParseResult.Rejected;
var result = PartialParseResultInternal.Rejected;
if (EndsWithDot(oldText) && EndsWithDot(change.NewText))
{
result = PartialParseResult.Accepted;
result = PartialParseResultInternal.Accepted;
if (!AcceptTrailingDot)
{
result |= PartialParseResult.Provisional;
result |= PartialParseResultInternal.Provisional;
}
}
return result;
}
private PartialParseResult HandleDeletion(Span target, char previousChar, SourceChange change)
private PartialParseResultInternal HandleDeletion(Span target, char previousChar, SourceChange change)
{
// What's left after deleting?
if (previousChar == '.')
{
return TryAcceptChange(target, change, PartialParseResult.Accepted | PartialParseResult.Provisional);
return TryAcceptChange(target, change, PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional);
}
else if (ParserHelpers.IsIdentifierPart(previousChar))
{
@ -293,11 +293,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
else
{
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}
}
private PartialParseResult HandleInsertion(Span target, char previousChar, SourceChange change)
private PartialParseResultInternal HandleInsertion(Span target, char previousChar, SourceChange change)
{
// What are we inserting after?
if (previousChar == '.')
@ -310,11 +310,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
}
else
{
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}
}
private PartialParseResult HandleInsertionAfterIdPart(Span target, SourceChange change)
private PartialParseResultInternal HandleInsertionAfterIdPart(Span target, SourceChange change)
{
// If the insertion is a full identifier part, accept it
if (ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false))
@ -324,16 +324,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
else if (EndsWithDot(change.NewText))
{
// Accept it, possibly provisionally
var result = PartialParseResult.Accepted;
var result = PartialParseResultInternal.Accepted;
if (!AcceptTrailingDot)
{
result |= PartialParseResult.Provisional;
result |= PartialParseResultInternal.Provisional;
}
return TryAcceptChange(target, change, result);
}
else
{
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}
}
@ -344,22 +344,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
content.Take(content.Length - 1).All(ParserHelpers.IsIdentifierPart));
}
private PartialParseResult HandleInsertionAfterDot(Span target, SourceChange change)
private PartialParseResultInternal HandleInsertionAfterDot(Span target, SourceChange change)
{
// If the insertion is a full identifier or another dot, accept it
if (ParserHelpers.IsIdentifier(change.NewText) || change.NewText == ".")
{
return TryAcceptChange(target, change);
}
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}
private PartialParseResult TryAcceptChange(Span target, SourceChange change, PartialParseResult acceptResult = PartialParseResult.Accepted)
private PartialParseResultInternal TryAcceptChange(Span target, SourceChange change, PartialParseResultInternal acceptResult = PartialParseResultInternal.Accepted)
{
var content = change.GetEditedContent(target);
if (StartsWithKeyword(content))
{
return PartialParseResult.Rejected | PartialParseResult.SpanContextChanged;
return PartialParseResultInternal.Rejected | PartialParseResultInternal.SpanContextChanged;
}
return acceptResult;

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
/// Provisional may NOT be set with Rejected and SpanContextChanged may NOT be set with Accepted.
/// </remarks>
[Flags]
internal enum PartialParseResult
internal enum PartialParseResultInternal
{
/// <summary>
/// Indicates that the edit could not be accepted and that a reparse is underway.

View File

@ -38,14 +38,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public virtual EditResult ApplyChange(Span target, SourceChange change, bool force)
{
var result = PartialParseResult.Accepted;
var result = PartialParseResultInternal.Accepted;
if (!force)
{
result = CanAcceptChange(target, change);
}
// If the change is accepted then apply the change
if ((result & PartialParseResult.Accepted) == PartialParseResult.Accepted)
if ((result & PartialParseResultInternal.Accepted) == PartialParseResultInternal.Accepted)
{
return new EditResult(result, UpdateSpan(target, change));
}
@ -60,9 +60,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
(changeOldEnd < end || (changeOldEnd == end && AcceptedCharacters != AcceptedCharactersInternal.None));
}
protected virtual PartialParseResult CanAcceptChange(Span target, SourceChange change)
protected virtual PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
{
return PartialParseResult.Rejected;
return PartialParseResultInternal.Rejected;
}
protected virtual SpanBuilder UpdateSpan(Span target, SourceChange change)

View File

@ -0,0 +1,419 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.VisualStudio.Text;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
internal class BackgroundParser : IDisposable
{
private MainThreadState _main;
private BackgroundThread _bg;
public BackgroundParser(RazorTemplateEngine templateEngine, string filePath)
{
_main = new MainThreadState(filePath);
_bg = new BackgroundThread(_main, templateEngine, filePath);
_main.ResultsReady += (sender, args) => OnResultsReady(args);
}
/// <summary>
/// Fired on the main thread.
/// </summary>
public event EventHandler<DocumentParseCompleteEventArgs> ResultsReady;
public bool IsIdle
{
get { return _main.IsIdle; }
}
public void Start()
{
_bg.Start();
}
public void Cancel()
{
_main.Cancel();
}
public void QueueChange(SourceChange change, ITextSnapshot snapshot)
{
var edit = new Edit(change, snapshot);
_main.QueueChange(edit);
}
public void Dispose()
{
_main.Cancel();
}
public IDisposable SynchronizeMainThreadState()
{
return _main.Lock();
}
protected virtual void OnResultsReady(DocumentParseCompleteEventArgs args)
{
var handler = ResultsReady;
if (handler != null)
{
handler(this, args);
}
}
private static bool TreesAreDifferent(RazorSyntaxTree leftTree, RazorSyntaxTree rightTree, IEnumerable<Edit> edits, CancellationToken cancelToken)
{
return TreesAreDifferent(leftTree.Root, rightTree.Root, edits.Select(edit => edit.Change), cancelToken);
}
internal static bool TreesAreDifferent(Block leftTree, Block rightTree, IEnumerable<SourceChange> changes, CancellationToken cancelToken)
{
// Apply all the pending changes to the original tree
// PERF: If this becomes a bottleneck, we can probably do it the other way around,
// i.e. visit the tree and find applicable changes for each node.
foreach (var change in changes)
{
cancelToken.ThrowIfCancellationRequested();
var changeOwner = leftTree.LocateOwner(change);
// Apply the change to the tree
if (changeOwner == null)
{
return true;
}
var result = changeOwner.EditHandler.ApplyChange(changeOwner, change, force: true);
changeOwner.ReplaceWith(result.EditedSpan);
}
// Now compare the trees
var treesDifferent = !leftTree.EquivalentTo(rightTree);
return treesDifferent;
}
private abstract class ThreadStateBase
{
#if DEBUG
private int _id = -1;
#endif
protected ThreadStateBase()
{
}
[Conditional("DEBUG")]
protected void SetThreadId(int id)
{
#if DEBUG
_id = id;
#endif
}
[Conditional("DEBUG")]
protected void EnsureOnThread()
{
#if DEBUG
Debug.Assert(_id != -1, "SetThreadId was never called!");
Debug.Assert(Thread.CurrentThread.ManagedThreadId == _id, "Called from an unexpected thread!");
#endif
}
[Conditional("DEBUG")]
protected void EnsureNotOnThread()
{
#if DEBUG
Debug.Assert(_id != -1, "SetThreadId was never called!");
Debug.Assert(Thread.CurrentThread.ManagedThreadId != _id, "Called from an unexpected thread!");
#endif
}
}
private class MainThreadState : ThreadStateBase, IDisposable
{
private readonly CancellationTokenSource _cancelSource = new CancellationTokenSource();
private readonly ManualResetEventSlim _hasParcel = new ManualResetEventSlim(false);
private CancellationTokenSource _currentParcelCancelSource;
private string _fileName;
private readonly object _stateLock = new object();
private IList<Edit> _changes = new List<Edit>();
public MainThreadState(string fileName)
{
_fileName = fileName;
SetThreadId(Thread.CurrentThread.ManagedThreadId);
}
public event EventHandler<DocumentParseCompleteEventArgs> ResultsReady;
public CancellationToken CancelToken
{
get { return _cancelSource.Token; }
}
public bool IsIdle
{
get
{
lock (_stateLock)
{
return _currentParcelCancelSource == null;
}
}
}
public void Cancel()
{
EnsureOnThread();
_cancelSource.Cancel();
}
public IDisposable Lock()
{
Monitor.Enter(_stateLock);
return new DisposableAction(() => Monitor.Exit(_stateLock));
}
public void QueueChange(Edit edit)
{
EnsureOnThread();
lock (_stateLock)
{
// CurrentParcel token source is not null ==> There's a parse underway
if (_currentParcelCancelSource != null)
{
_currentParcelCancelSource.Cancel();
}
_changes.Add(edit);
_hasParcel.Set();
}
}
public WorkParcel GetParcel()
{
EnsureNotOnThread(); // Only the background thread can get a parcel
_hasParcel.Wait(_cancelSource.Token);
_hasParcel.Reset();
lock (_stateLock)
{
// Create a cancellation source for this parcel
_currentParcelCancelSource = new CancellationTokenSource();
var changes = _changes;
_changes = new List<Edit>();
return new WorkParcel(changes, _currentParcelCancelSource.Token);
}
}
public void ReturnParcel(DocumentParseCompleteEventArgs args)
{
lock (_stateLock)
{
// Clear the current parcel cancellation source
if (_currentParcelCancelSource != null)
{
_currentParcelCancelSource.Dispose();
_currentParcelCancelSource = null;
}
// If there are things waiting to be parsed, just don't fire the event because we're already out of date
if (_changes.Any())
{
return;
}
}
var handler = ResultsReady;
if (handler != null)
{
handler(this, args);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_currentParcelCancelSource != null)
{
_currentParcelCancelSource.Dispose();
_currentParcelCancelSource = null;
}
_cancelSource.Dispose();
_hasParcel.Dispose();
}
}
}
private class BackgroundThread : ThreadStateBase
{
private MainThreadState _main;
private Thread _backgroundThread;
private CancellationToken _shutdownToken;
private RazorTemplateEngine _templateEngine;
private string _filePath;
private RazorSyntaxTree _currentSyntaxTree;
private IList<Edit> _previouslyDiscarded = new List<Edit>();
public BackgroundThread(MainThreadState main, RazorTemplateEngine templateEngine, string fileName)
{
// Run on MAIN thread!
_main = main;
_shutdownToken = _main.CancelToken;
_templateEngine = templateEngine;
_filePath = fileName;
_backgroundThread = new Thread(WorkerLoop);
SetThreadId(_backgroundThread.ManagedThreadId);
}
// **** ANY THREAD ****
public void Start()
{
_backgroundThread.Start();
}
// **** BACKGROUND THREAD ****
private void WorkerLoop()
{
var fileNameOnly = Path.GetFileName(_filePath);
try
{
EnsureOnThread();
while (!_shutdownToken.IsCancellationRequested)
{
// Grab the parcel of work to do
var parcel = _main.GetParcel();
if (parcel.Edits.Any())
{
try
{
DocumentParseCompleteEventArgs args = null;
using (var linkedCancel = CancellationTokenSource.CreateLinkedTokenSource(_shutdownToken, parcel.CancelToken))
{
if (!linkedCancel.IsCancellationRequested)
{
// Collect ALL changes
List<Edit> allEdits;
if (_previouslyDiscarded != null)
{
allEdits = Enumerable.Concat(_previouslyDiscarded, parcel.Edits).ToList();
}
else
{
allEdits = parcel.Edits.ToList();
}
var finalEdit = allEdits.Last();
var results = ParseChange(finalEdit.Snapshot, linkedCancel.Token);
if (results != null && !linkedCancel.IsCancellationRequested)
{
// Clear discarded changes list
_previouslyDiscarded = null;
var treeStructureChanged = _currentSyntaxTree == null || TreesAreDifferent(_currentSyntaxTree, results.GetSyntaxTree(), allEdits, parcel.CancelToken);
_currentSyntaxTree = results.GetSyntaxTree();
// Build Arguments
args = new DocumentParseCompleteEventArgs(
finalEdit.Change,
finalEdit.Snapshot,
treeStructureChanged,
results);
}
else
{
// Parse completed but we were cancelled in the mean time. Add these to the discarded changes set
_previouslyDiscarded = allEdits;
}
}
}
if (args != null)
{
_main.ReturnParcel(args);
}
}
catch (OperationCanceledException)
{
}
}
else
{
Thread.Yield();
}
}
}
catch (OperationCanceledException)
{
// Do nothing. Just shut down.
}
finally
{
// Clean up main thread resources
_main.Dispose();
}
}
private RazorCodeDocument ParseChange(ITextSnapshot snapshot, CancellationToken token)
{
EnsureOnThread();
var sourceDocument = new TextSnapshotSourceDocument(snapshot, _filePath);
var imports = _templateEngine.GetImports(_filePath);
var codeDocument = RazorCodeDocument.Create(sourceDocument, imports);
_templateEngine.GenerateCode(codeDocument);
return codeDocument;
}
}
private class WorkParcel
{
public WorkParcel(IList<Edit> changes, CancellationToken cancelToken)
{
Edits = changes;
CancelToken = cancelToken;
}
public CancellationToken CancelToken { get; }
public IList<Edit> Edits { get; }
}
private class Edit
{
public Edit(SourceChange change, ITextSnapshot snapshot)
{
Change = change;
Snapshot = snapshot;
}
public SourceChange Change { get; }
public ITextSnapshot Snapshot { get; set; }
}
}
}

View File

@ -0,0 +1,47 @@
// 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.VisualStudio.LanguageServices.Razor
{
/// <summary>
/// Arguments for the <see cref="RazorEditorParser.DocumentParseComplete"/> event in <see cref="RazorEditorParser"/>.
/// </summary>
public sealed class DocumentParseCompleteEventArgs : EventArgs
{
public DocumentParseCompleteEventArgs(
SourceChange change,
ITextSnapshot buffer,
bool treeStructureChanged,
RazorCodeDocument codeDocument)
{
SourceChange = change;
Buffer = buffer;
TreeStructureChanged = treeStructureChanged;
CodeDocument = codeDocument;
}
/// <summary>
/// The <see cref="AspNetCore.Razor.Language.SourceChange"/> which triggered the re-parse.
/// </summary>
public SourceChange SourceChange { get; }
/// <summary>
/// The text snapshot used in the re-parse.
/// </summary>
public ITextSnapshot Buffer { get; }
/// <summary>
/// Indicates if the tree structure has actually changed since the previous re-parse.
/// </summary>
public bool TreeStructureChanged { get; }
/// <summary>
/// The result of the parsing and code generation.
/// </summary>
public RazorCodeDocument CodeDocument { get; }
}
}

View File

@ -0,0 +1,21 @@
// 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;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
[Flags]
public enum PartialParseResult
{
Rejected = 1,
Accepted = 2,
Provisional = 4,
SpanContextChanged = 8,
AutoCompleteBlock = 16
}
}

View File

@ -0,0 +1,187 @@
// 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.Diagnostics;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.VisualStudio.Text;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public class RazorEditorParser : IDisposable
{
private AspNetCore.Razor.Language.Legacy.Span _lastChangeOwner;
private AspNetCore.Razor.Language.Legacy.Span _lastAutoCompleteSpan;
private BackgroundParser _parser;
public RazorEditorParser(RazorTemplateEngine templateEngine, string filePath)
{
if (templateEngine == null)
{
throw new ArgumentNullException(nameof(templateEngine));
}
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentException(
AspNetCore.Razor.Language.Resources.ArgumentCannotBeNullOrEmpty,
nameof(filePath));
}
TemplateEngine = templateEngine;
_parser = new BackgroundParser(templateEngine, filePath);
_parser.ResultsReady += (sender, args) => OnDocumentParseComplete(args);
_parser.Start();
}
/// <summary>
/// Event fired when a full reparse of the document completes.
/// </summary>
public event EventHandler<DocumentParseCompleteEventArgs> DocumentParseComplete;
public RazorTemplateEngine TemplateEngine { get; }
// Internal for testing.
internal RazorSyntaxTree CurrentSyntaxTree { get; private set; }
// Internal for testing.
internal bool LastResultProvisional { get; private set; }
public virtual string GetAutoCompleteString()
{
if (_lastAutoCompleteSpan?.EditHandler is AutoCompleteEditHandler editHandler)
{
return editHandler.AutoCompleteString;
}
return null;
}
public virtual PartialParseResult CheckForStructureChanges(SourceChange change, ITextSnapshot snapshot)
{
if (snapshot == null)
{
throw new ArgumentNullException(nameof(snapshot));
}
var result = PartialParseResultInternal.Rejected;
using (_parser.SynchronizeMainThreadState())
{
// Check if we can partial-parse
if (CurrentSyntaxTree != null && _parser.IsIdle)
{
result = TryPartialParse(change);
}
}
// If partial parsing failed or there were outstanding parser tasks, start a full reparse
if ((result & PartialParseResultInternal.Rejected) == PartialParseResultInternal.Rejected)
{
_parser.QueueChange(change, snapshot);
}
// Otherwise, remember if this was provisionally accepted for next partial parse
LastResultProvisional = (result & PartialParseResultInternal.Provisional) == PartialParseResultInternal.Provisional;
VerifyFlagsAreValid(result);
return (PartialParseResult)result;
}
/// <summary>
/// Disposes of this parser. Should be called when the editor window is closed and the document is unloaded.
/// </summary>
public void Dispose()
{
_parser.Dispose();
GC.SuppressFinalize(this);
}
private PartialParseResultInternal TryPartialParse(SourceChange change)
{
var result = PartialParseResultInternal.Rejected;
// Try the last change owner
if (_lastChangeOwner != null && _lastChangeOwner.EditHandler.OwnsChange(_lastChangeOwner, change))
{
var editResult = _lastChangeOwner.EditHandler.ApplyChange(_lastChangeOwner, change);
result = editResult.Result;
if ((editResult.Result & PartialParseResultInternal.Rejected) != PartialParseResultInternal.Rejected)
{
_lastChangeOwner.ReplaceWith(editResult.EditedSpan);
}
return result;
}
// Locate the span responsible for this change
_lastChangeOwner = CurrentSyntaxTree.Root.LocateOwner(change);
if (LastResultProvisional)
{
// Last change owner couldn't accept this, so we must do a full reparse
result = PartialParseResultInternal.Rejected;
}
else if (_lastChangeOwner != null)
{
var editResult = _lastChangeOwner.EditHandler.ApplyChange(_lastChangeOwner, change);
result = editResult.Result;
if ((editResult.Result & PartialParseResultInternal.Rejected) != PartialParseResultInternal.Rejected)
{
_lastChangeOwner.ReplaceWith(editResult.EditedSpan);
}
if ((result & PartialParseResultInternal.AutoCompleteBlock) == PartialParseResultInternal.AutoCompleteBlock)
{
_lastAutoCompleteSpan = _lastChangeOwner;
}
else
{
_lastAutoCompleteSpan = null;
}
}
return result;
}
private void OnDocumentParseComplete(DocumentParseCompleteEventArgs args)
{
using (_parser.SynchronizeMainThreadState())
{
CurrentSyntaxTree = args.CodeDocument.GetSyntaxTree();
_lastChangeOwner = null;
}
Debug.Assert(args != null, "Event arguments cannot be null");
EventHandler<DocumentParseCompleteEventArgs> handler = DocumentParseComplete;
if (handler != null)
{
try
{
handler(this, args);
}
catch (Exception ex)
{
Debug.WriteLine("[RzEd] Document Parse Complete Handler Threw: " + ex.ToString());
}
}
}
[Conditional("DEBUG")]
private static void VerifyFlagsAreValid(PartialParseResultInternal result)
{
Debug.Assert(((result & PartialParseResultInternal.Accepted) == PartialParseResultInternal.Accepted) ||
((result & PartialParseResultInternal.Rejected) == PartialParseResultInternal.Rejected),
"Partial Parse result does not have either of Accepted or Rejected flags set");
Debug.Assert(((result & PartialParseResultInternal.Rejected) == PartialParseResultInternal.Rejected) ||
((result & PartialParseResultInternal.SpanContextChanged) != PartialParseResultInternal.SpanContextChanged),
"Partial Parse result was Accepted AND had SpanContextChanged flag set");
Debug.Assert(((result & PartialParseResultInternal.Rejected) == PartialParseResultInternal.Rejected) ||
((result & PartialParseResultInternal.AutoCompleteBlock) != PartialParseResultInternal.AutoCompleteBlock),
"Partial Parse result was Accepted AND had AutoCompleteBlock flag set");
Debug.Assert(((result & PartialParseResultInternal.Accepted) == PartialParseResultInternal.Accepted) ||
((result & PartialParseResultInternal.Provisional) != PartialParseResultInternal.Provisional),
"Partial Parse result was Rejected AND had Provisional flag set");
}
}
}

View File

@ -0,0 +1,79 @@
// 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.Text;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.VisualStudio.Text;
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
internal class TextSnapshotSourceDocument : RazorSourceDocument
{
private readonly ITextSnapshot _buffer;
private readonly RazorSourceLineCollection _lines;
public TextSnapshotSourceDocument(ITextSnapshot snapshot, string filePath)
{
if (snapshot == null)
{
throw new ArgumentNullException(nameof(snapshot));
}
if (filePath == null)
{
throw new ArgumentNullException(nameof(filePath));
}
_buffer = snapshot;
FilePath = filePath;
_lines = new DefaultRazorSourceLineCollection(this);
}
public override char this[int position] => _buffer[position];
public override Encoding Encoding => Encoding.UTF8;
public override int Length => _buffer.Length;
public override RazorSourceLineCollection Lines => _lines;
public override string FilePath { get; }
public override void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
{
if (destination == null)
{
throw new ArgumentNullException(nameof(destination));
}
if (sourceIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(sourceIndex));
}
if (destinationIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(destinationIndex));
}
if (count < 0 || count > Length - sourceIndex || count > destination.Length - destinationIndex)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
if (count == 0)
{
return;
}
for (var i = 0; i < count; i++)
{
destination[destinationIndex + i] = this[sourceIndex + i];
}
}
public override byte[] GetChecksum() => throw new NotImplementedException();
}
}

View File

@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
var result = directiveTokenHandler.CanAcceptChange(target, sourceChange);
// Assert
Assert.Equal(PartialParseResult.Accepted | PartialParseResult.Provisional, result);
Assert.Equal(PartialParseResultInternal.Accepted | PartialParseResultInternal.Provisional, result);
}
[Theory]
@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
var result = directiveTokenHandler.CanAcceptChange(target, sourceChange);
// Assert
Assert.Equal(PartialParseResult.Rejected, result);
Assert.Equal(PartialParseResultInternal.Rejected, result);
}
private class TestDirectiveTokenEditHandler : DirectiveTokenEditHandler
@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Test
{
}
public new PartialParseResult CanAcceptChange(Span target, SourceChange change)
public new PartialParseResultInternal CanAcceptChange(Span target, SourceChange change)
=> base.CanAcceptChange(target, change);
}
}

View File

@ -1,53 +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;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class StringTextBuffer : ITextBuffer, IDisposable
{
private string _buffer;
public bool Disposed { get; set; }
public StringTextBuffer(string buffer)
{
_buffer = buffer;
}
public int Length
{
get { return _buffer.Length; }
}
public int Position { get; set; }
public int Read()
{
if (Position >= _buffer.Length)
{
return -1;
}
return _buffer[Position++];
}
public int Peek()
{
if (Position >= _buffer.Length)
{
return -1;
}
return _buffer[Position];
}
public void Dispose()
{
Disposed = true;
}
public object VersionToken
{
get { return _buffer; }
}
}
}

View File

@ -1,16 +0,0 @@
@{
string hello = "Hello, World";
}
<html>
<head>
<title>Simple Page</title>
</head>
<body>
<h1>Simple Page</h1>
<p>@hello</p>
<p>
@foreach(char c in hello) {@c}
</p>
</body>
</html>

View File

@ -1,43 +0,0 @@
namespace Razor
{
#line hidden
public class Template
{
#pragma warning disable 219
private void __RazorDirectiveTokenHelpers__() {
}
#pragma warning restore 219
private static System.Object __o = null;
#pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync()
{
#line 1 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
string hello = "Hello, World";
#line default
#line hidden
#line 11 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
__o = hello;
#line default
#line hidden
#line 13 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
foreach(char c in hello) {
#line default
#line hidden
#line 13 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
__o = c;
#line default
#line hidden
#line 13 "C:\This\Path\Is\Just\For\Line\Pragmas.cshtml"
}
#line default
#line hidden
}
#pragma warning restore 1998
}
}

View File

@ -20,6 +20,7 @@
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Language\Microsoft.AspNetCore.Razor.Language.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor\Microsoft.AspNetCore.Razor.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.Test.Common\Microsoft.AspNetCore.Razor.Test.Common.csproj" />
</ItemGroup>
<ItemGroup>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,106 @@
// 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.IO;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Utilities;
namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
public class StringTextSnapshot : ITextSnapshot
{
private readonly string _content;
public StringTextSnapshot(string content)
{
_content = content;
}
public char this[int position] => _content[position];
public VisualStudio.Text.ITextBuffer TextBuffer => throw new NotImplementedException();
public IContentType ContentType => throw new NotImplementedException();
public ITextVersion Version => throw new NotImplementedException();
public int Length => _content.Length;
public int LineCount => throw new NotImplementedException();
public IEnumerable<ITextSnapshotLine> Lines => throw new NotImplementedException();
public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)
{
_content.CopyTo(sourceIndex, destination, destinationIndex, count);
}
public ITrackingPoint CreateTrackingPoint(int position, PointTrackingMode trackingMode)
{
throw new NotImplementedException();
}
public ITrackingPoint CreateTrackingPoint(int position, PointTrackingMode trackingMode, TrackingFidelityMode trackingFidelity)
{
throw new NotImplementedException();
}
public ITrackingSpan CreateTrackingSpan(VisualStudio.Text.Span span, SpanTrackingMode trackingMode)
{
throw new NotImplementedException();
}
public ITrackingSpan CreateTrackingSpan(VisualStudio.Text.Span span, SpanTrackingMode trackingMode, TrackingFidelityMode trackingFidelity)
{
throw new NotImplementedException();
}
public ITrackingSpan CreateTrackingSpan(int start, int length, SpanTrackingMode trackingMode)
{
throw new NotImplementedException();
}
public ITrackingSpan CreateTrackingSpan(int start, int length, SpanTrackingMode trackingMode, TrackingFidelityMode trackingFidelity)
{
throw new NotImplementedException();
}
public ITextSnapshotLine GetLineFromLineNumber(int lineNumber)
{
throw new NotImplementedException();
}
public ITextSnapshotLine GetLineFromPosition(int position)
{
throw new NotImplementedException();
}
public int GetLineNumberFromPosition(int position)
{
throw new NotImplementedException();
}
public string GetText(VisualStudio.Text.Span span)
{
throw new NotImplementedException();
}
public string GetText(int startIndex, int length) => _content.Substring(startIndex, length);
public string GetText() => _content;
public char[] ToCharArray(int startIndex, int length) => _content.ToCharArray();
public void Write(TextWriter writer, VisualStudio.Text.Span span)
{
throw new NotImplementedException();
}
public void Write(TextWriter writer)
{
throw new NotImplementedException();
}
}
}

View File

@ -2,8 +2,9 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public static class TestBoundAttributeDescriptorBuilderExtensions
{

View File

@ -2,8 +2,9 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public static class TestRequiredAttributeDescriptorBuilderExtensions
{

View File

@ -2,8 +2,9 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public static class TestTagHelperDescriptorBuilderExtensions
{

View File

@ -2,8 +2,9 @@
// 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;
namespace Microsoft.AspNetCore.Razor.Language
namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public static class TestTagMatchingRuleDescriptorBuilderExtensions
{