Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Web.Razor.Editor;
using System.Web.Razor.Text;
using System.Web.Razor.Tokenizer.Symbols;
using Microsoft.Internal.Web.Utils;
namespace System.Web.Razor.Parser.SyntaxTree
{
public class AutoCompleteEditHandler : SpanEditHandler
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public AutoCompleteEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer)
: base(tokenizer)
{
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public AutoCompleteEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, AcceptedCharacters accepted)
: base(tokenizer, accepted)
{
}
public bool AutoCompleteAtEndOfSpan { get; set; }
public string AutoCompleteString { get; set; }
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
{
if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, normalizedChange)) || IsAtEndOfFirstLine(target, normalizedChange)) &&
normalizedChange.IsInsert &&
ParserHelpers.IsNewLine(normalizedChange.NewText) &&
AutoCompleteString != null)
{
return PartialParseResult.Rejected | PartialParseResult.AutoCompleteBlock;
}
return PartialParseResult.Rejected;
}
public override string ToString()
{
return base.ToString() + ",AutoComplete:[" + (AutoCompleteString ?? "<null>") + "]" + (AutoCompleteAtEndOfSpan ? ";AtEnd" : ";AtEOL");
}
public override bool Equals(object obj)
{
AutoCompleteEditHandler other = obj as AutoCompleteEditHandler;
return base.Equals(obj) &&
other != null &&
String.Equals(other.AutoCompleteString, AutoCompleteString, StringComparison.Ordinal) &&
AutoCompleteAtEndOfSpan == other.AutoCompleteAtEndOfSpan;
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(base.GetHashCode())
.Add(AutoCompleteString)
.CombinedHash;
}
}
}

View File

@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Razor.Text;
namespace System.Web.Razor.Editor
{
internal class BackgroundParseTask : IDisposable
{
private CancellationTokenSource _cancelSource = new CancellationTokenSource();
private GeneratorResults _results;
[SuppressMessage("Microsoft.WebAPI", "CR4002:DoNotConstructTaskInstances", Justification = "This rule is not applicable to this assembly.")]
private BackgroundParseTask(RazorTemplateEngine engine, string sourceFileName, TextChange change)
{
Change = change;
Engine = engine;
SourceFileName = sourceFileName;
InnerTask = new Task(() => Run(_cancelSource.Token), _cancelSource.Token);
}
public Task InnerTask { get; private set; }
public TextChange Change { get; private set; }
public RazorTemplateEngine Engine { get; private set; }
public string SourceFileName { get; private set; }
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "It is the caller's responsibility to dispose this object, which will dispose all members of this object")]
public static BackgroundParseTask StartNew(RazorTemplateEngine engine, string sourceFileName, TextChange change)
{
BackgroundParseTask task = new BackgroundParseTask(engine, sourceFileName, change);
task.Start();
return task;
}
public void Cancel()
{
_cancelSource.Cancel();
}
[SuppressMessage("Microsoft.WebAPI", "CR4001:DoNotCallProblematicMethodsOnTask", Justification = "This rule is not applicable to this assembly.")]
public void Start()
{
InnerTask.Start();
}
[SuppressMessage("Microsoft.WebAPI", "CR4001:DoNotCallProblematicMethodsOnTask", Justification = "This rule is not applicable to this assembly.")]
public BackgroundParseTask ContinueWith(Action<GeneratorResults, BackgroundParseTask> continuation)
{
InnerTask.ContinueWith(t => RunContinuation(t, continuation));
return this;
}
private void RunContinuation(Task completed, Action<GeneratorResults, BackgroundParseTask> continuation)
{
if (!completed.IsCanceled)
{
continuation(_results, this);
}
}
internal virtual void Run(CancellationToken cancelToken)
{
if (!cancelToken.IsCancellationRequested)
{
// Seek the buffer to the beginning
Change.NewBuffer.Position = 0;
try
{
_results = Engine.GenerateCode(Change.NewBuffer, className: null, rootNamespace: null, sourceFileName: SourceFileName, cancelToken: cancelToken);
}
catch (OperationCanceledException ex)
{
if (ex.CancellationToken == cancelToken)
{
// We've been cancelled, so just return.
return;
}
else
{
// Exception was thrown for some other reason...
throw;
}
}
}
}
public void Dispose()
{
Dispose(disposing: true);
}
[SuppressMessage("Microsoft.WebAPI", "CR4001:DoNotCallProblematicMethodsOnTask", Justification = "This rule is not applicable to this assembly.")]
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (InnerTask != null)
{
InnerTask.Dispose();
InnerTask = null;
}
if (_cancelSource != null)
{
_cancelSource.Dispose();
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Web.Razor.Parser.SyntaxTree;
namespace System.Web.Razor.Editor
{
public class EditResult
{
public EditResult(PartialParseResult result, SpanBuilder editedSpan)
{
Result = result;
EditedSpan = editedSpan;
}
public PartialParseResult Result { get; set; }
public SpanBuilder EditedSpan { get; set; }
}
}

View File

@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
namespace System.Web.Razor.Editor
{
/// <summary>
/// Used within <see cref="F:SpanEditHandler.EditorHints"/>.
/// </summary>
[Flags]
public enum EditorHints
{
/// <summary>
/// The default (Markup or Code) editor behavior for Statement completion should be used.
/// Editors can always use the default behavior, even if the span is labeled with a different CompletionType.
/// </summary>
None = 0, // 0000 0000
/// <summary>
/// Indicates that Virtual Path completion should be used for this span if the editor supports it.
/// Editors need not support this mode of completion, and will use the default (<see cref="F:EditorHints.None"/>) behavior
/// if they do not support it.
/// </summary>
VirtualPath = 1, // 0000 0001
/// <summary>
/// Indicates that this span's content contains the path to the layout page for this document.
/// </summary>
LayoutPage = 2, // 0000 0010
}
}

View File

@@ -0,0 +1,236 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web.Razor.Parser;
using System.Web.Razor.Parser.SyntaxTree;
using System.Web.Razor.Text;
using System.Web.Razor.Tokenizer.Symbols;
using Microsoft.Internal.Web.Utils;
namespace System.Web.Razor.Editor
{
public class ImplicitExpressionEditHandler : SpanEditHandler
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public ImplicitExpressionEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, ISet<string> keywords, bool acceptTrailingDot)
: base(tokenizer)
{
Initialize(keywords, acceptTrailingDot);
}
public bool AcceptTrailingDot { get; private set; }
public ISet<string> Keywords { get; private set; }
public override string ToString()
{
return String.Format(CultureInfo.InvariantCulture, "{0};ImplicitExpression[{1}];K{2}", base.ToString(), AcceptTrailingDot ? "ATD" : "RTD", Keywords.Count);
}
public override bool Equals(object obj)
{
ImplicitExpressionEditHandler other = obj as ImplicitExpressionEditHandler;
return other != null &&
base.Equals(other) &&
Keywords.SetEquals(other.Keywords) &&
AcceptTrailingDot == other.AcceptTrailingDot;
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(base.GetHashCode())
.Add(AcceptTrailingDot)
.Add(Keywords)
.CombinedHash;
}
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
{
if (AcceptedCharacters == AcceptedCharacters.Any)
{
return PartialParseResult.Rejected;
}
if (IsAcceptableReplace(target, normalizedChange))
{
return HandleReplacement(target, normalizedChange);
}
int changeRelativePosition = normalizedChange.OldPosition - target.Start.AbsoluteIndex;
// Get the edit context
char? lastChar = null;
if (changeRelativePosition > 0 && target.Content.Length > 0)
{
lastChar = target.Content[changeRelativePosition - 1];
}
// Don't support 0->1 length edits
if (lastChar == null)
{
return PartialParseResult.Rejected;
}
// Only support insertions at the end of the span
if (IsAcceptableInsertion(target, normalizedChange))
{
// Handle the insertion
return HandleInsertion(target, lastChar.Value, normalizedChange);
}
if (IsAcceptableDeletion(target, normalizedChange))
{
return HandleDeletion(target, lastChar.Value, normalizedChange);
}
return PartialParseResult.Rejected;
}
private void Initialize(ISet<string> keywords, bool acceptTrailingDot)
{
Keywords = keywords ?? new HashSet<string>();
AcceptTrailingDot = acceptTrailingDot;
}
private static bool IsAcceptableReplace(Span target, TextChange change)
{
return IsEndReplace(target, change) ||
(change.IsReplace && RemainingIsWhitespace(target, change));
}
private static bool IsAcceptableDeletion(Span target, TextChange change)
{
return IsEndDeletion(target, change) ||
(change.IsDelete && RemainingIsWhitespace(target, change));
}
private static bool IsAcceptableInsertion(Span target, TextChange change)
{
return IsEndInsertion(target, change) ||
(change.IsInsert && RemainingIsWhitespace(target, change));
}
private static bool RemainingIsWhitespace(Span target, TextChange change)
{
int offset = (change.OldPosition - target.Start.AbsoluteIndex) + change.OldLength;
return String.IsNullOrWhiteSpace(target.Content.Substring(offset));
}
private PartialParseResult HandleReplacement(Span target, TextChange change)
{
// Special Case for IntelliSense commits.
// When IntelliSense commits, we get two changes (for example user typed "Date", then committed "DateTime" by pressing ".")
// 1. Insert "." at the end of this span
// 2. Replace the "Date." at the end of the span with "DateTime."
// We need partial parsing to accept case #2.
string oldText = GetOldText(target, change);
PartialParseResult result = PartialParseResult.Rejected;
if (EndsWithDot(oldText) && EndsWithDot(change.NewText))
{
result = PartialParseResult.Accepted;
if (!AcceptTrailingDot)
{
result |= PartialParseResult.Provisional;
}
}
return result;
}
private PartialParseResult HandleDeletion(Span target, char previousChar, TextChange change)
{
// What's left after deleting?
if (previousChar == '.')
{
return TryAcceptChange(target, change, PartialParseResult.Accepted | PartialParseResult.Provisional);
}
else if (ParserHelpers.IsIdentifierPart(previousChar))
{
return TryAcceptChange(target, change);
}
else
{
return PartialParseResult.Rejected;
}
}
private PartialParseResult HandleInsertion(Span target, char previousChar, TextChange change)
{
// What are we inserting after?
if (previousChar == '.')
{
return HandleInsertionAfterDot(target, change);
}
else if (ParserHelpers.IsIdentifierPart(previousChar) || previousChar == ')' || previousChar == ']')
{
return HandleInsertionAfterIdPart(target, change);
}
else
{
return PartialParseResult.Rejected;
}
}
private PartialParseResult HandleInsertionAfterIdPart(Span target, TextChange change)
{
// If the insertion is a full identifier part, accept it
if (ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false))
{
return TryAcceptChange(target, change);
}
else if (EndsWithDot(change.NewText))
{
// Accept it, possibly provisionally
PartialParseResult result = PartialParseResult.Accepted;
if (!AcceptTrailingDot)
{
result |= PartialParseResult.Provisional;
}
return TryAcceptChange(target, change, result);
}
else
{
return PartialParseResult.Rejected;
}
}
private static bool EndsWithDot(string content)
{
return (content.Length == 1 && content[0] == '.') ||
(content[content.Length - 1] == '.' &&
content.Take(content.Length - 1).All(ParserHelpers.IsIdentifierPart));
}
private PartialParseResult HandleInsertionAfterDot(Span target, TextChange change)
{
// If the insertion is a full identifier, accept it
if (ParserHelpers.IsIdentifier(change.NewText))
{
return TryAcceptChange(target, change);
}
return PartialParseResult.Rejected;
}
private PartialParseResult TryAcceptChange(Span target, TextChange change, PartialParseResult acceptResult = PartialParseResult.Accepted)
{
string content = change.ApplyChange(target);
if (StartsWithKeyword(content))
{
return PartialParseResult.Rejected | PartialParseResult.SpanContextChanged;
}
return acceptResult;
}
private bool StartsWithKeyword(string newContent)
{
using (StringReader reader = new StringReader(newContent))
{
return Keywords.Contains(reader.ReadWhile(ParserHelpers.IsIdentifierPart));
}
}
}
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Web.Razor.Parser.SyntaxTree;
using System.Web.Razor.Tokenizer.Symbols;
namespace System.Web.Razor.Editor
{
public class SingleLineMarkupEditHandler : SpanEditHandler
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public SingleLineMarkupEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer)
: base(tokenizer)
{
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public SingleLineMarkupEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, AcceptedCharacters accepted)
: base(tokenizer, accepted)
{
}
}
}

View File

@@ -0,0 +1,183 @@
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Web.Razor.Parser.SyntaxTree;
using System.Web.Razor.Text;
using System.Web.Razor.Tokenizer.Symbols;
using Microsoft.Internal.Web.Utils;
namespace System.Web.Razor.Editor
{
// Manages edits to a span
public class SpanEditHandler
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public SpanEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer)
: this(tokenizer, AcceptedCharacters.Any)
{
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public SpanEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, AcceptedCharacters accepted)
{
AcceptedCharacters = accepted;
Tokenizer = tokenizer;
}
public AcceptedCharacters AcceptedCharacters { get; set; }
/// <summary>
/// Provides a set of hints to editors which may be manipulating the document in which this span is located.
/// </summary>
public EditorHints EditorHints { get; set; }
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public Func<string, IEnumerable<ISymbol>> Tokenizer { get; set; }
public static SpanEditHandler CreateDefault()
{
return CreateDefault(s => Enumerable.Empty<ISymbol>());
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public static SpanEditHandler CreateDefault(Func<string, IEnumerable<ISymbol>> tokenizer)
{
return new SpanEditHandler(tokenizer);
}
public virtual EditResult ApplyChange(Span target, TextChange change)
{
return ApplyChange(target, change, force: false);
}
public virtual EditResult ApplyChange(Span target, TextChange change, bool force)
{
PartialParseResult result = PartialParseResult.Accepted;
TextChange normalized = change.Normalize();
if (!force)
{
result = CanAcceptChange(target, normalized);
}
// If the change is accepted then apply the change
if (result.HasFlag(PartialParseResult.Accepted))
{
return new EditResult(result, UpdateSpan(target, normalized));
}
return new EditResult(result, new SpanBuilder(target));
}
public virtual bool OwnsChange(Span target, TextChange change)
{
int end = target.Start.AbsoluteIndex + target.Length;
int changeOldEnd = change.OldPosition + change.OldLength;
return change.OldPosition >= target.Start.AbsoluteIndex &&
(changeOldEnd < end || (changeOldEnd == end && AcceptedCharacters != AcceptedCharacters.None));
}
protected virtual PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
{
return PartialParseResult.Rejected;
}
protected virtual SpanBuilder UpdateSpan(Span target, TextChange normalizedChange)
{
string newContent = normalizedChange.ApplyChange(target);
SpanBuilder newSpan = new SpanBuilder(target);
newSpan.ClearSymbols();
foreach (ISymbol sym in Tokenizer(newContent))
{
sym.OffsetStart(target.Start);
newSpan.Accept(sym);
}
if (target.Next != null)
{
SourceLocation newEnd = SourceLocationTracker.CalculateNewLocation(target.Start, newContent);
target.Next.ChangeStart(newEnd);
}
return newSpan;
}
protected internal static bool IsAtEndOfFirstLine(Span target, TextChange change)
{
int endOfFirstLine = target.Content.IndexOfAny(new char[] { (char)0x000d, (char)0x000a, (char)0x2028, (char)0x2029 });
return (endOfFirstLine == -1 || (change.OldPosition - target.Start.AbsoluteIndex) <= endOfFirstLine);
}
/// <summary>
/// Returns true if the specified change is an insertion of text at the end of this span.
/// </summary>
protected internal static bool IsEndInsertion(Span target, TextChange change)
{
return change.IsInsert && IsAtEndOfSpan(target, change);
}
/// <summary>
/// Returns true if the specified change is an insertion of text at the end of this span.
/// </summary>
protected internal static bool IsEndDeletion(Span target, TextChange change)
{
return change.IsDelete && IsAtEndOfSpan(target, change);
}
/// <summary>
/// Returns true if the specified change is a replacement of text at the end of this span.
/// </summary>
protected internal static bool IsEndReplace(Span target, TextChange change)
{
return change.IsReplace && IsAtEndOfSpan(target, change);
}
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This method should only be used on Spans")]
protected internal static bool IsAtEndOfSpan(Span target, TextChange change)
{
return (change.OldPosition + change.OldLength) == (target.Start.AbsoluteIndex + target.Length);
}
/// <summary>
/// Returns the old text referenced by the change.
/// </summary>
/// <remarks>
/// If the content has already been updated by applying the change, this data will be _invalid_
/// </remarks>
protected internal static string GetOldText(Span target, TextChange change)
{
return target.Content.Substring(change.OldPosition - target.Start.AbsoluteIndex, change.OldLength);
}
// Is the specified span to the right of this span and immediately adjacent?
internal static bool IsAdjacentOnRight(Span target, Span other)
{
return target.Start.AbsoluteIndex < other.Start.AbsoluteIndex && target.Start.AbsoluteIndex + target.Length == other.Start.AbsoluteIndex;
}
// Is the specified span to the left of this span and immediately adjacent?
internal static bool IsAdjacentOnLeft(Span target, Span other)
{
return other.Start.AbsoluteIndex < target.Start.AbsoluteIndex && other.Start.AbsoluteIndex + other.Length == target.Start.AbsoluteIndex;
}
public override string ToString()
{
return GetType().Name + ";Accepts:" + AcceptedCharacters + ((EditorHints == EditorHints.None) ? String.Empty : (";Hints: " + EditorHints.ToString()));
}
public override bool Equals(object obj)
{
SpanEditHandler other = obj as SpanEditHandler;
return other != null &&
AcceptedCharacters == other.AcceptedCharacters &&
EditorHints == other.EditorHints;
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(AcceptedCharacters)
.Add(EditorHints)
.CombinedHash;
}
}
}