diff options
author | Kirill Osenkov <github@osenkov.com> | 2018-08-15 21:13:16 +0300 |
---|---|---|
committer | Kirill Osenkov <github@osenkov.com> | 2018-08-15 21:13:16 +0300 |
commit | b407d9744a34fd4647cf3e99068a28d728fd65a2 (patch) | |
tree | e6d6cef915f32dec4f2d16b1868045143deb589f /src/Text/Impl | |
parent | cf974b266fb428d846e2e16a25a4eeb8f635c26f (diff) |
Updating to VS-Platform 15.8.519 (29d85337c7d2af248a692fd65ac37b444551e4cf).
Diffstat (limited to 'src/Text/Impl')
110 files changed, 2078 insertions, 1914 deletions
diff --git a/src/Text/Impl/BraceCompletion/BraceCompletionAggregator.cs b/src/Text/Impl/BraceCompletion/BraceCompletionAggregator.cs index fa25233..dbf866a 100644 --- a/src/Text/Impl/BraceCompletion/BraceCompletionAggregator.cs +++ b/src/Text/Impl/BraceCompletion/BraceCompletionAggregator.cs @@ -249,7 +249,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation /// This checks the type against all others until it finds one that it is /// a type of. List.Sort() does not work here since most types are unrelated. /// </summary> - private List<IContentType> SortContentTypes(List<IContentType> contentTypes) + private static List<IContentType> SortContentTypes(List<IContentType> contentTypes) { List<IContentType> sorted = new List<IContentType>(contentTypes.Count); diff --git a/src/Text/Impl/BraceCompletion/BraceCompletionAggregatorFactory.cs b/src/Text/Impl/BraceCompletion/BraceCompletionAggregatorFactory.cs index b2179b3..4795373 100644 --- a/src/Text/Impl/BraceCompletion/BraceCompletionAggregatorFactory.cs +++ b/src/Text/Impl/BraceCompletion/BraceCompletionAggregatorFactory.cs @@ -26,7 +26,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation internal IContentTypeRegistryService ContentTypeRegistryService { get; private set; } internal ITextBufferUndoManagerProvider UndoManager { get; private set; } internal IEditorOperationsFactoryService EditorOperationsFactoryService { get; private set; } - internal GuardedOperations GuardedOperations { get; private set; } + internal IGuardedOperations GuardedOperations { get; private set; } #endregion @@ -40,7 +40,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation IContentTypeRegistryService contentTypeRegistryService, ITextBufferUndoManagerProvider undoManager, IEditorOperationsFactoryService editorOperationsFactoryService, - GuardedOperations guardedOperations) + IGuardedOperations guardedOperations) { SessionProviders = sessionProviders; ContextProviders = contextProviders; diff --git a/src/Text/Impl/BraceCompletion/BraceCompletionDefaultSession.cs b/src/Text/Impl/BraceCompletion/BraceCompletionDefaultSession.cs index 6877b4e..f58b3e1 100644 --- a/src/Text/Impl/BraceCompletion/BraceCompletionDefaultSession.cs +++ b/src/Text/Impl/BraceCompletion/BraceCompletionDefaultSession.cs @@ -12,6 +12,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using System.Diagnostics; + using System.Globalization; /// <summary> /// BraceCompletionDefaultSession is a language neutral brace completion session @@ -102,7 +103,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation // insert the closing brace using (ITextEdit edit = _subjectBuffer.CreateEdit()) { - edit.Insert(closingSnapshotPoint, _closingBrace.ToString()); + edit.Insert(closingSnapshotPoint, _closingBrace.ToString(CultureInfo.CurrentCulture)); if (edit.HasFailedChanges) { @@ -125,7 +126,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation _closingPoint = SubjectBuffer.CurrentSnapshot.CreateTrackingPoint(_closingPoint.GetPoint(snapshot), PointTrackingMode.Negative); Debug.Assert(_closingPoint.GetPoint(snapshot).Position > 0 && (new SnapshotSpan(_closingPoint.GetPoint(snapshot).Subtract(1), 1)) - .GetText().Equals(_closingBrace.ToString()), "The closing point does not match the closing brace character"); + .GetText().Equals(_closingBrace.ToString(CultureInfo.CurrentCulture), System.StringComparison.Ordinal), "The closing point does not match the closing brace character"); // move the caret back between the braces _textView.Caret.MoveTo(beforePoint); diff --git a/src/Text/Impl/BraceCompletion/Options.cs b/src/Text/Impl/BraceCompletion/BraceCompletionEnabledOption.cs index ced4d14..b36dd5f 100644 --- a/src/Text/Impl/BraceCompletion/Options.cs +++ b/src/Text/Impl/BraceCompletion/BraceCompletionEnabledOption.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. // @@ -7,11 +7,9 @@ // namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation { - using Microsoft.VisualStudio.Text.Classification; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; using System.ComponentModel.Composition; - using System.Windows; [Export(typeof(EditorOptionDefinition))] [Name(DefaultTextViewOptions.BraceCompletionEnabledOptionName)] diff --git a/src/Text/Impl/BraceCompletion/BraceCompletionManager.cs b/src/Text/Impl/BraceCompletion/BraceCompletionManager.cs index 7cfe59c..b5f9689 100644 --- a/src/Text/Impl/BraceCompletion/BraceCompletionManager.cs +++ b/src/Text/Impl/BraceCompletion/BraceCompletionManager.cs @@ -9,6 +9,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation { using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Utilities; + using Microsoft.VisualStudio.Utilities; using System; using System.Diagnostics; @@ -24,7 +25,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation private readonly IBraceCompletionAggregatorFactory _sessionFactory; private readonly IBraceCompletionAggregator _sessionAggregator; private readonly ITextView _textView; - private readonly GuardedOperations _guardedOperations; + private readonly IGuardedOperations _guardedOperations; private bool _braceCompletionEnabled; @@ -36,7 +37,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation #region Constructors - internal BraceCompletionManager(ITextView textView, IBraceCompletionStack stack, IBraceCompletionAggregatorFactory sessionFactory, GuardedOperations guardedOperations) + internal BraceCompletionManager(ITextView textView, IBraceCompletionStack stack, IBraceCompletionAggregatorFactory sessionFactory, IGuardedOperations guardedOperations) { _textView = textView; _stack = stack; @@ -450,7 +451,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation } } - private bool IsSingleLine(ITrackingPoint openingPoint, ITrackingPoint closingPoint) + private static bool IsSingleLine(ITrackingPoint openingPoint, ITrackingPoint closingPoint) { if (openingPoint != null && closingPoint != null) { diff --git a/src/Text/Impl/BraceCompletion/BraceCompletionStack.cs b/src/Text/Impl/BraceCompletion/BraceCompletionStack.cs index 311d493..ac1ff29 100644 --- a/src/Text/Impl/BraceCompletion/BraceCompletionStack.cs +++ b/src/Text/Impl/BraceCompletion/BraceCompletionStack.cs @@ -11,6 +11,7 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation using Microsoft.VisualStudio.Text.BraceCompletion; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Utilities; + using Microsoft.VisualStudio.Utilities; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -31,11 +32,11 @@ namespace Microsoft.VisualStudio.Text.BraceCompletion.Implementation private IBraceCompletionAdornmentServiceFactory _adornmentServiceFactory; private IBraceCompletionAdornmentService _adornmentService; - private GuardedOperations _guardedOperations; + private IGuardedOperations _guardedOperations; #endregion #region Constructors - public BraceCompletionStack(ITextView textView, IBraceCompletionAdornmentServiceFactory adornmentFactory, GuardedOperations guardedOperations) + public BraceCompletionStack(ITextView textView, IBraceCompletionAdornmentServiceFactory adornmentFactory, IGuardedOperations guardedOperations) { _adornmentServiceFactory = adornmentFactory; _stack = new Stack<IBraceCompletionSession>(); diff --git a/src/Text/Impl/ClassificationAggregator/ClassifierAggregator.cs b/src/Text/Impl/ClassificationAggregator/ClassifierAggregator.cs index b10eee8..95b3bdf 100644 --- a/src/Text/Impl/ClassificationAggregator/ClassifierAggregator.cs +++ b/src/Text/Impl/ClassificationAggregator/ClassifierAggregator.cs @@ -37,15 +37,15 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation // Validate. if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } if (bufferTagAggregatorFactory == null) { - throw new ArgumentNullException("bufferTagAggregatorFactory"); + throw new ArgumentNullException(nameof(bufferTagAggregatorFactory)); } if (classificationTypeRegistry == null) { - throw new ArgumentNullException("classificationTypeRegistry"); + throw new ArgumentNullException(nameof(classificationTypeRegistry)); } _textBuffer = textBuffer; @@ -63,15 +63,15 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation // Validate. if (textView == null) { - throw new ArgumentNullException("textView"); + throw new ArgumentNullException(nameof(textView)); } if (viewTagAggregatorFactory == null) { - throw new ArgumentNullException("viewTagAggregatorFactory"); + throw new ArgumentNullException(nameof(viewTagAggregatorFactory)); } if (classificationTypeRegistry == null) { - throw new ArgumentNullException("classificationTypeRegistry"); + throw new ArgumentNullException(nameof(classificationTypeRegistry)); } _textBuffer = textView.TextBuffer; @@ -336,7 +336,7 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation return results; } - private int Compare(PointData a, PointData b) + private static int Compare(PointData a, PointData b) { if (a.Position == b.Position) return (b.IsStart.CompareTo(a.IsStart)); // startpoints go before end points when positions are tied diff --git a/src/Text/Impl/ClassificationAggregator/ClassifierTagger.cs b/src/Text/Impl/ClassificationAggregator/ClassifierTagger.cs index 653e768..4871c38 100644 --- a/src/Text/Impl/ClassificationAggregator/ClassifierTagger.cs +++ b/src/Text/Impl/ClassificationAggregator/ClassifierTagger.cs @@ -70,7 +70,9 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation #region IDisposable members +#pragma warning disable CA1063 // Implement IDisposable Correctly public void Dispose() +#pragma warning restore CA1063 // Implement IDisposable Correctly { foreach(var classifier in Classifiers) { diff --git a/src/Text/Impl/ClassificationAggregator/ProjectionWorkaround.cs b/src/Text/Impl/ClassificationAggregator/ProjectionWorkaround.cs index 35a5835..b2a65a3 100644 --- a/src/Text/Impl/ClassificationAggregator/ProjectionWorkaround.cs +++ b/src/Text/Impl/ClassificationAggregator/ProjectionWorkaround.cs @@ -9,12 +9,9 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation { using System; using System.Collections.Generic; - using System.Collections.ObjectModel; using System.ComponentModel.Composition; - using Microsoft.VisualStudio.Text.Differencing; using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.Text.Tagging; - using Microsoft.VisualStudio.Text.Utilities; using Microsoft.VisualStudio.Utilities; [Export(typeof(ITaggerProvider))] @@ -22,16 +19,13 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation [TagType(typeof(ClassificationTag))] internal class ProjectionWorkaroundProvider : ITaggerProvider { - [Import] - internal IDifferenceService diffService { get; set; } - public ITagger<T> CreateTagger<T>(ITextBuffer buffer) where T : ITag { IProjectionBuffer projectionBuffer = buffer as IProjectionBuffer; if (projectionBuffer == null) return null; - return new ProjectionWorkaroundTagger(projectionBuffer, diffService) as ITagger<T>; + return new ProjectionWorkaroundTagger(projectionBuffer) as ITagger<T>; } } @@ -45,82 +39,69 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation internal class ProjectionWorkaroundTagger : ITagger<ClassificationTag> { IProjectionBuffer ProjectionBuffer { get; set; } - IDifferenceService diffService; - internal ProjectionWorkaroundTagger(IProjectionBuffer projectionBuffer, IDifferenceService diffService) + internal ProjectionWorkaroundTagger(IProjectionBuffer projectionBuffer) { this.ProjectionBuffer = projectionBuffer; - this.diffService = diffService; this.ProjectionBuffer.SourceBuffersChanged += SourceSpansChanged; } #region ITagger<ClassificationTag> members public IEnumerable<ITagSpan<ClassificationTag>> GetTags(NormalizedSnapshotSpanCollection spans) { - yield break; + return Array.Empty<ITagSpan<ClassificationTag>>(); } public event EventHandler<SnapshotSpanEventArgs> TagsChanged; - #endregion #region Source span differencing + change event private void SourceSpansChanged(object sender, ProjectionSourceSpansChangedEventArgs e) { - if (e.Changes.Count == 0) + var handler = TagsChanged; + if ((handler != null) && (e.Changes.Count == 0)) { // If there weren't text changes, but there were span changes, then // send out a classification changed event over the spans that changed. - ProjectionSpanDifference difference = ProjectionSpanDiffer.DiffSourceSpans(this.diffService, e.Before, e.After); - int pos = 0; - int start = int.MaxValue; - int end = int.MinValue; - foreach (var diff in difference.DifferenceCollection) - { - pos += GetMatchSize(difference.DeletedSpans, diff.Before); - start = Math.Min(start, pos); - - // Now, for every span added in the new snapshot that replaced - // the deleted spans, add it to our span to raise changed events - // over. - for (int i = diff.Right.Start; i < diff.Right.End; i++) - { - pos += difference.InsertedSpans[i].Length; - } - - end = Math.Max(end, pos); - } + // + // We're raising a single event here so all we need is the start of the first changed span + // to the end of the last changed span (or, as we calculate it, the end of the first identical + // spans to the start of the last identical spans). + // + // Note that we are being generous in the span we raise. For example if I change the projection buffer + // from projecting (V0:[0,10)) and (V0:[10,15)) to projecting (V0:[0,5)) and (V0:[5,15)) we'll raise a snapshot changed + // event over the entire buffer even though neither the projected text nor the content type of its buffer + // changed. This case shouldn't happen very often and the cost of (falsely) raising a classification changed + // event is pretty small so this is a net perf win compared to doing a more expensive diff to get the actual + // changed span. + var leftSpans = e.Before.GetSourceSpans(); + var rightSpans = e.After.GetSourceSpans(); + var spansToCompare = Math.Min(leftSpans.Count, rightSpans.Count); - if (start != int.MaxValue && end != int.MinValue) + int start = 0; + int identicalSpansAtStart = 0; + while ((identicalSpansAtStart < spansToCompare) && (leftSpans[identicalSpansAtStart] == rightSpans[identicalSpansAtStart])) { - RaiseTagsChangedEvent(new SnapshotSpan(e.After, Span.FromBounds(start, end))); + start += rightSpans[identicalSpansAtStart].Length; + ++identicalSpansAtStart; } - } - } - private static int GetMatchSize(ReadOnlyCollection<SnapshotSpan> spans, Match match) - { - int size = 0; - if (match != null) - { - Span extent = match.Left; - for (int s = extent.Start; s < extent.End; ++s) + if ((identicalSpansAtStart < leftSpans.Count) || (identicalSpansAtStart < rightSpans.Count)) { - size += spans[s].Length; - } - } - return size; - } + // There are at least some span differences between leftSpans and rightSpans so we don't need to worry about running over. + spansToCompare -= identicalSpansAtStart; //No need to compare spans in the starting identical block. + int end = e.After.Length; + int identicalSpansAtEndPlus1 = 1; + while ((identicalSpansAtEndPlus1 <= spansToCompare) && (leftSpans[leftSpans.Count - identicalSpansAtEndPlus1] == rightSpans[rightSpans.Count - identicalSpansAtEndPlus1])) + { + end -= rightSpans[rightSpans.Count - identicalSpansAtEndPlus1].Length; + ++identicalSpansAtEndPlus1; + } - private void RaiseTagsChangedEvent(SnapshotSpan span) - { - var handler = TagsChanged; - if (handler != null) - { - handler(this, new SnapshotSpanEventArgs(span)); + handler(this, new SnapshotSpanEventArgs(new SnapshotSpan(e.After, Span.FromBounds(start, end)))); + } } } - #endregion } } diff --git a/src/Text/Impl/ClassificationType/ClassificationTypeImpl.cs b/src/Text/Impl/ClassificationType/ClassificationTypeImpl.cs index 6f3409f..5a47305 100644 --- a/src/Text/Impl/ClassificationType/ClassificationTypeImpl.cs +++ b/src/Text/Impl/ClassificationType/ClassificationTypeImpl.cs @@ -38,7 +38,7 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation public bool IsOfType(string type) { - if (this.name == type) + if (string.Equals(this.name, type, System.StringComparison.Ordinal)) return true; else if (this.baseTypes != null) { diff --git a/src/Text/Impl/ClassificationType/ClassificationTypeRegistryService.cs b/src/Text/Impl/ClassificationType/ClassificationTypeRegistryService.cs index d890eb4..6f746a0 100644 --- a/src/Text/Impl/ClassificationType/ClassificationTypeRegistryService.cs +++ b/src/Text/Impl/ClassificationType/ClassificationTypeRegistryService.cs @@ -59,12 +59,12 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation { if (type == null) { - throw new ArgumentNullException("type"); + throw new ArgumentNullException(nameof(type)); } if (baseTypes == null) { - throw new ArgumentNullException("baseTypes"); + throw new ArgumentNullException(nameof(baseTypes)); } if (ClassificationTypes.ContainsKey(type)) { @@ -94,7 +94,7 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation // Validate if (baseTypes == null) { - throw new ArgumentNullException("baseTypes"); + throw new ArgumentNullException(nameof(baseTypes)); } if (!baseTypes.GetEnumerator().MoveNext()) { @@ -115,7 +115,7 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation // Validate if (baseTypes == null) { - throw new ArgumentNullException("baseTypes"); + throw new ArgumentNullException(nameof(baseTypes)); } if (baseTypes.Length == 0) { @@ -150,7 +150,7 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation { if (_classificationTypes == null) { - _classificationTypes = new Dictionary<string, ClassificationTypeImpl>(StringComparer.InvariantCultureIgnoreCase); + _classificationTypes = new Dictionary<string, ClassificationTypeImpl>(StringComparer.OrdinalIgnoreCase); BuildClassificationTypes(_classificationTypes); } return _classificationTypes; @@ -209,7 +209,7 @@ namespace Microsoft.VisualStudio.Text.Classification.Implementation // Lazily init if (_transientClassificationTypes == null) { - _transientClassificationTypes = new Dictionary<string, ClassificationTypeImpl>(StringComparer.InvariantCultureIgnoreCase); + _transientClassificationTypes = new Dictionary<string, ClassificationTypeImpl>(StringComparer.OrdinalIgnoreCase); } List<IClassificationType> sortedBaseTypes = new List<IClassificationType>(baseTypes); diff --git a/src/Text/Impl/Commanding/EditorCommandHandlerService.cs b/src/Text/Impl/Commanding/EditorCommandHandlerService.cs index 315e55f..4ced5c3 100644 --- a/src/Text/Impl/Commanding/EditorCommandHandlerService.cs +++ b/src/Text/Impl/Commanding/EditorCommandHandlerService.cs @@ -9,6 +9,7 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Threading; using ICommandHandlerAndMetadata = System.Lazy<Microsoft.VisualStudio.Commanding.ICommandHandler, Microsoft.VisualStudio.UI.Text.Commanding.Implementation.ICommandHandlerMetadata>; +using System.Runtime.CompilerServices; namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation { @@ -124,20 +125,38 @@ namespace Microsoft.VisualStudio.UI.Text.Commanding.Implementation handlerChain(); } + if (handler is IDynamicCommandHandler<T> dynamicCommandHandler && + !dynamicCommandHandler.CanExecuteCommand(args)) + { + // Skip this one as it cannot execute the command. + continue; + } + if (commandExecutionContext == null) { commandExecutionContext = CreateCommandExecutionContext(); } - handlerChain = () => _guardedOperations.CallExtensionPoint(handler, () => handler.ExecuteCommand(args, nextHandler, commandExecutionContext)); + handlerChain = () => _guardedOperations.CallExtensionPoint(handler, + () => handler.ExecuteCommand(args, nextHandler, commandExecutionContext), + // Do not guard against cancellation exceptions, they are handled by ExecuteCommandHandlerChain + exceptionGuardFilter: (e) => !IsOperationCancelledException(e)); } ExecuteCommandHandlerChain(commandExecutionContext, handlerChain, nextCommandHandler); } } - private void ExecuteCommandHandlerChain(CommandExecutionContext commandExecutionContext, - Action handlerChain, Action nextCommandHandler) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsOperationCancelledException(Exception e) + { + return e is OperationCanceledException || e is AggregateException aggregate && aggregate.InnerExceptions.All(ie => ie is OperationCanceledException); + } + + private static void ExecuteCommandHandlerChain( + CommandExecutionContext commandExecutionContext, + Action handlerChain, + Action nextCommandHandler) { try { diff --git a/src/Text/Impl/DifferenceAlgorithm/DefaultTextDifferencingService.cs b/src/Text/Impl/DifferenceAlgorithm/DefaultTextDifferencingService.cs index 85f1156..4a0d84e 100644 --- a/src/Text/Impl/DifferenceAlgorithm/DefaultTextDifferencingService.cs +++ b/src/Text/Impl/DifferenceAlgorithm/DefaultTextDifferencingService.cs @@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation } else { - throw new ArgumentOutOfRangeException("differenceOptions"); + throw new ArgumentOutOfRangeException(nameof(differenceOptions)); } return DiffText(left, right, type, differenceOptions); @@ -92,7 +92,7 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation } else { - throw new ArgumentOutOfRangeException("differenceOptions"); + throw new ArgumentOutOfRangeException(nameof(differenceOptions)); } return DiffText(left, right, type, differenceOptions); @@ -103,7 +103,7 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation StringDifferenceOptions nextOptions = new StringDifferenceOptions(differenceOptions); nextOptions.DifferenceType &= ~type; - var diffCollection = ComputeMatches(type, differenceOptions, left, right); + var diffCollection = ComputeMatches(differenceOptions, left, right); return new HierarchicalDifferenceCollection(diffCollection, left, right, this, nextOptions); } @@ -133,13 +133,13 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation return line.GetTextIncludingLineBreak(); } - static IDifferenceCollection<string> ComputeMatches(StringDifferenceTypes differenceType, StringDifferenceOptions differenceOptions, + static IDifferenceCollection<string> ComputeMatches(StringDifferenceOptions differenceOptions, IList<string> leftSequence, IList<string> rightSequence) { - return ComputeMatches(differenceType, differenceOptions, leftSequence, rightSequence, leftSequence, rightSequence); + return ComputeMatches(differenceOptions, leftSequence, rightSequence, leftSequence, rightSequence); } - static IDifferenceCollection<string> ComputeMatches(StringDifferenceTypes differenceType, StringDifferenceOptions differenceOptions, + static IDifferenceCollection<string> ComputeMatches(StringDifferenceOptions differenceOptions, IList<string> leftSequence, IList<string> rightSequence, IList<string> originalLeftSequence, IList<string> originalRightSequence) { diff --git a/src/Text/Impl/DifferenceAlgorithm/HierarchicalDifferenceCollection.cs b/src/Text/Impl/DifferenceAlgorithm/HierarchicalDifferenceCollection.cs index 33d06cb..93f5d32 100644 --- a/src/Text/Impl/DifferenceAlgorithm/HierarchicalDifferenceCollection.cs +++ b/src/Text/Impl/DifferenceAlgorithm/HierarchicalDifferenceCollection.cs @@ -46,11 +46,11 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation StringDifferenceOptions options) { if (differenceCollection == null) - throw new ArgumentNullException("differenceCollection"); + throw new ArgumentNullException(nameof(differenceCollection)); if (left == null) - throw new ArgumentNullException("left"); + throw new ArgumentNullException(nameof(left)); if (right == null) - throw new ArgumentNullException("right"); + throw new ArgumentNullException(nameof(right)); if (!object.ReferenceEquals(left, differenceCollection.LeftSequence)) throw new ArgumentException("left must equal differenceCollection.LeftSequence"); if (!object.ReferenceEquals(right, differenceCollection.RightSequence)) diff --git a/src/Text/Impl/DifferenceAlgorithm/MaximalSubsequenceAlgorithm.cs b/src/Text/Impl/DifferenceAlgorithm/MaximalSubsequenceAlgorithm.cs index a40aa72..2f664d9 100644 --- a/src/Text/Impl/DifferenceAlgorithm/MaximalSubsequenceAlgorithm.cs +++ b/src/Text/Impl/DifferenceAlgorithm/MaximalSubsequenceAlgorithm.cs @@ -20,7 +20,7 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation internal sealed class MaximalSubsequenceAlgorithm : IDifferenceService { #region IDifferenceService Members - static readonly Microsoft.TeamFoundation.Diff.Copy.IDiffChange[] Empty = new Microsoft.TeamFoundation.Diff.Copy.IDiffChange[0]; + static readonly Microsoft.TeamFoundation.Diff.Copy.IDiffChange[] Empty = Array.Empty<TeamFoundation.Diff.Copy.IDiffChange>(); public IDifferenceCollection<T> DifferenceSequences<T>(IList<T> left, IList<T> right) { @@ -37,9 +37,9 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation internal static DifferenceCollection<T> DifferenceSequences<T>(IList<T> left, IList<T> right, IList<T> originalLeft, IList<T> originalRight, ContinueProcessingPredicate<T> continueProcessingPredicate) { if (left == null) - throw new ArgumentNullException("left"); + throw new ArgumentNullException(nameof(left)); if (right == null) - throw new ArgumentNullException("right"); + throw new ArgumentNullException(nameof(right)); Microsoft.TeamFoundation.Diff.Copy.IDiffChange[] changes; if ((left.Count == 0) || (right.Count == 0)) diff --git a/src/Text/Impl/DifferenceAlgorithm/SnapshotLineList.cs b/src/Text/Impl/DifferenceAlgorithm/SnapshotLineList.cs index 47df212..20e92c3 100644 --- a/src/Text/Impl/DifferenceAlgorithm/SnapshotLineList.cs +++ b/src/Text/Impl/DifferenceAlgorithm/SnapshotLineList.cs @@ -27,7 +27,7 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation public SnapshotLineList(SnapshotSpan snapshotSpan, Func<ITextSnapshotLine, string> getLineTextCallback, StringDifferenceOptions options) { if (getLineTextCallback == null) - throw new ArgumentNullException("getLineTextCallback"); + throw new ArgumentNullException(nameof(getLineTextCallback)); if ((options.DifferenceType & StringDifferenceTypes.Line) == 0) throw new InvalidOperationException("This collection can only be used for line differencing"); @@ -96,7 +96,7 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation SnapshotSpan GetSpanOfIndex(int index) { if (index < 0 || index >= _lineSpan.Length) - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); ITextSnapshotLine line = _snapshotSpan.Snapshot.GetLineFromLineNumber(_lineSpan.Start + index); SnapshotSpan? lineSpan = line.ExtentIncludingLineBreak.Intersection(_snapshotSpan); diff --git a/src/Text/Impl/DifferenceAlgorithm/TFS/DiffFinder.cs b/src/Text/Impl/DifferenceAlgorithm/TFS/DiffFinder.cs index 6e2bfd3..e401345 100644 --- a/src/Text/Impl/DifferenceAlgorithm/TFS/DiffFinder.cs +++ b/src/Text/Impl/DifferenceAlgorithm/TFS/DiffFinder.cs @@ -515,7 +515,7 @@ namespace Microsoft.TeamFoundation.Diff.Copy /// the constructed changes. /// </summary> //************************************************************************* - internal class DiffChangeHelper : IDisposable + internal sealed class DiffChangeHelper : IDisposable { //********************************************************************* /// <summary> @@ -657,6 +657,7 @@ namespace Microsoft.TeamFoundation.Diff.Copy /// A base for classes which compute the differences between two input sequences. /// </summary> //************************************************************************* +#pragma warning disable CA1063 // Implement IDisposable Correctly public abstract class DiffFinder<T> : IDisposable { //************************************************************************* @@ -689,12 +690,14 @@ namespace Microsoft.TeamFoundation.Diff.Copy get { return m_elementComparer; } } + //************************************************************************* /// <summary> /// Disposes resources used by this DiffFinder /// </summary> //************************************************************************* public virtual void Dispose() +#pragma warning restore CA1063 // Implement IDisposable Correctly { if (m_originalIds != null) { @@ -914,7 +917,7 @@ namespace Microsoft.TeamFoundation.Diff.Copy Debug.Assert(modifiedStart == modifiedEnd + 1, "modifiedStart should only be one more than modifiedEnd"); // Identical sequences - No differences - changes = new IDiffChange[0]; + changes = Array.Empty<IDiffChange>(); } return changes; @@ -948,7 +951,9 @@ namespace Microsoft.TeamFoundation.Diff.Copy /// </summary> //************************************************************************* [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Lcs")] +#pragma warning disable CA1000 // Do not declare static members on generic types public static DiffFinder<T> LcsDiff +#pragma warning restore CA1000 // Do not declare static members on generic types { get { return new LcsDiff<T>(); } } @@ -971,4 +976,4 @@ namespace Microsoft.TeamFoundation.Diff.Copy //Early termination predicate private ContinueDifferencePredicate<T> m_predicate; } -}
\ No newline at end of file +} diff --git a/src/Text/Impl/DifferenceAlgorithm/TFS/LCSDiff.cs b/src/Text/Impl/DifferenceAlgorithm/TFS/LCSDiff.cs index c70ca9d..9fd9720 100644 --- a/src/Text/Impl/DifferenceAlgorithm/TFS/LCSDiff.cs +++ b/src/Text/Impl/DifferenceAlgorithm/TFS/LCSDiff.cs @@ -6,10 +6,8 @@ // Use at your own risk. // using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Text; //************************************************************************* // The code from this point on is a soure-port of the TFS diff algorithm, to be available @@ -54,7 +52,6 @@ namespace Microsoft.TeamFoundation.Diff.Copy { m_reverseHistory = null; } - GC.SuppressFinalize(this); } //************************************************************************* @@ -138,7 +135,7 @@ namespace Microsoft.TeamFoundation.Diff.Copy Debug.Assert(modifiedStart == modifiedEnd + 1, "modifiedStart should only be one more than modifiedEnd"); // Identical sequences - No differences - changes = new IDiffChange[0]; + changes = Array.Empty<IDiffChange>(); } return changes; @@ -162,7 +159,7 @@ namespace Microsoft.TeamFoundation.Diff.Copy // Second Half: (midOriginal + 1, minModified + 1) to (originalEnd, modifiedEnd) // NOTE: ComputeDiff() is inclusive, therefore the second range starts on the next point IDiffChange[] leftChanges = ComputeDiffRecursive(originalStart, midOriginal, modifiedStart, midModified, out quitEarly); - IDiffChange[] rightChanges = new IDiffChange[0]; + IDiffChange[] rightChanges = Array.Empty<IDiffChange>(); if (!quitEarly) { @@ -671,7 +668,7 @@ namespace Microsoft.TeamFoundation.Diff.Copy /// <param name="right">The right changes</param> /// <returns>The concatenated list</returns> //************************************************************************* - private IDiffChange[] ConcatenateChanges(IDiffChange[] left, IDiffChange[] right) + private static IDiffChange[] ConcatenateChanges(IDiffChange[] left, IDiffChange[] right) { IDiffChange mergedChange; @@ -713,7 +710,7 @@ namespace Microsoft.TeamFoundation.Diff.Copy /// null otherwise</param> /// <returns>True if the two changes overlap</returns> //************************************************************************* - private bool ChangesOverlap(IDiffChange left, IDiffChange right, out IDiffChange mergedChange) + private static bool ChangesOverlap(IDiffChange left, IDiffChange right, out IDiffChange mergedChange) { Debug.Assert(left.OriginalStart <= right.OriginalStart, "Left change is not less than or equal to right change"); Debug.Assert(left.ModifiedStart <= right.ModifiedStart, "Left change is not less than or equal to right change"); @@ -760,10 +757,11 @@ namespace Microsoft.TeamFoundation.Diff.Copy /// <param name="numDiagonals">The total number of diagonals.</param> /// <returns>The clipped diagonal index.</returns> //************************************************************************* - private int ClipDiagonalBound(int diagonal, - int numDifferences, - int diagonalBaseIndex, - int numDiagonals) + private static int ClipDiagonalBound( + int diagonal, + int numDifferences, + int diagonalBaseIndex, + int numDiagonals) { if (diagonal >= 0 && diagonal < numDiagonals) { diff --git a/src/Text/Impl/DifferenceAlgorithm/TokenizedStringList.cs b/src/Text/Impl/DifferenceAlgorithm/TokenizedStringList.cs index f7afc1c..01e6895 100644 --- a/src/Text/Impl/DifferenceAlgorithm/TokenizedStringList.cs +++ b/src/Text/Impl/DifferenceAlgorithm/TokenizedStringList.cs @@ -42,7 +42,7 @@ namespace Microsoft.VisualStudio.Text.Differencing.Implementation protected TokenizedStringList(string original) { if (original == null) - throw new ArgumentNullException("original"); + throw new ArgumentNullException(nameof(original)); this.original = original; } diff --git a/src/Text/Impl/EditorOperations/AfterTextBufferChangeUndoPrimitive.cs b/src/Text/Impl/EditorOperations/AfterTextBufferChangeUndoPrimitive.cs index 97d1369..d19da59 100644 --- a/src/Text/Impl/EditorOperations/AfterTextBufferChangeUndoPrimitive.cs +++ b/src/Text/Impl/EditorOperations/AfterTextBufferChangeUndoPrimitive.cs @@ -19,10 +19,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { // Think twice before adding any fields here! These objects are long-lived and consume considerable space. // Unusual cases should be handled by the GeneralAfterTextBufferChangedUndoPrimitive class below. - protected ITextUndoHistory _undoHistory; - protected int _newCaretIndex; - protected byte _newCaretAffinityByte; - protected bool _canUndo; + private readonly ITextUndoHistory _undoHistory; + public readonly SelectionState State; + private bool _canUndo; /// <summary> /// Constructs a AfterTextBufferChangeUndoPrimitive. @@ -39,57 +38,21 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { if (textView == null) { - throw new ArgumentNullException("textView"); + throw new ArgumentNullException(nameof(textView)); } if (undoHistory == null) { - throw new ArgumentNullException("undoHistory"); + throw new ArgumentNullException(nameof(undoHistory)); } - // Store the ITextView for these changes in the ITextUndoHistory properties so we can retrieve it later. - if (!undoHistory.Properties.ContainsProperty(typeof(ITextView))) - { - undoHistory.Properties[typeof(ITextView)] = textView; - } - - IMapEditToData map = BeforeTextBufferChangeUndoPrimitive.GetMap(textView); - - CaretPosition caret = textView.Caret.Position; - int newCaretIndex = BeforeTextBufferChangeUndoPrimitive.MapToData(map, caret.BufferPosition); - int newCaretVirtualSpaces = caret.VirtualBufferPosition.VirtualSpaces; - - VirtualSnapshotPoint anchor = textView.Selection.AnchorPoint; - int newSelectionAnchorIndex = BeforeTextBufferChangeUndoPrimitive.MapToData(map, anchor.Position); - int newSelectionAnchorVirtualSpaces = anchor.VirtualSpaces; - - VirtualSnapshotPoint active = textView.Selection.ActivePoint; - int newSelectionActiveIndex = BeforeTextBufferChangeUndoPrimitive.MapToData(map, active.Position); - int newSelectionActiveVirtualSpaces = active.VirtualSpaces; + return new AfterTextBufferChangeUndoPrimitive(textView, undoHistory); - TextSelectionMode newSelectionMode = textView.Selection.Mode; - - if (newCaretVirtualSpaces != 0 || - newSelectionAnchorIndex != newCaretIndex || - newSelectionAnchorVirtualSpaces != 0 || - newSelectionActiveIndex != newCaretIndex || - newSelectionActiveVirtualSpaces != 0 || - newSelectionMode != TextSelectionMode.Stream) - { - return new GeneralAfterTextBufferChangeUndoPrimitive - (undoHistory, newCaretIndex, caret.Affinity, newCaretVirtualSpaces, newSelectionAnchorIndex, - newSelectionAnchorVirtualSpaces, newSelectionActiveIndex, newSelectionActiveVirtualSpaces, newSelectionMode); - } - else - { - return new AfterTextBufferChangeUndoPrimitive(undoHistory, newCaretIndex, caret.Affinity); - } } - protected AfterTextBufferChangeUndoPrimitive(ITextUndoHistory undoHistory, int caretIndex, PositionAffinity caretAffinity) + protected AfterTextBufferChangeUndoPrimitive(ITextView textView, ITextUndoHistory undoHistory) { _undoHistory = undoHistory; - _newCaretIndex = caretIndex; - _newCaretAffinityByte = (byte)caretAffinity; + this.State = new SelectionState(textView); _canUndo = true; } @@ -106,15 +69,6 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return view; } - internal int CaretIndex - { - get { return _newCaretIndex; } - } - - internal virtual int CaretVirtualSpace - { - get { return 0; } - } #region ITextUndoPrimitive Members @@ -151,7 +105,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation Debug.Assert(view == null || !view.IsClosed, "Attempt to undo/redo on a closed view? This shouldn't happen."); if (view != null && !view.IsClosed) { - DoMoveCaretAndSelect(view, BeforeTextBufferChangeUndoPrimitive.GetMap(view)); + this.State.Restore(view); view.Caret.EnsureVisible(); } @@ -159,17 +113,6 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation } /// <summary> - /// Move the caret and restore the selection as part of the Redo operation. - /// </summary> - protected virtual void DoMoveCaretAndSelect(ITextView view, IMapEditToData map) - { - SnapshotPoint newCaret = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _newCaretIndex)); - - view.Caret.MoveTo(newCaret, (PositionAffinity)_newCaretAffinityByte); - view.Selection.Clear(); - } - - /// <summary> /// Undo the action. /// </summary> /// <exception cref="InvalidOperationException">Operation cannot be undone.</exception> @@ -197,65 +140,4 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation } #endregion } - - /// <summary> - /// The UndoPrimitive to take place on the Undo stack before a text buffer change. This is the general - /// version of the primitive that handles all cases, including those involving selections and virtual space. - /// </summary> - internal class GeneralAfterTextBufferChangeUndoPrimitive : AfterTextBufferChangeUndoPrimitive - { - private int _newCaretVirtualSpaces; - private int _newSelectionAnchorIndex; - private int _newSelectionAnchorVirtualSpaces; - private int _newSelectionActiveIndex; - private int _newSelectionActiveVirtualSpaces; - private TextSelectionMode _newSelectionMode; - - public GeneralAfterTextBufferChangeUndoPrimitive(ITextUndoHistory undoHistory, - int newCaretIndex, - PositionAffinity newCaretAffinity, - int newCaretVirtualSpaces, - int newSelectionAnchorIndex, - int newSelectionAnchorVirtualSpaces, - int newSelectionActiveIndex, - int newSelectionActiveVirtualSpaces, - TextSelectionMode newSelectionMode) - : base(undoHistory, newCaretIndex, newCaretAffinity) - { - _newCaretVirtualSpaces = newCaretVirtualSpaces; - _newSelectionAnchorIndex = newSelectionAnchorIndex; - _newSelectionAnchorVirtualSpaces = newSelectionAnchorVirtualSpaces; - _newSelectionActiveIndex = newSelectionActiveIndex; - _newSelectionActiveVirtualSpaces = newSelectionActiveVirtualSpaces; - _newSelectionMode = newSelectionMode; - } - - internal override int CaretVirtualSpace - { - get { return _newCaretVirtualSpaces; } - } - - /// <summary> - /// Move the caret and restore the selection as part of the Redo operation. - /// </summary> - protected override void DoMoveCaretAndSelect(ITextView view, IMapEditToData map) - { - SnapshotPoint newCaret = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _newCaretIndex)); - SnapshotPoint newAnchor = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _newSelectionAnchorIndex)); - SnapshotPoint newActive = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _newSelectionActiveIndex)); - - view.Caret.MoveTo(new VirtualSnapshotPoint(newCaret, _newCaretVirtualSpaces), (PositionAffinity)_newCaretAffinityByte); - - view.Selection.Mode = _newSelectionMode; - - var virtualAnchor = new VirtualSnapshotPoint(newAnchor, _newSelectionAnchorVirtualSpaces); - var virtualActive = new VirtualSnapshotPoint(newActive, _newSelectionActiveVirtualSpaces); - - // Buffer may have been changed by one of the listeners on the caret move event. - virtualAnchor = virtualAnchor.TranslateTo(view.TextSnapshot); - virtualActive = virtualActive.TranslateTo(view.TextSnapshot); - - view.Selection.Select(virtualAnchor, virtualActive); - } - } } diff --git a/src/Text/Impl/EditorOperations/BeforeTextBufferChangeUndoPrimitive.cs b/src/Text/Impl/EditorOperations/BeforeTextBufferChangeUndoPrimitive.cs index 47abd9b..baf7c70 100644 --- a/src/Text/Impl/EditorOperations/BeforeTextBufferChangeUndoPrimitive.cs +++ b/src/Text/Impl/EditorOperations/BeforeTextBufferChangeUndoPrimitive.cs @@ -19,10 +19,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { // Think twice before adding any fields here! These objects are long-lived and consume considerable space. // Unusual cases should be handled by the GeneralAfterTextBufferChangedUndoPrimitive class below. - protected ITextUndoHistory _undoHistory; - protected int _oldCaretIndex; - protected byte _oldCaretAffinityByte; - protected bool _canUndo; + private readonly ITextUndoHistory _undoHistory; + public readonly SelectionState State; + private bool _canUndo; /// <summary> /// Constructs a BeforeTextBufferChangeUndoPrimitive. @@ -39,86 +38,20 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { if (textView == null) { - throw new ArgumentNullException("textView"); + throw new ArgumentNullException(nameof(textView)); } if (undoHistory == null) { - throw new ArgumentNullException("undoHistory"); + throw new ArgumentNullException(nameof(undoHistory)); } - // Store the ITextView for these changes in the ITextUndoHistory properties so we can retrieve it later. - if (!undoHistory.Properties.ContainsProperty(typeof(ITextView))) - { - undoHistory.Properties[typeof(ITextView)] = textView; - } - - CaretPosition caret = textView.Caret.Position; - - IMapEditToData map = BeforeTextBufferChangeUndoPrimitive.GetMap(textView); - - int oldCaretIndex = BeforeTextBufferChangeUndoPrimitive.MapToData(map, caret.BufferPosition); - int oldCaretVirtualSpaces = caret.VirtualBufferPosition.VirtualSpaces; - - VirtualSnapshotPoint anchor = textView.Selection.AnchorPoint; - int oldSelectionAnchorIndex = BeforeTextBufferChangeUndoPrimitive.MapToData(map, anchor.Position); - int oldSelectionAnchorVirtualSpaces = anchor.VirtualSpaces; - - VirtualSnapshotPoint active = textView.Selection.ActivePoint; - int oldSelectionActiveIndex = BeforeTextBufferChangeUndoPrimitive.MapToData(map, active.Position); - int oldSelectionActiveVirtualSpaces = active.VirtualSpaces; - - TextSelectionMode oldSelectionMode = textView.Selection.Mode; - - if (oldCaretVirtualSpaces != 0 || - oldSelectionAnchorIndex != oldCaretIndex || - oldSelectionAnchorVirtualSpaces != 0 || - oldSelectionActiveIndex != oldCaretIndex || - oldSelectionActiveVirtualSpaces != 0 || - oldSelectionMode != TextSelectionMode.Stream) - { - return new GeneralBeforeTextBufferChangeUndoPrimitive - (undoHistory, oldCaretIndex, caret.Affinity, oldCaretVirtualSpaces, oldSelectionAnchorIndex, - oldSelectionAnchorVirtualSpaces, oldSelectionActiveIndex, oldSelectionActiveVirtualSpaces, oldSelectionMode); - } - else - { - return new BeforeTextBufferChangeUndoPrimitive(undoHistory, oldCaretIndex, caret.Affinity); - } - } - - //Get the map -- if any -- used to map points in the view's edit buffer to the data buffer. The map is needed because the undo history - //typically lives on the data buffer, but is used by the view on the edit buffer and a view (if any) on the data buffer. If there isn't - //a contract that guarantees that the contents of the edit and databuffers are the same, undoing an action on the edit buffer view and - //then undoing it on the data buffer view will cause cause the undo to try and restore caret/selection (in the data buffer) the coorinates - //saved in the edit buffer. This isn't good. - internal static IMapEditToData GetMap(ITextView view) - { - IMapEditToData map = null; - if (view.TextViewModel.EditBuffer != view.TextViewModel.DataBuffer) - { - view.Properties.TryGetProperty(typeof(IMapEditToData), out map); - } - - return map; - } - - //Map point from a position in the edit buffer to a position in the data buffer (== if there is no map, otherwise ask the map). - internal static int MapToData(IMapEditToData map, int point) - { - return (map != null) ? map.MapEditToData(point) : point; - } - - //Map point from a position in the data buffer to a position in the edit buffer (== if there is no map, otherwise ask the map). - internal static int MapToEdit(IMapEditToData map, int point) - { - return (map != null) ? map.MapDataToEdit(point) : point; + return new BeforeTextBufferChangeUndoPrimitive(textView, undoHistory); } - protected BeforeTextBufferChangeUndoPrimitive(ITextUndoHistory undoHistory, int caretIndex, PositionAffinity caretAffinity) + private BeforeTextBufferChangeUndoPrimitive(ITextView textView, ITextUndoHistory undoHistory) { _undoHistory = undoHistory; - _oldCaretIndex = caretIndex; - _oldCaretAffinityByte = (byte)caretAffinity; + this.State = new SelectionState(textView); _canUndo = true; } @@ -192,34 +125,18 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation Debug.Assert(view == null || !view.IsClosed, "Attempt to undo/redo on a closed view? This shouldn't happen."); if (view != null && !view.IsClosed) { - UndoMoveCaretAndSelect(view, BeforeTextBufferChangeUndoPrimitive.GetMap(view)); + this.State.Restore(view); view.Caret.EnsureVisible(); } _canUndo = false; } - /// <summary> - /// Move the caret and restore the selection as part of the Undo operation. - /// </summary> - protected virtual void UndoMoveCaretAndSelect(ITextView view, IMapEditToData map) - { - SnapshotPoint newCaret = new SnapshotPoint(view.TextSnapshot, MapToEdit(map, _oldCaretIndex)); - - view.Caret.MoveTo(new VirtualSnapshotPoint(newCaret), (PositionAffinity)_oldCaretAffinityByte); - view.Selection.Clear(); - } - - protected virtual int OldCaretVirtualSpaces - { - get { return 0; } - } - public override bool CanMerge(ITextUndoPrimitive older) { if (older == null) { - throw new ArgumentNullException("older"); + throw new ArgumentNullException(nameof(older)); } AfterTextBufferChangeUndoPrimitive olderPrimitive = older as AfterTextBufferChangeUndoPrimitive; @@ -229,70 +146,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return false; } - return (olderPrimitive.CaretIndex == _oldCaretIndex) && (olderPrimitive.CaretVirtualSpace == OldCaretVirtualSpaces); + return olderPrimitive.State.Matches(this.State); } #endregion } - - /// <summary> - /// The UndoPrimitive to take place on the Undo stack before a text buffer change. This is the general - /// version of the primitive that handles all cases, including those involving selections and virtual space. - /// </summary> - internal class GeneralBeforeTextBufferChangeUndoPrimitive : BeforeTextBufferChangeUndoPrimitive - { - private int _oldCaretVirtualSpaces; - private int _oldSelectionAnchorIndex; - private int _oldSelectionAnchorVirtualSpaces; - private int _oldSelectionActiveIndex; - private int _oldSelectionActiveVirtualSpaces; - private TextSelectionMode _oldSelectionMode; - - public GeneralBeforeTextBufferChangeUndoPrimitive(ITextUndoHistory undoHistory, - int oldCaretIndex, - PositionAffinity oldCaretAffinity, - int oldCaretVirtualSpaces, - int oldSelectionAnchorIndex, - int oldSelectionAnchorVirtualSpaces, - int oldSelectionActiveIndex, - int oldSelectionActiveVirtualSpaces, - TextSelectionMode oldSelectionMode) - : base(undoHistory, oldCaretIndex, oldCaretAffinity) - { - _oldCaretVirtualSpaces = oldCaretVirtualSpaces; - _oldSelectionAnchorIndex = oldSelectionAnchorIndex; - _oldSelectionAnchorVirtualSpaces = oldSelectionAnchorVirtualSpaces; - _oldSelectionActiveIndex = oldSelectionActiveIndex; - _oldSelectionActiveVirtualSpaces = oldSelectionActiveVirtualSpaces; - _oldSelectionMode = oldSelectionMode; - } - - /// <summary> - /// Move the caret and restore the selection as part of the Undo operation. - /// </summary> - protected override void UndoMoveCaretAndSelect(ITextView view, IMapEditToData map) - { - SnapshotPoint newCaret = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _oldCaretIndex)); - SnapshotPoint newAnchor = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _oldSelectionAnchorIndex)); - SnapshotPoint newActive = new SnapshotPoint(view.TextSnapshot, BeforeTextBufferChangeUndoPrimitive.MapToEdit(map, _oldSelectionActiveIndex)); - - view.Caret.MoveTo(new VirtualSnapshotPoint(newCaret, _oldCaretVirtualSpaces), (PositionAffinity)_oldCaretAffinityByte); - - view.Selection.Mode = _oldSelectionMode; - - var virtualAnchor = new VirtualSnapshotPoint(newAnchor, _oldSelectionAnchorVirtualSpaces); - var virtualActive = new VirtualSnapshotPoint(newActive, _oldSelectionActiveVirtualSpaces); - - // Buffer may have been changed by one of the listeners on the caret move event. - virtualAnchor = virtualAnchor.TranslateTo(view.TextSnapshot); - virtualActive = virtualActive.TranslateTo(view.TextSnapshot); - - view.Selection.Select(virtualAnchor, virtualActive); - } - - protected override int OldCaretVirtualSpaces - { - get { return _oldCaretVirtualSpaces; } - } - } } diff --git a/src/Text/Impl/EditorOperations/CollapsedMoveUndoPrimitive.cs b/src/Text/Impl/EditorOperations/CollapsedMoveUndoPrimitive.cs index 44fa09e..aa065e4 100644 --- a/src/Text/Impl/EditorOperations/CollapsedMoveUndoPrimitive.cs +++ b/src/Text/Impl/EditorOperations/CollapsedMoveUndoPrimitive.cs @@ -126,17 +126,17 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { if (textView == null) { - throw new ArgumentNullException("textView"); + throw new ArgumentNullException(nameof(textView)); } if (outliningManager == null) { - throw new ArgumentNullException("outliningManager"); + throw new ArgumentNullException(nameof(outliningManager)); } if (collaspedSpans == null) { - throw new ArgumentNullException("collaspedSpans"); + throw new ArgumentNullException(nameof(collaspedSpans)); } _outliningManager = outliningManager; diff --git a/src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionCommandHandler.cs b/src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionCommandHandler.cs deleted file mode 100644 index c7ec9b1..0000000 --- a/src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionCommandHandler.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.ComponentModel.Composition; -using Microsoft.VisualStudio.Commanding; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; -using Microsoft.VisualStudio.Utilities; - -namespace Microsoft.VisualStudio.Text.Operations.Implementation -{ - [Export(typeof(ICommandHandler))] - [Name(nameof(ExpandContractSelectionCommandHandler))] - [ContentType("any")] - [TextViewRole(PredefinedTextViewRoles.PrimaryDocument)] - [TextViewRole(PredefinedTextViewRoles.EmbeddedPeekTextView)] - internal sealed class ExpandContractSelectionCommandHandler - : ICommandHandler<ExpandSelectionCommandArgs>, ICommandHandler<ContractSelectionCommandArgs> - { - [ImportingConstructor] - public ExpandContractSelectionCommandHandler( - IEditorOptionsFactoryService editorOptionsFactoryService, - ITextStructureNavigatorSelectorService navigatorSelectorService) - { - this.EditorOptionsFactoryService = editorOptionsFactoryService; - this.NavigatorSelectorService = navigatorSelectorService; - } - - public IEditorOptionsFactoryService EditorOptionsFactoryService { get; } - - private readonly ITextStructureNavigatorSelectorService NavigatorSelectorService; - - public string DisplayName => Strings.ExpandContractSelectionCommandHandlerName; - - public CommandState GetCommandState(ExpandSelectionCommandArgs args) - { - var storedCommandState = ExpandContractSelectionImplementation.GetOrCreateExpandContractState( - args.TextView, - this.EditorOptionsFactoryService, - this.NavigatorSelectorService); - return storedCommandState.GetExpandCommandState(args.TextView); - } - - public CommandState GetCommandState(ContractSelectionCommandArgs args) - { - var storedCommandState = ExpandContractSelectionImplementation.GetOrCreateExpandContractState( - args.TextView, - this.EditorOptionsFactoryService, - this.NavigatorSelectorService); - return storedCommandState.GetContractCommandState(args.TextView); - } - - public bool ExecuteCommand(ExpandSelectionCommandArgs args, CommandExecutionContext context) - { - var storedCommandState = ExpandContractSelectionImplementation.GetOrCreateExpandContractState( - args.TextView, - this.EditorOptionsFactoryService, - this.NavigatorSelectorService); - return storedCommandState.ExpandSelection(args.TextView); - } - - public bool ExecuteCommand(ContractSelectionCommandArgs args, CommandExecutionContext context) - { - var storedCommandState = ExpandContractSelectionImplementation.GetOrCreateExpandContractState( - args.TextView, - this.EditorOptionsFactoryService, - this.NavigatorSelectorService); - return storedCommandState.ContractSelection(args.TextView); - } - } -} diff --git a/src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionImplementation.cs b/src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionImplementation.cs deleted file mode 100644 index a26c025..0000000 --- a/src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionImplementation.cs +++ /dev/null @@ -1,134 +0,0 @@ -namespace Microsoft.VisualStudio.Text.Operations.Implementation -{ - using System; - using System.Collections.Generic; - using Microsoft.VisualStudio.Commanding; - using Microsoft.VisualStudio.Text.Editor; - - internal class ExpandContractSelectionImplementation - { - private readonly IEditorOptions editorOptions; - private readonly ITextStructureNavigatorSelectorService navigatorSelectorService; - private bool ignoreSelectionChangedEvent; - - public static ExpandContractSelectionImplementation GetOrCreateExpandContractState( - ITextView textView, - IEditorOptionsFactoryService editorOptionsFactoryService, - ITextStructureNavigatorSelectorService navigator) - { - return textView.Properties.GetOrCreateSingletonProperty<ExpandContractSelectionImplementation>( - typeof(ExpandContractSelectionImplementation), - () => new ExpandContractSelectionImplementation( - navigator, - editorOptionsFactoryService.GetOptions(textView), - textView)); - } - - private ExpandContractSelectionImplementation( - ITextStructureNavigatorSelectorService navigatorSelectorService, - IEditorOptions editorOptions, - ITextView textView) - { - this.editorOptions = editorOptions; - this.navigatorSelectorService = navigatorSelectorService; - textView.Selection.SelectionChanged += this.OnSelectionChanged; - } - - // Internal for testing. - internal readonly Stack<Tuple<VirtualSnapshotSpan, TextSelectionMode>> previousExpansionsStack - = new Stack<Tuple<VirtualSnapshotSpan, TextSelectionMode>>(); - - public CommandState GetExpandCommandState(ITextView textView) => CommandState.Available; - - public CommandState GetContractCommandState(ITextView textView) - { - if (this.previousExpansionsStack.Count > 0) - { - return CommandState.Available; - } - - return CommandState.Unavailable; - } - - public bool ExpandSelection(ITextView textView) - { - try - { - this.ignoreSelectionChangedEvent = true; - - var navigator = this.GetNavigator(textView); - VirtualSnapshotSpan currentSelection = textView.Selection.StreamSelectionSpan; - previousExpansionsStack.Push(Tuple.Create(currentSelection, textView.Selection.Mode)); - - SnapshotSpan newSelection; - - // If the current language has opt-ed out, return the span of the current word instead. - if (this.editorOptions.GetOptionValue(ExpandContractSelectionOptions.ExpandContractSelectionEnabledKey)) - { - // On first invocation, select the current word. - if (currentSelection.Length == 0) - { - newSelection = this.GetNavigator(textView).GetExtentOfWord(currentSelection.Start.Position).Span; - } - else - { - newSelection = this.GetNavigator(textView).GetSpanOfEnclosing(currentSelection.SnapshotSpan); - } - } - else - { - // Since the span of the current word can be left or right associative relative to the caret - // in different contexts, to avoid different selections on subsequent invocations of Expand - // Selection, always use the center point in the selection to compute the span of the current word. - var centerPoint = currentSelection.Start.Position.Add( - (currentSelection.End.Position.Position - currentSelection.Start.Position.Position) / 2); - newSelection = navigator.GetExtentOfWord(centerPoint).Span; - } - - textView.Selection.Mode = TextSelectionMode.Stream; - textView.Selection.Select(newSelection, isReversed: false); - } - finally - { - this.ignoreSelectionChangedEvent = false; - } - - return true; //return true if command is handled - } - - public bool ContractSelection(ITextView textView) - { - try - { - this.ignoreSelectionChangedEvent = true; - - if (this.previousExpansionsStack.Count > 0) - { - Tuple<VirtualSnapshotSpan, TextSelectionMode> previousExpansion = this.previousExpansionsStack.Pop(); - VirtualSnapshotSpan previousExpansionSpan = previousExpansion.Item1; - TextSelectionMode previousExpansionSelectionMode = previousExpansion.Item2; - - textView.Selection.Mode = previousExpansionSelectionMode; - textView.Selection.Select(previousExpansionSpan.Start, previousExpansionSpan.End); - } - } - finally - { - this.ignoreSelectionChangedEvent = false; - } - - return true;//return true if command is handled - } - - private void OnSelectionChanged(object sender, EventArgs eventArgs) - { - if (!this.ignoreSelectionChangedEvent) - { - this.previousExpansionsStack.Clear(); - } - } - - private ITextStructureNavigator GetNavigator(ITextView textView) - => this.navigatorSelectorService.GetTextStructureNavigator(textView.TextBuffer); - } -} diff --git a/src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionOptionDefinitions.cs b/src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionOptionDefinitions.cs deleted file mode 100644 index b39c3f8..0000000 --- a/src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionOptionDefinitions.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Microsoft.VisualStudio.Text.Operations.Implementation -{ - using System.ComponentModel.Composition; - using Microsoft.VisualStudio.Text.Editor; - using Microsoft.VisualStudio.Text.Operations; - using Microsoft.VisualStudio.Utilities; - - /// <summary> - /// Defines Expand Contract Selection Option. - /// </summary> - [Export(typeof(EditorOptionDefinition))] - [Name(ExpandContractSelectionOptions.ExpandContractSelectionEnabledOptionId)] - internal sealed class ExpandContractSelectionEnabled : EditorOptionDefinition<bool> - { - /// <summary> - /// Gets the default value, which is <c>false</c>. - /// </summary> - public override bool Default => true; - - /// <summary> - /// Gets the default text view host value. - /// </summary> - public override EditorOptionKey<bool> Key => ExpandContractSelectionOptions.ExpandContractSelectionEnabledKey; - } -} diff --git a/src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs b/src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs new file mode 100644 index 0000000..25948cf --- /dev/null +++ b/src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs @@ -0,0 +1,156 @@ +namespace Microsoft.VisualStudio.Text.Operations.Implementation +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.Composition; + using System.Diagnostics; + using System.Linq; + using Microsoft.VisualStudio.Commanding; + using Microsoft.VisualStudio.Text.Editor; + using Microsoft.VisualStudio.Text.Editor.Commanding.Commands; + using Microsoft.VisualStudio.Text.Tagging; + using Microsoft.VisualStudio.Utilities; + + [Export(typeof(ICommandHandler))] + [Name("default " + nameof(NavigateToNextIssueCommandHandler))] + [ContentType("any")] + [TextViewRole(PredefinedTextViewRoles.Analyzable)] + internal sealed class NavigateToNextIssueCommandHandler : ICommandHandler<NavigateToNextIssueInDocumentCommandArgs>, ICommandHandler<NavigateToPreviousIssueInDocumentCommandArgs> + { + [Import] + private Lazy<IBufferTagAggregatorFactoryService> tagAggregatorFactoryService; + + public string DisplayName => Strings.NextIssue; + + #region Previous Issue + + public CommandState GetCommandState(NavigateToPreviousIssueInDocumentCommandArgs args) => CommandState.Available; + + public bool ExecuteCommand(NavigateToPreviousIssueInDocumentCommandArgs args, CommandExecutionContext executionContext) + { + var snapshot = args.TextView.TextSnapshot; + var spans = this.GetTagSpansCollection(snapshot, args.ErrorTagTypeNames); + + if (spans.Count == 0) + { + return true; + } + + (int indexOfErrorSpan, bool containsPoint) = IndexOfTagSpanNearPoint(spans, args.TextView.Caret.Position.BufferPosition.Position); + + int nextIndex = indexOfErrorSpan - 1; + if (containsPoint && (spans.Count == 1)) + { + // There is only one error tag and it contains the caret. Ensure it stays put. + return true; + } + + // Wrap if needed. + if (nextIndex < 0) + { + nextIndex = (spans.Count - 1); + } + + args.TextView.Caret.MoveTo(new SnapshotPoint(snapshot, spans[nextIndex].Start)); + args.TextView.Caret.EnsureVisible(); + return true; + } + + #endregion + + #region Next Issue + public CommandState GetCommandState(NavigateToNextIssueInDocumentCommandArgs args) => CommandState.Available; + + public bool ExecuteCommand(NavigateToNextIssueInDocumentCommandArgs args, CommandExecutionContext executionContext) + { + var snapshot = args.TextView.TextSnapshot; + var spans = this.GetTagSpansCollection(snapshot, args.ErrorTagTypeNames); + + if (spans.Count == 0) + { + return true; + } + + (int indexOfErrorSpan, bool containsPoint) = IndexOfTagSpanNearPoint(spans, args.TextView.Caret.Position.BufferPosition.Position); + + int nextIndex = indexOfErrorSpan + 1; + if (containsPoint) + { + if (spans.Count == 1) + { + // There is only one error tag and it contains the caret. Ensure it stays put. + return true; + } + } + else + { + nextIndex = indexOfErrorSpan; + } + + // Wrap if needed. + if ((indexOfErrorSpan == -1) || (nextIndex >= spans.Count)) + { + nextIndex = 0; + } + + args.TextView.Caret.MoveTo(new SnapshotPoint(snapshot, spans[nextIndex].Start)); + args.TextView.Caret.EnsureVisible(); + return true; + } + + #endregion + + private static (int index, bool containsPoint) IndexOfTagSpanNearPoint(NormalizedSpanCollection spans, int point) + { + Debug.Assert(spans.Count > 0); + Span? tagBefore = null; + Span? tagAfter = null; + + for (int i = 0; i < spans.Count; i++) + { + tagBefore = tagAfter; + tagAfter = spans[i]; + + // Case 0: point falls within error tag. We use explicit comparisons instead + // of 'Contains' so that we match a tag even if the caret at the end of it. + if ((point >= tagAfter.Value.Start) && (point <= tagAfter.Value.End)) + { + // Return tag containing the point. + return (i, true); + } + + // Case 1: point falls between two tags. + if ((tagBefore != null) && (tagBefore.Value.End < point) && (tagAfter.Value.Start > point)) + { + // Return tag following the point. + return (i, false); + } + } + + // Case 2: point falls after all tags. + return (-1, false); + } + + private NormalizedSpanCollection GetTagSpansCollection(ITextSnapshot snapshot, IEnumerable<string> errorTagTypeNames) + { + using (var tagger = this.tagAggregatorFactoryService.Value.CreateTagAggregator<IErrorTag>(snapshot.TextBuffer)) + { + var rawTags = tagger.GetTags(new SnapshotSpan(snapshot, 0, snapshot.Length)); + var curatedTags = (errorTagTypeNames?.Any() ?? false) ? + rawTags.Where(tag => errorTagTypeNames.Contains(tag.Tag.ErrorType)) : + rawTags; + + // In this case we only grab the first span that the IMappingTagSpan maps to because we always + // want to place the caret at the start of the error, and so, don't care about possibly disjoint + // subspans after mapping to the view's buffer. NormalizedSpanCollection takes care of sorting + // and joining overlapping spans together for us. It's possible for a tag to map to zero spans + // in projection scenarios in which the tag exists entirely within a region that doesn't map to + // visible space. + return new NormalizedSpanCollection( + curatedTags.Select(tagSpan => tagSpan.Span.GetSpans(snapshot)) + .Where(spanCollection => spanCollection.Count > 0) + .Select(spanCollection => spanCollection[0].Span)); + } + } + } +} diff --git a/src/Text/Impl/EditorOperations/EditorOperations.cs b/src/Text/Impl/EditorOperations/EditorOperations.cs index 5ac282a..369273b 100644 --- a/src/Text/Impl/EditorOperations/EditorOperations.cs +++ b/src/Text/Impl/EditorOperations/EditorOperations.cs @@ -16,7 +16,6 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation using System.IO; using System.Linq; using System.Text.RegularExpressions; - using System.Threading; using System.Windows; using Microsoft.VisualStudio.Text; @@ -56,7 +55,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation ClearVirtualSpace }; - #region Private Members +#region Private Members ITextView _textView; EditorOperationsFactoryService _factory; @@ -65,6 +64,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation ITextUndoHistory _undoHistory; IViewPrimitives _editorPrimitives; IEditorOptions _editorOptions; + IMultiSelectionBroker _multiSelectionBroker; private ITrackingSpan _immProvisionalComposition; @@ -80,7 +80,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </summary> private const string _boxSelectionCutCopyTag = "MSDEVColumnSelect"; - #endregion // Private Members +#endregion // Private Members /// <summary> /// Constructs an <see cref="EditorOperations"/> bound to a given <see cref="ITextView"/>. @@ -93,13 +93,13 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { // Validate if (textView == null) - throw new ArgumentNullException("textView"); + throw new ArgumentNullException(nameof(textView)); if (factory == null) - throw new ArgumentNullException("factory"); + throw new ArgumentNullException(nameof(factory)); _textView = textView; _factory = factory; - + _multiSelectionBroker = _textView.GetMultiSelectionBroker(); _editorPrimitives = factory.EditorPrimitivesProvider.GetViewPrimitives(textView); // Get the TextStructure Navigator _textStructureNavigator = factory.TextStructureNavigatorFactory.GetTextStructureNavigator(_textView.TextBuffer); @@ -121,7 +121,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation } - #region IEditorOperations2 Members +#region IEditorOperations2 Members public bool MoveSelectedLinesUp() { @@ -129,15 +129,13 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { bool success = false; - var view = _textView as ITextView; - // find line start - var startViewLine = GetLineStart(view, view.Selection.Start.Position); + ITextViewLine startViewLine = GetLineStart(_textView, _textView.Selection.Start.Position); SnapshotPoint start = startViewLine.Start; ITextSnapshotLine startLine = start.GetContainingLine(); // find the last line view - var endViewLine = GetLineEnd(view, view.Selection.End.Position); + ITextViewLine endViewLine = GetLineEnd(_textView, _textView.Selection.End.Position); SnapshotPoint end = endViewLine.EndIncludingLineBreak; ITextSnapshotLine endLine = endViewLine.End.GetContainingLine(); @@ -146,24 +144,24 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // Handle the case where multiple lines are selected and the caret is sitting just after the line break on the next line. // Shortening the selection here handles the case where the last line is a collapsed region. Using endLine.End will give // a line within the collapsed region instead of skipping it all together. - if (GetLineEnd(view, startViewLine.Start) != endViewLine - && view.Selection.End.Position == GetLineStart(view, view.Selection.End.Position).Start - && !view.Selection.End.IsInVirtualSpace) + if (GetLineEnd(_textView, startViewLine.Start) != endViewLine + && _textView.Selection.End.Position == GetLineStart(_textView, _textView.Selection.End.Position).Start + && !_textView.Selection.End.IsInVirtualSpace) { endLine = snapshot.GetLineFromLineNumber(endLine.LineNumber - 1); end = endLine.EndIncludingLineBreak; - endViewLine = view.GetTextViewLineContainingBufferPosition(view.Selection.End.Position - 1); + endViewLine = _textView.GetTextViewLineContainingBufferPosition(_textView.Selection.End.Position - 1); } - #region Initial Asserts +#region Initial Asserts - Debug.Assert(view.Selection.Start.Position.Snapshot == view.TextSnapshot, "Selection is out of sync with view."); + Debug.Assert(_textView.Selection.Start.Position.Snapshot == _textView.TextSnapshot, "Selection is out of sync with view."); - Debug.Assert(view.TextSnapshot == view.TextBuffer.CurrentSnapshot, "View is out of sync with text buffer."); + Debug.Assert(_textView.TextSnapshot == _textView.TextBuffer.CurrentSnapshot, "View is out of sync with text buffer."); - Debug.Assert(view.TextSnapshot == snapshot, "Text view lines are out of sync with the view"); + Debug.Assert(_textView.TextSnapshot == snapshot, "Text view lines are out of sync with the view"); - #endregion +#endregion // check if we are at the top of the file, or trying to move a blank line if (startLine.LineNumber < 1 || start == end) @@ -177,11 +175,11 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation ITextSnapshotLine prevLine = snapshot.GetLineFromLineNumber(startLine.LineNumber - 1); // prevLineExtent is different from prevLine.Extent and avoids issues around collapsed regions - SnapshotPoint prevLineStart = GetLineStart(view, prevLine.Start).Start; + SnapshotPoint prevLineStart = GetLineStart(_textView, prevLine.Start).Start; SnapshotSpan prevLineExtent = new SnapshotSpan(prevLineStart, prevLine.End); SnapshotSpan prevLineExtentIncludingLineBreak = new SnapshotSpan(prevLineStart, prevLine.EndIncludingLineBreak); - using (ITextEdit edit = view.TextBuffer.CreateEdit()) + using (ITextEdit edit = _textView.TextBuffer.CreateEdit()) { int offset; @@ -193,7 +191,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation bool hasCollapsedRegions = false; IOutliningManager outliningManager = (_factory.OutliningManagerService != null) - ? _factory.OutliningManagerService.GetOutliningManager(view) + ? _factory.OutliningManagerService.GetOutliningManager(_textView) : null; if (outliningManager != null) @@ -208,7 +206,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { using (ITextUndoTransaction undoTransaction = _undoHistory.CreateTransaction(Strings.MoveSelLinesUp)) { - BeforeCollapsedMoveUndoPrimitive undoPrim = new BeforeCollapsedMoveUndoPrimitive(outliningManager, view, collapsedSpansInCurLine); + BeforeCollapsedMoveUndoPrimitive undoPrim = new BeforeCollapsedMoveUndoPrimitive(outliningManager, _textView, collapsedSpansInCurLine); undoTransaction.AddUndo(undoPrim); undoTransaction.Complete(); } @@ -237,11 +235,11 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation if (!edit.HasFailedChanges) { // store the position before the edit is applied - int anchorPos = view.Selection.AnchorPoint.Position.Position; - int anchorVirtualSpace = view.Selection.AnchorPoint.VirtualSpaces; - int activePos = view.Selection.ActivePoint.Position.Position; - int activeVirtualSpace = view.Selection.ActivePoint.VirtualSpaces; - var selectionMode = view.Selection.Mode; + int anchorPos = _textView.Selection.AnchorPoint.Position.Position; + int anchorVirtualSpace = _textView.Selection.AnchorPoint.VirtualSpaces; + int activePos = _textView.Selection.ActivePoint.Position.Position; + int activeVirtualSpace = _textView.Selection.ActivePoint.VirtualSpaces; + var selectionMode = _textView.Selection.Mode; // apply the edit ITextSnapshot newSnapshot = edit.Apply(); @@ -266,8 +264,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { // This comes from adhocoutliner.cs in env\editor\pkg\impl\outlining and will not be available outside of VS SimpleTagger<IOutliningRegionTag> simpleTagger = - view.TextBuffer.Properties.GetOrCreateSingletonProperty<SimpleTagger<IOutliningRegionTag>>( - () => new SimpleTagger<IOutliningRegionTag>(view.TextBuffer)); + _textView.TextBuffer.Properties.GetOrCreateSingletonProperty<SimpleTagger<IOutliningRegionTag>>( + () => new SimpleTagger<IOutliningRegionTag>(_textView.TextBuffer)); if (simpleTagger != null) { @@ -316,7 +314,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // we need to recollapse after a redo using (ITextUndoTransaction undoTransaction = _undoHistory.CreateTransaction(Strings.MoveSelLinesUp)) { - AfterCollapsedMoveUndoPrimitive undoPrim = new AfterCollapsedMoveUndoPrimitive(outliningManager, view, spansForUndo); + AfterCollapsedMoveUndoPrimitive undoPrim = new AfterCollapsedMoveUndoPrimitive(outliningManager, _textView, spansForUndo); undoTransaction.AddUndo(undoPrim); undoTransaction.Complete(); } @@ -341,18 +339,15 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { Func<bool> action = () => { - bool success = false; - var view = _textView as ITextView; - // find line start - var startViewLine = GetLineStart(view, view.Selection.Start.Position); + ITextViewLine startViewLine = GetLineStart(_textView, _textView.Selection.Start.Position); SnapshotPoint start = startViewLine.Start; ITextSnapshotLine startLine = start.GetContainingLine(); // find the last line view - var endViewLine = GetLineEnd(view, view.Selection.End.Position); + ITextViewLine endViewLine = GetLineEnd(_textView, _textView.Selection.End.Position); ITextSnapshotLine endLine = endViewLine.End.GetContainingLine(); ITextSnapshot snapshot = endLine.Snapshot; @@ -360,23 +355,23 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // Handle the case where multiple lines are selected and the caret is sitting just after the line break on the next line. // Shortening the selection here handles the case where the last line is a collapsed region. Using endLine.End will give // a line within the collapsed region instead of skipping it all together. - if (GetLineEnd(view, startViewLine.Start) != endViewLine - && view.Selection.End.Position == GetLineStart(view, view.Selection.End.Position).Start - && !view.Selection.End.IsInVirtualSpace) + if (GetLineEnd(_textView, startViewLine.Start) != endViewLine + && _textView.Selection.End.Position == GetLineStart(_textView, _textView.Selection.End.Position).Start + && !_textView.Selection.End.IsInVirtualSpace) { endLine = snapshot.GetLineFromLineNumber(endLine.LineNumber - 1); - endViewLine = view.GetTextViewLineContainingBufferPosition(view.Selection.End.Position - 1); + endViewLine = _textView.GetTextViewLineContainingBufferPosition(_textView.Selection.End.Position - 1); } - #region Initial Asserts +#region Initial Asserts - Debug.Assert(view.Selection.Start.Position.Snapshot == view.TextSnapshot, "Selection is out of sync with view."); + Debug.Assert(_textView.Selection.Start.Position.Snapshot == _textView.TextSnapshot, "Selection is out of sync with view."); - Debug.Assert(view.TextSnapshot == view.TextBuffer.CurrentSnapshot, "View is out of sync with text buffer."); + Debug.Assert(_textView.TextSnapshot == _textView.TextBuffer.CurrentSnapshot, "View is out of sync with text buffer."); - Debug.Assert(view.TextSnapshot == snapshot, "Text view lines are out of sync with the view"); + Debug.Assert(_textView.TextSnapshot == snapshot, "Text view lines are out of sync with the view"); - #endregion +#endregion // check if we are at the end of the file if ((endLine.LineNumber + 1) >= snapshot.LineCount) @@ -387,11 +382,11 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation else { // nextLineExtent is different from prevLine.Extent and avoids issues around collapsed regions - var lastNextLine = GetLineEnd(view, endViewLine.EndIncludingLineBreak); + ITextViewLine lastNextLine = GetLineEnd(_textView, endViewLine.EndIncludingLineBreak); SnapshotSpan nextLineExtent = new SnapshotSpan(endViewLine.EndIncludingLineBreak, lastNextLine.End); SnapshotSpan nextLineExtentIncludingLineBreak = new SnapshotSpan(endViewLine.EndIncludingLineBreak, lastNextLine.EndIncludingLineBreak); - using (ITextEdit edit = view.TextBuffer.CreateEdit()) + using (ITextEdit edit = _textView.TextBuffer.CreateEdit()) { SnapshotSpan curLineExtent = new SnapshotSpan(startViewLine.Start, endViewLine.End); SnapshotSpan curLineExtentIncLineBreak = new SnapshotSpan(startViewLine.Start, endViewLine.EndIncludingLineBreak); @@ -410,7 +405,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation bool hasCollapsedRegions = false; IOutliningManager outliningManager = (_factory.OutliningManagerService != null) - ? _factory.OutliningManagerService.GetOutliningManager(view) + ? _factory.OutliningManagerService.GetOutliningManager(_textView) : null; if (outliningManager != null) @@ -425,7 +420,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { using (ITextUndoTransaction undoTransaction = _undoHistory.CreateTransaction(Strings.MoveSelLinesDown)) { - BeforeCollapsedMoveUndoPrimitive undoPrim = new BeforeCollapsedMoveUndoPrimitive(outliningManager, view, collapsedSpansInCurLine); + BeforeCollapsedMoveUndoPrimitive undoPrim = new BeforeCollapsedMoveUndoPrimitive(outliningManager, _textView, collapsedSpansInCurLine); undoTransaction.AddUndo(undoPrim); undoTransaction.Complete(); } @@ -455,11 +450,11 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation } else { - int anchorPos = view.Selection.AnchorPoint.Position.Position; - int anchorVirtualSpace = view.Selection.AnchorPoint.VirtualSpaces; - int activePos = view.Selection.ActivePoint.Position.Position; - int activeVirtualSpace = view.Selection.ActivePoint.VirtualSpaces; - var selectionMode = view.Selection.Mode; + int anchorPos = _textView.Selection.AnchorPoint.Position.Position; + int anchorVirtualSpace = _textView.Selection.AnchorPoint.VirtualSpaces; + int activePos = _textView.Selection.ActivePoint.Position.Position; + int activeVirtualSpace = _textView.Selection.ActivePoint.VirtualSpaces; + var selectionMode = _textView.Selection.Mode; ITextSnapshot newSnapshot = edit.Apply(); if (newSnapshot == snapshot) @@ -483,7 +478,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { // This comes from adhocoutliner.cs in env\editor\pkg\impl\outlining and will not be available outside of VS SimpleTagger<IOutliningRegionTag> simpleTagger = - view.TextBuffer.Properties.GetOrCreateSingletonProperty<SimpleTagger<IOutliningRegionTag>>(() => new SimpleTagger<IOutliningRegionTag>(view.TextBuffer)); + _textView.TextBuffer.Properties.GetOrCreateSingletonProperty<SimpleTagger<IOutliningRegionTag>>(() => new SimpleTagger<IOutliningRegionTag>(_textView.TextBuffer)); if (simpleTagger != null) { @@ -532,7 +527,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // we need to recollapse after a redo using (ITextUndoTransaction undoTransaction = _undoHistory.CreateTransaction(Strings.MoveSelLinesDown)) { - AfterCollapsedMoveUndoPrimitive undoPrim = new AfterCollapsedMoveUndoPrimitive(outliningManager, view, spansForUndo); + AfterCollapsedMoveUndoPrimitive undoPrim = new AfterCollapsedMoveUndoPrimitive(outliningManager, _textView, spansForUndo); undoTransaction.AddUndo(undoPrim); undoTransaction.Complete(); } @@ -555,7 +550,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation private static ITextViewLine GetLineStart(ITextView view, SnapshotPoint snapshotPoint) { - var line = view.GetTextViewLineContainingBufferPosition(snapshotPoint); + ITextViewLine line = view.GetTextViewLineContainingBufferPosition(snapshotPoint); while (!line.IsFirstTextViewLineForSnapshotLine) { line = view.GetTextViewLineContainingBufferPosition(line.Start - 1); @@ -565,7 +560,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation private static ITextViewLine GetLineEnd(ITextView view, SnapshotPoint snapshotPoint) { - var line = view.GetTextViewLineContainingBufferPosition(snapshotPoint); + ITextViewLine line = view.GetTextViewLineContainingBufferPosition(snapshotPoint); while (!line.IsLastTextViewLineForSnapshotLine) { line = view.GetTextViewLineContainingBufferPosition(line.EndIncludingLineBreak); @@ -573,10 +568,10 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return line; } - #endregion +#endregion - #region IEditorOperations Members +#region IEditorOperations Members public void SelectAndMoveCaret(VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint) { @@ -592,41 +587,14 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { bool empty = (anchorPoint == activePoint); - // TODO: Whenever caret/selection is updated to offer a way to set both simultaneously without either eventing before - // the other is updated, we should update this method to use that. There are potential bugs below in how clients - // react to things like selection moving. For example, if someone reacts to moving the selection by moving the caret, - // the logic below will override that caret position, which may not be desirable. - - // The order of operations here is important: - // 1) We need to move the selection first. Clients (like VB) who listen for caret change need the selection to be correct, - // and we have yet to have clients that require the opposite order. See Dev10 #793198 for what happens when we do this selection-first. - // - // 2) Then we move the caret. This behaves differently, depending on if the new selection is empty or not (explained below). - - if (empty) + var selection = new Selection(anchorPoint, activePoint); + if (selectionMode == TextSelectionMode.Box) { - _textView.Selection.Clear(); - _textView.Selection.Mode = selectionMode; - - // Since the selection is empty, move the caret to the provided active point and translate that point - // to the view's text snapshot (in case someone was listening to the selection changed event and made a text edit). - // The empty selection will track the caret. - // See Dev10 #785792 for an example of what happens when we get this wrong by moving the caret to the active point - // of the selection when the selection is being cleared. - _textView.Caret.MoveTo(activePoint.TranslateTo(_textView.TextSnapshot)); + _multiSelectionBroker.SetBoxSelection(selection); } else { - _textView.Selection.Select(anchorPoint, activePoint); - _textView.Selection.Mode = selectionMode; - - // Move the caret to the active point of the selection (don't use activePoint since someone -- on the selection changed event -- might have - // moved the selection). - // But if the selection is empty (it shouldn't be since anchorPoint != activePoint, but those points could be normalized to an empty span - // or someone could have moved it), move the caret to the requested activePoint. - _textView.Caret.MoveTo(_textView.Selection.IsEmpty - ? activePoint.TranslateTo(_textView.TextSnapshot) - : _textView.Selection.ActivePoint); + _multiSelectionBroker.SetSelection(selection); } // 3) If scrollOptions were provided, we're going to try and make the span visible using the provided options. @@ -657,7 +625,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void MoveToNextCharacter(bool select) { - _editorPrimitives.Caret.MoveToNextCharacter(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToNextCaretPosition : PredefinedSelectionTransformations.MoveToNextCaretPosition); + _textView.Caret.EnsureVisible(); } /// <summary> @@ -668,18 +637,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void MoveToPreviousCharacter(bool select) { - bool isCaretAtStartOfViewLine = (!_textView.Caret.InVirtualSpace) && - (_textView.Caret.Position.BufferPosition == _textView.Caret.ContainingTextViewLine.Start); - - //Prevent the caret from moving from column 0 to the end of the previous line if either: - // virtual space is turned on or - // the user is extending a box selection. - if (isCaretAtStartOfViewLine && (_editorOptions.IsVirtualSpaceEnabled() || (select && (_textView.Selection.Mode == TextSelectionMode.Box)))) - { - return; - } - - _editorPrimitives.Caret.MoveToPreviousCharacter(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToPreviousCaretPosition : PredefinedSelectionTransformations.MoveToPreviousCaretPosition); + _textView.Caret.EnsureVisible(); } /// <summary> @@ -690,7 +649,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void MoveToNextWord(bool select) { - _editorPrimitives.Caret.MoveToNextWord(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToNextWord : PredefinedSelectionTransformations.MoveToNextWord); + _textView.Caret.EnsureVisible(); } /// <summary> @@ -701,15 +661,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void MoveToPreviousWord(bool select) { - // In extending a box selection, we don't want this to jump to the previous line (if - // we are on the beginning of a line) - if (select && _textView.Selection.Mode == TextSelectionMode.Box && !_textView.Caret.InVirtualSpace) - { - if (_editorPrimitives.Caret.CurrentPosition == _editorPrimitives.Caret.StartOfViewLine) - return; - } - - _editorPrimitives.Caret.MoveToPreviousWord(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToPreviousWord : PredefinedSelectionTransformations.MoveToPreviousWord); + _textView.Caret.EnsureVisible(); } /// <summary> @@ -720,7 +673,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void MoveToStartOfDocument(bool select) { - _editorPrimitives.Caret.MoveToStartOfDocument(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToStartOfDocument : PredefinedSelectionTransformations.MoveToStartOfDocument); + _textView.Caret.EnsureVisible(); } /// <summary> @@ -731,7 +685,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void MoveToEndOfDocument(bool select) { - _editorPrimitives.Caret.MoveToEndOfDocument(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToEndOfDocument : PredefinedSelectionTransformations.MoveToEndOfDocument); + _textView.Caret.EnsureVisible(); } /// <summary> @@ -928,110 +883,267 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </summary> public bool Backspace() { - bool emptyBox = IsEmptyBoxSelection(); - NormalizedSnapshotSpanCollection boxDeletions = null; + bool success = true; - // First, handle cases that don't require edits - if (_textView.Selection.IsEmpty) + if (WillBackspaceCreateEdit()) { - if (_textView.Caret.InVirtualSpace) - { - this.MoveCaretToPreviousIndentStopInVirtualSpace(); + var selections = _multiSelectionBroker.AllSelections; + var boxSelection = _multiSelectionBroker.BoxSelection; + var primarySelection = _multiSelectionBroker.PrimarySelection; - _textView.Caret.EnsureVisible(); - return true; - } - if (_textView.Caret.Position.BufferPosition.Position == 0) + Func<bool> action = () => { - return true; - } + using (_multiSelectionBroker.BeginBatchOperation()) + { + if (TryBackspaceEdit(selections)) + { + return TryPostBackspaceSelectionUpdate(selections, primarySelection, boxSelection); + } + } + return false; + }; + + success = ExecuteAction(Strings.DeleteCharToLeft, action, SelectionUpdate.Ignore, ensureVisible: false); } - // If the entire selection is in virtual space, clear it - else if (_textView.Selection.VirtualSelectedSpans.All(s => s.SnapshotSpan.IsEmpty && s.IsInVirtualSpace)) + else { - this.ResetVirtualSelection(); - _textView.Caret.EnsureVisible(); - return true; + success = TryBackspaceSelections(); } - else if (emptyBox) // empty box selection, make sure it is valid + + if (success) { - List<SnapshotSpan> spans = new List<SnapshotSpan>(); + _multiSelectionBroker.TryEnsureVisible(_multiSelectionBroker.PrimarySelection, EnsureSpanVisibleOptions.MinimumScroll); + } + + return success; + } + + private bool TryPostBackspaceSelectionUpdate(IReadOnlyList<Selection> selections, Selection primarySelection, Selection boxSelection) + { + // Throughout this method, the parameters passed in are the OLD values, and the parameters on _multiSelectionBroker are the NEW ones - foreach (var span in _textView.Selection.VirtualSelectedSpans.Where(s => !s.IsInVirtualSpace).Select(s => s.SnapshotSpan)) + if (boxSelection != Selection.Invalid) + { + // If this is an empty box, we may need to capture the new active/anchor points, as points in virtual space + // won't track as we want them to through the edit. + VirtualSnapshotPoint anchorPoint = _multiSelectionBroker.BoxSelection.AnchorPoint; + VirtualSnapshotPoint activePoint = _multiSelectionBroker.BoxSelection.ActivePoint; + + if (primarySelection.IsEmpty) { - var line = span.Start.GetContainingLine(); - if (span.Start > line.Start) + if (boxSelection.AnchorPoint.IsInVirtualSpace) + { + anchorPoint = new VirtualSnapshotPoint(_multiSelectionBroker.BoxSelection.AnchorPoint.Position, boxSelection.AnchorPoint.VirtualSpaces - 1); + } + if (boxSelection.ActivePoint.IsInVirtualSpace) { - spans.Add(_textView.GetTextElementSpan(span.Start - 1)); + activePoint = new VirtualSnapshotPoint(_multiSelectionBroker.BoxSelection.ActivePoint.Position, boxSelection.ActivePoint.VirtualSpaces - 1); } } + else + { + // Just take the starting points in the first and last selections + activePoint = selections[boxSelection.IsReversed ? 0 : selections.Count - 1].Start; + anchorPoint = selections[boxSelection.IsReversed ? selections.Count - 1 : 0].Start; + } - // If there is nothing to delete, clear the selection - if (spans.Count == 0) + VirtualSnapshotPoint newAnchor = anchorPoint.TranslateTo(_textView.TextSnapshot); + VirtualSnapshotPoint newActive = activePoint.TranslateTo(_textView.TextSnapshot); + + var newSelection = new Selection(insertionPoint: newActive, anchorPoint: newAnchor, activePoint: newActive, boxSelection.InsertionPointAffinity); + if (_multiSelectionBroker.BoxSelection != newSelection) { - _textView.Caret.MoveTo(_textView.Selection.Start); - _textView.Selection.Clear(); - _textView.Caret.EnsureVisible(); - return true; + _multiSelectionBroker.SetBoxSelection(newSelection); } + } + else + { + // Perf: This is actually an n^2 algorithm here, since TryPerform... also loops through all the selections. Try to avoid copying this code + // elsewhere. We need it here because we're actually modifying each one based on its context AND because merges can happen with backspace so we + // can't do anything funny like caching the transformers. + for (int i = 0; i < selections.Count; i++) + { + //Some could have merged away, ignore return values here intentionally. + _multiSelectionBroker.TryPerformActionOnSelection(selections[i], transformer => + { + // We can't use the virtual snapshot point TranslateTo since it will remove the virtual space (because the line's line break was deleted). + // VirtualSnapshotPoint.TranslateTo doesn't know what to do with virtual whitespace, so we have to do this ourselves. + if (selections[i].IsEmpty && selections[i].InsertionPoint.IsInVirtualSpace) + { + // Move the caret back one if we have an empty selection + transformer.MoveTo(new VirtualSnapshotPoint(transformer.Selection.InsertionPoint.Position, selections[i].InsertionPoint.VirtualSpaces - 1), + select: false, + insertionPointAffinity: PositionAffinity.Successor); + } + else + { + //Move the caret to the start of the selection. + transformer.MoveTo(new VirtualSnapshotPoint(transformer.Selection.InsertionPoint.Position, selections[i].Start.VirtualSpaces), + select: false, + PositionAffinity.Successor); + } + }, out _); + } + } + + return true; + } - boxDeletions = new NormalizedSnapshotSpanCollection(spans); + private bool WillBackspaceCreateEdit() + { + if (_multiSelectionBroker.IsBoxSelection) + { + // Edits can not happen if we're a box selection at the beginning of a line + var primary = _multiSelectionBroker.PrimarySelection; + if (primary.IsEmpty && primary.Start.Position == primary.Start.Position.GetContainingLine().Start) + { + return false; + } } - // Now, handle cases that require edits - Func<bool> action = () => + var selections = _multiSelectionBroker.AllSelections; + for (int i = 0; i < selections.Count; i++) { - // 1. An empty selection mean backspace the caret - if (_textView.Selection.IsEmpty) - return _editorPrimitives.Caret.DeletePrevious(); + if ((!selections[i].Extent.SnapshotSpan.IsEmpty) || + (selections[i].IsEmpty + && !selections[i].InsertionPoint.IsInVirtualSpace + && selections[i].InsertionPoint.Position.Position != 0)) + { + return true; + } + } - // 2. If this is an empty box, we may need to capture the new active/anchor points, as points in virtual space - // won't track as we want them to through the edit. - VirtualSnapshotPoint? anchorPoint = null; - VirtualSnapshotPoint? activePoint = null; + return false; + } - if (emptyBox) + private bool TryBackspaceEdit(IReadOnlyList<Selection> selections) + { + using (var edit = _textView.TextBuffer.CreateEdit()) + { + for (int i = (selections.Count - 1); i >= 0; i--) { - if (_textView.Selection.AnchorPoint.IsInVirtualSpace) + var selection = selections[i]; + + if (selection.IsEmpty) { - anchorPoint = new VirtualSnapshotPoint(_textView.Selection.AnchorPoint.Position, _textView.Selection.AnchorPoint.VirtualSpaces - 1); + if (selection.Extent.IsInVirtualSpace) + { + continue; + } + + if (!TryBackspaceEmptySelection(selection, edit)) + { + return false; + } } - if (_textView.Selection.ActivePoint.IsInVirtualSpace) + else if (!edit.Delete(selection.Extent.SnapshotSpan)) { - activePoint = new VirtualSnapshotPoint(_textView.Selection.ActivePoint.Position, _textView.Selection.ActivePoint.VirtualSpaces - 1); + return false; } } - // 3. The selection is non-empty, so delete the selected spans (unless this is an empty box selection: An empty box selection means treat this as a backspace on each line) - NormalizedSnapshotSpanCollection deletion = boxDeletions ?? _textView.Selection.SelectedSpans; + edit.Apply(); + return !edit.Canceled; + } + } + + private bool TryBackspaceEmptySelection(Selection selection, ITextEdit edit) + { + // Assumptions: + // We should have already validated this before calling. + Debug.Assert(selection.IsEmpty); - int selectionStartVirtualSpaces = _textView.Selection.Start.VirtualSpaces; + // This method is only written to perform edits on text. Virtual space operations should be performed separately and not passed here. + Debug.Assert(!selection.InsertionPoint.IsInVirtualSpace); - if (!DeleteHelper(deletion)) - return false; + // Performing deletion: + // Identify what should be deleted + if (selection.InsertionPoint.Position.Position == 0) + { + // We're at the beginning of the document, we're done. + return true; + } - // 5. Now, fix up the start and end points if this is an empty box - if (emptyBox && (anchorPoint.HasValue || activePoint.HasValue)) - { - VirtualSnapshotPoint newAnchor = (anchorPoint.HasValue) ? anchorPoint.Value.TranslateTo(_textView.TextSnapshot) : _textView.Selection.AnchorPoint; - VirtualSnapshotPoint newActive = (activePoint.HasValue) ? activePoint.Value.TranslateTo(_textView.TextSnapshot) : _textView.Selection.ActivePoint; + // Get the span of the previous element + SnapshotSpan previousElementSpan = TextView.GetTextElementSpan(selection.InsertionPoint.Position - 1); + + // Here we have some interesting decisions to make. If this is a collapsed region, we want to delete the whole thing. + // If this is a multi-byte character, we typically want to delete just one byte to allow for easier typing in chinese and other languages. + // However, if that multi-byte character is a surrogate pair or newline, we want to delete the whole thing. + + // We start by looking to see if this is a collapsed region or something like one. + if ((previousElementSpan.Length > 0) && + (_textView.TextViewModel.IsPointInVisualBuffer(selection.InsertionPoint.Position, PositionAffinity.Successor)) && + (!_textView.TextViewModel.IsPointInVisualBuffer(previousElementSpan.End - 1, PositionAffinity.Successor))) + { + // Since the previous character is not visible but the current one is, delete + // the entire previous text element span. + return edit.Delete(previousElementSpan); + } + else + { + //Next we test for surrogate pairs and newline: + ITextSnapshot snapshot = edit.Snapshot; + int previousPosition = selection.InsertionPoint.Position.Position - 1; + + int index = previousPosition; + char currentCharacter = snapshot[previousPosition]; - _textView.Caret.MoveTo(_textView.Selection.ActivePoint); - _textView.Selection.Select(newAnchor, newActive); + // By default VS (and many other apps) will delete only the last character + // of a combining character sequence. The one exception to this rule is + // surrogate pais which we are handling here. + if (char.GetUnicodeCategory(currentCharacter) == UnicodeCategory.Surrogate) + { + index--; } - else if (_textView.Selection.Mode != TextSelectionMode.Box) + + if ((index > 0) && + (currentCharacter == '\n') && + (snapshot[previousPosition - 1] == '\r')) { - //Move the caret to the start of the selection (this doesn't happen automatically if the caret was in virtual space). - //But we can't use the virtual snapshot point TranslateTo since it will remove the virtual space (because the line's line break was deleted). - _textView.Caret.MoveTo(new VirtualSnapshotPoint(_textView.Selection.Start.Position, selectionStartVirtualSpaces)); - _textView.Selection.Clear(); + index--; } - _textView.Caret.EnsureVisible(); - return true; - }; + // With index moved back in the cases of newline and surrogate pairs, this delete should handle all other cases. + return edit.Delete(new Span(index, previousPosition - index + 1)); + } + } - return ExecuteAction(Strings.DeleteCharToLeft, action, SelectionUpdate.ResetUnlessEmptyBox, true); + private bool TryBackspaceSelections() + { + if (_multiSelectionBroker.IsBoxSelection && _multiSelectionBroker.PrimarySelection.InsertionPoint.IsInVirtualSpace) + { + _multiSelectionBroker.SetSelection(new Selection(_multiSelectionBroker.PrimarySelection.Start)); + } + else if (!_multiSelectionBroker.IsBoxSelection) + { + _multiSelectionBroker.PerformActionOnAllSelections(transformer => + { + if (transformer.Selection.IsEmpty) + { + if (transformer.Selection.InsertionPoint.IsInVirtualSpace) + { + MoveToPreviousTabStop(transformer); + } + else + { + transformer.PerformAction(PredefinedSelectionTransformations.MoveToPreviousCaretPosition); + } + } + else + { + transformer.MoveTo(transformer.Selection.Start, select: false, PositionAffinity.Successor); + } + }); + } + + return true; + } + + private void MoveToPreviousTabStop(ISelectionTransformer transformer) + { + var previousStop = GetPreviousIndentStopInVirtualSpace(transformer.Selection.InsertionPoint); + transformer.MoveTo(previousStop, select: false, PositionAffinity.Successor); } private void ResetVirtualSelection() @@ -1048,8 +1160,12 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation ITextViewLine activeLine = (_textView.Selection.IsReversed) ? startLine : endLine; VirtualSnapshotPoint newCaret = activeLine.GetInsertionBufferPositionFromXCoordinate(leftEdge); - _textView.Caret.MoveTo(newCaret); - _textView.Selection.Clear(); + _multiSelectionBroker.ClearSecondarySelections(); + Selection unused; + _multiSelectionBroker.TryPerformActionOnSelection(_multiSelectionBroker.PrimarySelection, transformer => + { + transformer.MoveTo(newCaret, select: false, PositionAffinity.Successor); + }, out unused); } public bool DeleteFullLine() @@ -1255,7 +1371,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { if (textLine == null) { - throw new ArgumentNullException("textLine"); + throw new ArgumentNullException(nameof(textLine)); } if (extendSelection) @@ -1288,7 +1404,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void MoveLineUp(bool select) { - _editorPrimitives.Caret.MoveToPreviousLine(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToPreviousLine : PredefinedSelectionTransformations.MoveToPreviousLine); + _textView.Caret.EnsureVisible(); } /// <summary> @@ -1299,7 +1416,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void MoveLineDown(bool select) { - _editorPrimitives.Caret.MoveToNextLine(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToNextLine : PredefinedSelectionTransformations.MoveToNextLine); + _textView.Caret.EnsureVisible(); } /// <summary> @@ -1310,7 +1428,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void PageUp(bool select) { - _editorPrimitives.Caret.MovePageUp(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectPageUp : PredefinedSelectionTransformations.MovePageUp); + } /// <summary> @@ -1321,7 +1440,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void PageDown(bool select) { - _editorPrimitives.Caret.MovePageDown(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectPageDown : PredefinedSelectionTransformations.MovePageDown); } /// <summary> @@ -1332,34 +1451,14 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </param> public void MoveToEndOfLine(bool select) { - // If the caret is at the start of an empty line, respond by trying to position - // the caret at the smart indent location. - if (_textView.Caret.Position.BufferPosition.GetContainingLine().Extent.IsEmpty && - !_textView.Caret.InVirtualSpace) - { - if (PositionCaretWithSmartIndent(useOnlyVirtualSpace: true, extendSelection: select)) - { - _editorPrimitives.Caret.EnsureVisible(); - return; - } - } - - _editorPrimitives.Caret.MoveToEndOfViewLine(select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToEndOfLine : PredefinedSelectionTransformations.MoveToEndOfLine); + _textView.Caret.EnsureVisible(); } public void MoveToHome(bool select) { - int newPosition = _editorPrimitives.Caret.GetFirstNonWhiteSpaceCharacterOnViewLine().CurrentPosition; - - // If the caret is already at the first non-whitespace character or - // the line is entirely whitepsace, move to the start of the view line. - if (newPosition == _editorPrimitives.Caret.CurrentPosition || - newPosition == _editorPrimitives.Caret.EndOfViewLine) - { - newPosition = _editorPrimitives.Caret.StartOfViewLine; - } - - _editorPrimitives.Caret.MoveTo(newPosition, select); + _multiSelectionBroker.PerformActionOnAllSelections(select ? PredefinedSelectionTransformations.SelectToHome : PredefinedSelectionTransformations.MoveToHome); + _textView.Caret.EnsureVisible(); } public void MoveToStartOfLine(bool select) @@ -1374,117 +1473,103 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { Func<bool> action = () => { - VirtualSnapshotPoint caret = _textView.Caret.Position.VirtualBufferPosition; - ITextSnapshotLine line = caret.Position.GetContainingLine(); - ITextSnapshot snapshot = line.Snapshot; - - // todo: the following logic is duplicated in DefaultTextPointPrimitive.InsertNewLine() - // didn't call that method here because it would result in two text transactions - // ultimately everything here should probably move into primitives. - string textToInsert = TextBufferOperationHelpers.GetNewLineCharacterToInsert(line, _editorOptions); + bool editSucceeded = true; + ITextSnapshot snapshot = _textView.TextViewModel.EditBuffer.CurrentSnapshot; - bool succeeded = false; - bool caretMoved = false; - EventHandler<CaretPositionChangedEventArgs> caretWatcher = delegate (object sender, CaretPositionChangedEventArgs e) + using (var batchOp = _multiSelectionBroker.BeginBatchOperation()) { - caretMoved = true; - }; + var toIndent = new HashSet<object>(); - // Indent unless the caret is at column 0 or the current line is empty. - // This appears to be added as a fix for Venus; which combined with our implementation of - // PositionCaretWithSmartIndent does not indent correctly on NewLine when Caret is at column 0. - bool doIndent = caret.IsInVirtualSpace || (caret.Position != _textView.Caret.ContainingTextViewLine.Start) - || (_textView.Caret.ContainingTextViewLine.Extent.Length == 0); - - try - { using (var edit = _textView.TextBuffer.CreateEdit()) { - _textView.Caret.PositionChanged += caretWatcher; - int searchIndexforPreviousWhitespaces = -1; - var lineContainingTrimTrailingWhitespacesSearchindex = line; // usually is the line containing caret. + if (_multiSelectionBroker.IsBoxSelection) + { + _multiSelectionBroker.BreakBoxSelection(); + } - if (_textView.Selection.Mode == TextSelectionMode.Stream) + _multiSelectionBroker.PerformActionOnAllSelections(transformer => { + bool doIndent = false; + + VirtualSnapshotPoint caret = transformer.Selection.InsertionPoint; + ITextSnapshotLine line = caret.Position.GetContainingLine(); + ITextViewLine viewLine = _textView.GetTextViewLineContainingBufferPosition(caret.Position); + + // todo: the following logic is duplicated in DefaultTextPointPrimitive.InsertNewLine() + // didn't call that method here because it would result in two text transactions + // ultimately everything here should probably move into primitives. + string textToInsert = TextBufferOperationHelpers.GetNewLineCharacterToInsert(line, _editorOptions); + + // Indent unless the caret is at column 0 or the current line is empty. + // This appears to be added as a fix for Venus; which combined with our implementation of + // PositionCaretWithSmartIndent does not indent correctly on NewLine when Caret is at column 0. + doIndent = caret.IsInVirtualSpace || (caret.Position != viewLine.Extent.Start) + || (viewLine.Extent.Length == 0); + + int searchIndexforPreviousWhitespaces = -1; + var lineContainingTrimTrailingWhitespacesSearchindex = line; // usually is the line containing caret. + // This ignores virtual space - Span selection = _textView.Selection.StreamSelectionSpan.SnapshotSpan; - succeeded = edit.Replace(selection, textToInsert); + Span selection = transformer.Selection.Extent.SnapshotSpan; + + editSucceeded = editSucceeded && edit.Replace(selection, textToInsert); // For stream selection you should always look for trimming whitespaces previous to selection.start instead of caret position lineContainingTrimTrailingWhitespacesSearchindex = snapshot.GetLineFromPosition(selection.Start); searchIndexforPreviousWhitespaces = selection.Start - lineContainingTrimTrailingWhitespacesSearchindex.Start.Position; - } - else - { - var isDeleteSuccessfull = true; - searchIndexforPreviousWhitespaces = caret.Position.Position - line.Start.Position; - foreach (var span in _textView.Selection.SelectedSpans) + // Trim traling whitespaces as we insert the new line as well if the editor option is set + if (_editorOptions.GetOptionValue<bool>(DefaultOptions.TrimTrailingWhiteSpaceOptionId)) { - // In a box selection if the caret is forward positioned then - //we should search for whitespaces from the start of the last span since the spans are not yet deleted - if (span.End.Position == caret.Position.Position) - { - searchIndexforPreviousWhitespaces = span.Start.Position - line.Start.Position; - } - if (!edit.Delete(span)) - { - isDeleteSuccessfull = false; - } - } - if (!isDeleteSuccessfull) - return false; - succeeded = edit.Replace(new SnapshotSpan(_textView.Caret.Position.BufferPosition, 0), - textToInsert); - } - // Trim traling whitespaces as we insert the new line as well if the editor option is set - if (_editorOptions.GetOptionValue<bool>(DefaultOptions.TrimTrailingWhiteSpaceOptionId)) - { - var previousNonWhitespaceCharacterIndex = lineContainingTrimTrailingWhitespacesSearchindex.IndexOfPreviousNonWhiteSpaceCharacter(searchIndexforPreviousWhitespaces); + var previousNonWhitespaceCharacterIndex = lineContainingTrimTrailingWhitespacesSearchindex.IndexOfPreviousNonWhiteSpaceCharacter(searchIndexforPreviousWhitespaces); - // Note: If previousNonWhiteSpaceCharacter index is -1 this will automatically default to line.start.position - var startIndexForTrailingWhitespaceSpan = lineContainingTrimTrailingWhitespacesSearchindex.Start.Position + previousNonWhitespaceCharacterIndex + 1; - var lengthOfTrailingWhitespaceSpan = searchIndexforPreviousWhitespaces - previousNonWhitespaceCharacterIndex - 1; + // Note: If previousNonWhiteSpaceCharacter index is -1 this will automatically default to line.start.position + var startIndexForTrailingWhitespaceSpan = lineContainingTrimTrailingWhitespacesSearchindex.Start.Position + previousNonWhitespaceCharacterIndex + 1; + var lengthOfTrailingWhitespaceSpan = searchIndexforPreviousWhitespaces - previousNonWhitespaceCharacterIndex - 1; - if (lengthOfTrailingWhitespaceSpan != 0) // If there are any whitespaces before the caret delete them - edit.Delete(new Span(startIndexForTrailingWhitespaceSpan, lengthOfTrailingWhitespaceSpan)); - } + if (lengthOfTrailingWhitespaceSpan != 0) // If there are any whitespaces before the caret delete them + edit.Delete(new Span(startIndexForTrailingWhitespaceSpan, lengthOfTrailingWhitespaceSpan)); + } + + if (doIndent) + { + // WARNING: We're caching the transformers here because we are both inside a batch operation + // and we're inserting text, so we know that there will be no merging of selections going on. + // We're using them as a perf optimization so we can avoid searching through the list of selections + // later, since we already know what we need. + // + // When writing multiple selection-aware code, do everything you can to avoid saving transformers. + toIndent.Add(transformer); + } + }); // Apply all changes - succeeded = (edit.Apply() != snapshot); + editSucceeded = editSucceeded && (edit.Apply() != snapshot); } - } - finally - { - _textView.Caret.PositionChanged -= caretWatcher; - } - if (succeeded) - { - if (doIndent) + if (editSucceeded && toIndent.Count > 0) { - caret = _textView.Caret.Position.VirtualBufferPosition; - line = caret.Position.GetContainingLine(); - - //Only attempt to auto indent if -- after the edit above -- no one moved the caret on the buffer change - //and the caret is at the start of its new line (no one did any funny edits to the buffer on the buffer change). - if ((!caretMoved) && (caret.Position == line.Start)) - { - caretMoved = PositionCaretWithSmartIndent(useOnlyVirtualSpace: false, extendSelection: false); - if (!caretMoved && caret.IsInVirtualSpace) - { - //No smart indent logic so make sure the caret is not in virtual space. - _textView.Caret.MoveTo(caret.Position); - } - } + // Need to move carets to indented location after the edit has completed, so we put them at the correct indentation in the new snapshot. + _multiSelectionBroker.PerformActionOnAllSelections(transformer => + { + if (toIndent.Contains(transformer)) + { + var caretMoved = PositionCaretWithSmartIndent(transformer, useOnlyVirtualSpace: false, extendSelection: false); + if (!caretMoved && transformer.Selection.InsertionPoint.IsInVirtualSpace) + { + //No smart indent logic so make sure the caret is not in virtual space. + transformer.MoveTo(new VirtualSnapshotPoint(transformer.Selection.InsertionPoint.Position), select: false, PositionAffinity.Successor); + } + transformer.PerformAction(PredefinedSelectionTransformations.ClearSelection); + transformer.CapturePreferredReferencePoint(); + } + }); } - ResetSelection(); } - return succeeded; + + return editSucceeded; }; return ExecuteAction(Strings.InsertNewLine, action, SelectionUpdate.Ignore, true); } - - public bool OpenLineAbove() { Func<bool> action = () => @@ -1767,11 +1852,10 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { Debug.Assert(startLine.LineNumber <= endLine.LineNumber); - var view = _textView as ITextView; bool isEditMade = false; bool success = true; - using (ITextEdit edit = view.TextBuffer.CreateEdit()) + using (ITextEdit edit = _textView.TextBuffer.CreateEdit()) { var currentSnapshot = _textView.TextBuffer.CurrentSnapshot; for (int i = startLine.LineNumber; i <= endLine.LineNumber; i++) @@ -1793,7 +1877,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return success; } - private Span? GetTrailingWhitespaceSpanToDelete(ITextSnapshotLine line) + private static Span? GetTrailingWhitespaceSpanToDelete(ITextSnapshotLine line) { int indexOfLastNonWhitespaceCharacter = -1; @@ -1864,7 +1948,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation public void SelectLine(ITextViewLine viewLine, bool extendSelection) { if (viewLine == null) - throw new ArgumentNullException("viewLine"); + throw new ArgumentNullException(nameof(viewLine)); SnapshotPoint anchor; SnapshotPoint active; @@ -1917,91 +2001,205 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// </summary> public bool Delete() { - bool emptyBox = IsEmptyBoxSelection(); - NormalizedSnapshotSpanCollection boxDeletions = null; + bool success = true; - // First, handle cases that don't require edits - if (_textView.Selection.IsEmpty) + if (WillDeleteCreateEdit()) { - if (_textView.Caret.Position.BufferPosition.Position == _textView.TextSnapshot.Length) + var selections = _multiSelectionBroker.AllSelections; + var boxSelection = _multiSelectionBroker.BoxSelection; + var primarySelection = _multiSelectionBroker.PrimarySelection; + + Func<bool> action = () => { - return true; - } + using (_multiSelectionBroker.BeginBatchOperation()) + { + if (TryDeleteEdit(selections)) + { + return TryPostDeleteSelectionUpdate(selections, primarySelection, boxSelection); + } + } + return false; + }; + + success = ExecuteAction(Strings.DeleteCharToRight, action, SelectionUpdate.Ignore, ensureVisible: false); } - // If the entire selection is empty and in virtual space, clear it - else if (_textView.Selection.VirtualSelectedSpans.All(s => s.SnapshotSpan.IsEmpty && s.IsInVirtualSpace)) + else { - this.ResetVirtualSelection(); - return true; + success = TryDeleteSelections(); } - else if (emptyBox) // empty box selection, make sure it is valid + + if (success) { - List<SnapshotSpan> spans = new List<SnapshotSpan>(); + _multiSelectionBroker.TryEnsureVisible(_multiSelectionBroker.PrimarySelection, EnsureSpanVisibleOptions.MinimumScroll); + } - foreach (var span in _textView.Selection.SelectedSpans) + return success; + } + + private bool TryDeleteSelections() + { + if (_multiSelectionBroker.IsBoxSelection && _multiSelectionBroker.PrimarySelection.InsertionPoint.IsInVirtualSpace) + { + _multiSelectionBroker.SetSelection(new Selection(_multiSelectionBroker.PrimarySelection.Start)); + } + else if (!_multiSelectionBroker.IsBoxSelection) + { + _multiSelectionBroker.PerformActionOnAllSelections(transformer => { - var line = span.Start.GetContainingLine(); - if (span.Start < line.End) + if (!transformer.Selection.IsEmpty) { - spans.Add(_textView.GetTextElementSpan(span.Start)); + transformer.MoveTo(transformer.Selection.Start, select: false, PositionAffinity.Successor); } - } + }); + } + + return true; + } + + private bool TryPostDeleteSelectionUpdate(IReadOnlyList<Selection> selections, Selection primarySelection, Selection boxSelection) + { + // Throughout this method, the parameters passed in are the OLD values, and the parameters on _multiSelectionBroker are the NEW ones + if (boxSelection != Selection.Invalid) + { + // If this is an empty box, we may need to capture the new active/anchor points, as points in virtual space + // won't track as we want them to through the edit. + VirtualSnapshotPoint anchorPoint = _multiSelectionBroker.BoxSelection.AnchorPoint; + VirtualSnapshotPoint activePoint = _multiSelectionBroker.BoxSelection.ActivePoint; - // If there is nothing to delete, clear the selection - if (spans.Count == 0) + if (primarySelection.IsEmpty) { - _textView.Caret.MoveTo(_textView.Selection.Start); - _textView.Selection.Clear(); - return true; + if (boxSelection.AnchorPoint.IsInVirtualSpace) + { + anchorPoint = new VirtualSnapshotPoint(_multiSelectionBroker.BoxSelection.AnchorPoint.Position, boxSelection.AnchorPoint.VirtualSpaces); + } + if (boxSelection.ActivePoint.IsInVirtualSpace) + { + activePoint = new VirtualSnapshotPoint(_multiSelectionBroker.BoxSelection.ActivePoint.Position, boxSelection.ActivePoint.VirtualSpaces); + } } + else + { + // Just take the starting points in the first and last selections + activePoint = selections[boxSelection.IsReversed ? 0 : selections.Count - 1].Start; + anchorPoint = selections[boxSelection.IsReversed ? selections.Count - 1 : 0].Start; + } + + VirtualSnapshotPoint newAnchor = anchorPoint.TranslateTo(_textView.TextSnapshot); + VirtualSnapshotPoint newActive = activePoint.TranslateTo(_textView.TextSnapshot); - boxDeletions = new NormalizedSnapshotSpanCollection(spans); + var newSelection = new Selection(insertionPoint: newActive, anchorPoint: newAnchor, activePoint: newActive, boxSelection.InsertionPointAffinity); + if (_multiSelectionBroker.BoxSelection != newSelection) + { + _multiSelectionBroker.SetBoxSelection(newSelection); + } + } + else + { + // Perf: This is actually an n^2 algorithm here, since TryPerform... also loops through all the selections. Try to avoid copying this code + // elsewhere. We need it here because we're actually modifying each one based on its context AND because merges can happen with backspace so we + // can't do anything funny like caching the transformers. + for (int i = 0; i < selections.Count; i++) + { + //Some could have merged away, ignore return values here intentionally. + _multiSelectionBroker.TryPerformActionOnSelection(selections[i], transformer => + { + // We can't use the virtual snapshot point TranslateTo since it will remove the virtual space (because the line's line break was deleted). + // VirtualSnapshotPoint.TranslateTo doesn't know what to do with virtual whitespace, so we have to do this ourselves. + if (selections[i].IsEmpty && selections[i].InsertionPoint.IsInVirtualSpace) + { + // Move the caret back one if we have an empty selection + transformer.MoveTo(new VirtualSnapshotPoint(transformer.Selection.InsertionPoint.Position, selections[i].InsertionPoint.VirtualSpaces - 1), + select: false, + insertionPointAffinity: PositionAffinity.Successor); + } + else + { + //Move the caret to the start of the selection. + transformer.MoveTo(new VirtualSnapshotPoint(transformer.Selection.InsertionPoint.Position, selections[i].Start.VirtualSpaces), + select: false, + PositionAffinity.Successor); + } + }, out _); + } } + return true; + } - // Now handle cases that require edits - Func<bool> action = () => + private bool TryDeleteEdit(IReadOnlyList<Selection> selections) + { + using (var edit = _textView.TextBuffer.CreateEdit()) { - if (_textView.Selection.IsEmpty) + for (int i = (selections.Count - 1); i >= 0; i--) { - CaretPosition position = _textView.Caret.Position; - if (position.VirtualBufferPosition.IsInVirtualSpace) + var selection = selections[i]; + + if (selection.IsEmpty) { - string whitespace = GetWhitespaceForVirtualSpace(position.VirtualBufferPosition); - SnapshotSpan span = _textView.GetTextElementSpan(_textView.Caret.Position.VirtualBufferPosition.Position); + if (_multiSelectionBroker.IsBoxSelection) + { + var endOfLine = selection.InsertionPoint.Position.GetContainingLine().End; + + if (selection.InsertionPoint.Position == endOfLine) + { + continue; + } + } + + if (selection.InsertionPoint.IsInVirtualSpace) + { + var whitespace = GetWhitespaceForVirtualSpace(selection.InsertionPoint); + var span = _textView.GetTextElementSpan(selection.InsertionPoint.Position); - return ReplaceHelper(span, whitespace); + if (!edit.Replace(span, whitespace)) + { + return false; + } + } + else if (!edit.Delete(_textView.GetTextElementSpan(selection.InsertionPoint.Position))) + { + return false; + } } - else + else if (!edit.Delete(selection.Extent.SnapshotSpan)) { - return DeleteHelper(_textView.GetTextElementSpan(position.VirtualBufferPosition.Position)); + return false; } } - else - { - // The selection is non-empty, so delete selected spans - NormalizedSnapshotSpanCollection deletion = _textView.Selection.SelectedSpans; - // Unless it is an empty box selection, so treat it as a delete on each line - if (emptyBox && boxDeletions != null) - deletion = boxDeletions; + edit.Apply(); + return !edit.Canceled; + } + } - int selectionStartVirtualSpaces = _textView.Selection.Start.VirtualSpaces; - bool succeeded = DeleteHelper(deletion); + private bool WillDeleteCreateEdit() + { + var selections = _multiSelectionBroker.AllSelections; - if (succeeded && (_textView.Selection.Mode != TextSelectionMode.Box)) + if (_multiSelectionBroker.IsBoxSelection) + { + // Edits can not happen if we're a box selection at the end of every line + for (int i = 0; i < selections.Count; i++) + { + if (selections[i].Start.Position < selections[i].Start.Position.GetContainingLine().End) { - //Move the caret to the start of the selection (this doesn't happen automatically if the caret was in virtual space). - //But we can't use the virtual snapshot point TranslateTo since it will remove the virtual space (because the line's line break was deleted). - _textView.Caret.MoveTo(new VirtualSnapshotPoint(_textView.Selection.Start.Position, selectionStartVirtualSpaces)); - _textView.Selection.Clear(); + return true; } - - return succeeded; } - }; + } + else + { + for (int i = 0; i < selections.Count; i++) + { + if ((!selections[i].Extent.SnapshotSpan.IsEmpty) || + (selections[i].IsEmpty && selections[i].InsertionPoint.Position.Position != _multiSelectionBroker.CurrentSnapshot.Length)) + { + return true; + } + } + } - return ExecuteAction(Strings.DeleteText, action, SelectionUpdate.ResetUnlessEmptyBox, true); + return false; } /// <summary> @@ -2016,7 +2214,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // Validate if (text == null) { - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); } Func<bool> action = () => @@ -2042,7 +2240,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // Validate if (span.End > _textView.TextSnapshot.Length) { - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); } Func<bool> action = () => @@ -2077,7 +2275,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { if (searchText == null) { - throw new ArgumentNullException("searchText"); + throw new ArgumentNullException(nameof(searchText)); } FindData findData = new FindData(searchText, _textView.TextSnapshot); @@ -2341,7 +2539,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation } string streamText = string.Join(_editorOptions.GetNewLineCharacter() + whitespace, lines); - return this.InsertText(streamText.ToString(), true, Strings.Paste, isOverwriteModeEnabled: false); + return this.InsertText(streamText.ToString(CultureInfo.CurrentCulture), true, Strings.Paste, isOverwriteModeEnabled: false); } else { @@ -2438,7 +2636,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { // Validate if (lineNumber < 0 || lineNumber > _textView.TextSnapshot.LineCount - 1) - throw new ArgumentOutOfRangeException("lineNumber"); + throw new ArgumentOutOfRangeException(nameof(lineNumber)); ITextSnapshotLine line = _textView.TextSnapshot.GetLineFromLineNumber(lineNumber); @@ -2775,9 +2973,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation #endif } - #endregion // IEditorOperations Members +#endregion // IEditorOperations Members - #region Virtual Space to Whitespace helpers +#region Virtual Space to Whitespace helpers public string GetWhitespaceForVirtualSpace(VirtualSnapshotPoint point) { @@ -2866,9 +3064,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return textToInsert; } - #endregion +#endregion - #region Text insertion helpers +#region Text insertion helpers private bool InsertText(string text, bool final) { @@ -2880,7 +3078,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // Validate if (text == null) { - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); } if ((text.Length == 0) && !final) @@ -2937,7 +3135,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation } else { - replaceSpans = _textView.Selection.VirtualSelectedSpans; + replaceSpans = _multiSelectionBroker.VirtualSelectedSpans; } // The provisional composition span should be null here (the IME should @@ -2983,18 +3181,21 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation } else { - VirtualSnapshotPoint insertionPoint = _textView.Caret.Position.VirtualBufferPosition; - if (isOverwriteModeEnabled && !insertionPoint.IsInVirtualSpace) + var spans = new List<VirtualSnapshotSpan>(); + foreach (var caret in _multiSelectionBroker.GetSelectionsIntersectingSpan(new SnapshotSpan(_multiSelectionBroker.CurrentSnapshot, 0, _multiSelectionBroker.CurrentSnapshot.Length))) { - SnapshotPoint point = insertionPoint.Position; - replaceSpans = new VirtualSnapshotSpan[] { new VirtualSnapshotSpan( - new SnapshotSpan(point, _textView.GetTextElementSpan(point).End)) }; - } - else - { - replaceSpans = new VirtualSnapshotSpan[] { - new VirtualSnapshotSpan(insertionPoint, insertionPoint) }; + var insertionPoint = caret.InsertionPoint; + if (isOverwriteModeEnabled && !insertionPoint.IsInVirtualSpace) + { + SnapshotPoint point = insertionPoint.Position; + spans.Add(new VirtualSnapshotSpan(new SnapshotSpan(point, _textView.GetTextElementSpan(point).End))); + } + else + { + spans.Add(new VirtualSnapshotSpan(insertionPoint, insertionPoint)); + } } + replaceSpans = spans; } ITextVersion currentVersion = _textView.TextSnapshot.Version; @@ -3053,17 +3254,28 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation if (editSuccessful) { - // Get rid of virtual space if there is any, - // since we've just made it non-virtual - _textView.Caret.MoveTo(_textView.Caret.Position.BufferPosition); - _textView.Selection.Select( - new VirtualSnapshotPoint(_textView.Selection.AnchorPoint.Position), - new VirtualSnapshotPoint(_textView.Selection.ActivePoint.Position)); + if (_multiSelectionBroker.IsBoxSelection) + { + _textView.Caret.MoveTo(_textView.Caret.Position.BufferPosition); + _textView.Selection.Select( + new VirtualSnapshotPoint(_textView.Selection.AnchorPoint.Position), + new VirtualSnapshotPoint(_textView.Selection.ActivePoint.Position)); + + // If the selection ends up being non-empty (meaning not an empty + // single selection *or* an empty box), then clear it. + if (_textView.Selection.VirtualSelectedSpans.Any(s => !s.IsEmpty)) + _textView.Selection.Clear(); - // If the selection ends up being non-empty (meaning not an empty - // single selection *or* an empty box), then clear it. - if (_textView.Selection.VirtualSelectedSpans.Any(s => !s.IsEmpty)) - _textView.Selection.Clear(); + } + else + { + _multiSelectionBroker.PerformActionOnAllSelections(transformer => + { + // We've done the edit now. We need to both remove virtual space and clear selections. + var newInsertion = new VirtualSnapshotPoint(transformer.Selection.InsertionPoint.Position, 0); + transformer.MoveTo(newInsertion, select: false, PositionAffinity.Successor); + }); + } _textView.Caret.EnsureVisible(); @@ -3118,7 +3330,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation public bool InsertTextAsBox(string text, out VirtualSnapshotPoint boxStart, out VirtualSnapshotPoint boxEnd, string undoText) { if (text == null) - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); boxStart = boxEnd = _textView.Caret.Position.VirtualBufferPosition; @@ -3290,9 +3502,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return succeeded; } - #endregion +#endregion - #region Clipboard and RTF helpers +#region Clipboard and RTF helpers private Func<bool> PrepareClipboardSelectionCopy() { @@ -3376,7 +3588,6 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // which causes the OS to send out two almost simultaneous clipboard open/close notification pairs // which confuse applications that try to synchronize clipboard data between multiple machines such // as MagicMouse or remote desktop. - Clipboard.SetDataObject(dataObject, false); #endif @@ -3392,11 +3603,6 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation private string GenerateRtf(NormalizedSnapshotSpanCollection spans) { #if WINDOWS - if (_factory.RtfBuilderService == null) - { - return null; - } - //Don't generate RTF for large spans (since it is expensive and probably not wanted). int length = spans.Sum((span) => span.Length); if (length < _textView.Options.GetOptionValue(MaxRtfCopyLength.OptionKey)) @@ -3426,9 +3632,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return GenerateRtf(new NormalizedSnapshotSpanCollection(span)); } - #endregion +#endregion - #region Horizontal whitespace helpers +#region Horizontal whitespace helpers private bool DeleteHorizontalWhitespace() { @@ -3610,9 +3816,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return startPoint; } - #endregion +#endregion - #region Indent/unindent helpers +#region Indent/unindent helpers // Perform the given indent action (indent/unindent) on each line at the first non-whitespace // character, skipping lines that are either empty or just whitespace. @@ -3912,9 +4118,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return new VirtualSnapshotPoint(point.Position); } - #endregion +#endregion - #region Box Selection indent/unindent helpers +#region Box Selection indent/unindent helpers /// <summary> /// Given a "fix-up" anchor/active point determined before the box operation, fix up the current selection's @@ -4011,7 +4217,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { textPoint.MoveTo(i); string character = textPoint.GetNextCharacter(); - if (character != " " && character != "\t") + if (!string.Equals(character, " ", StringComparison.Ordinal) && !string.Equals(character, "\t", StringComparison.Ordinal)) break; column = textPoint.Column; @@ -4028,9 +4234,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return maxColumnUnindent; } - #endregion +#endregion - #region Miscellaneous line helpers +#region Miscellaneous line helpers private DisplayTextRange GetFullLines() { @@ -4094,9 +4300,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation return firstTextColumn.CurrentPosition == displayTextPoint.EndOfViewLine; } - #endregion +#endregion - #region Tabs <-> spaces +#region Tabs <-> spaces private bool ConvertSpacesAndTabsHelper(bool toTabs) { @@ -4214,16 +4420,16 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation } Span replaceSpan = Span.FromBounds(whiteSpaceStart, whiteSpaceEnd); - if ((replaceSpan.Length != textToInsert.Length) || (textToInsert != textEdit.Snapshot.GetText(replaceSpan))) //performance hack: don't get the text if we know they'll be different. + if ((replaceSpan.Length != textToInsert.Length) || (!string.Equals(textToInsert, textEdit.Snapshot.GetText(replaceSpan), StringComparison.Ordinal))) //performance hack: don't get the text if we know they'll be different. return textEdit.Replace(replaceSpan, textToInsert); } return true; } - #endregion +#endregion - #region Edit/Replace/Delete helpers +#region Edit/Replace/Delete helpers internal bool EditHelper(Func<ITextEdit, bool> editAction) { @@ -4300,7 +4506,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation }); } - #endregion +#endregion internal bool IsEmptyBoxSelection() { @@ -4318,9 +4524,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// <param name="extendSelection">If <c>true</c>, extend the current selection, from the existing anchor point, /// to the new caret position.</param> /// <returns><c>true</c> if the caret was positioned in virtual space.</returns> - private bool PositionCaretWithSmartIndent(bool useOnlyVirtualSpace = true, bool extendSelection = false) + private bool PositionCaretWithSmartIndent(ISelectionTransformer transformer, bool useOnlyVirtualSpace = true, bool extendSelection = false) { - var caretPosition = _textView.Caret.Position.VirtualBufferPosition; + var caretPosition = transformer.Selection.InsertionPoint; var caretLine = caretPosition.Position.GetContainingLine(); int? indentation = _factory.SmartIndentationService.GetDesiredIndentation(_textView, caretLine); @@ -4330,8 +4536,8 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { //Position the caret in virtual space at the appropriate indentation. var newCaretPoint = new VirtualSnapshotPoint(caretPosition.Position, Math.Max(0, indentation.Value - caretLine.Length)); - var anchorPoint = (extendSelection) ? _textView.Selection.AnchorPoint : newCaretPoint; - SelectAndMoveCaret(anchorPoint, newCaretPoint, selectionMode: TextSelectionMode.Stream, scrollOptions: null); + var anchorPoint = (extendSelection) ? transformer.Selection.AnchorPoint : newCaretPoint; + transformer.MoveTo(anchorPoint, newCaretPoint, newCaretPoint, PositionAffinity.Successor); return true; } else if (!useOnlyVirtualSpace) @@ -4355,7 +4561,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation /// <param name="line">Which line to evaluate</param> /// <param name="startPosition">Position where the count starts</param> /// <returns>Number of leading whitespace characters located after startPosition</returns> - private int GetLeadingWhitespaceChars(ITextSnapshotLine line, SnapshotPoint startPosition) + private static int GetLeadingWhitespaceChars(ITextSnapshotLine line, SnapshotPoint startPosition) { int whitespace = 0; for (int i = startPosition.Position; i < line.End; ++i) @@ -4390,7 +4596,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation SnapshotSpan currentWhiteSpace = new SnapshotSpan(line.Start, firstNonWhitespaceCharacter.AdvancedTextPoint); - if (whitespace != currentWhiteSpace.GetText()) + if (!string.Equals(whitespace, currentWhiteSpace.GetText(), StringComparison.Ordinal)) { if (!textEdit.Replace(currentWhiteSpace, whitespace)) return false; @@ -4572,7 +4778,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation if (line.LineBreakLength != 0) { string breakText = line.GetLineBreakText(); - if (breakText != replacement) + if (!string.Equals(breakText, replacement, StringComparison.Ordinal)) { if (!edit.Replace(line.End, line.LineBreakLength, replacement)) return false; diff --git a/src/Text/Impl/EditorOperations/EditorOperationsFactoryService.cs b/src/Text/Impl/EditorOperations/EditorOperationsFactoryService.cs index 7b1d693..a61fee1 100644 --- a/src/Text/Impl/EditorOperations/EditorOperationsFactoryService.cs +++ b/src/Text/Impl/EditorOperations/EditorOperationsFactoryService.cs @@ -22,6 +22,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation internal sealed class EditorOperationsFactoryService : IEditorOperationsFactoryService { [Import] + public IMultiSelectionBrokerFactory MultiSelectionBrokerFactory { get; set; } + + [Import] internal ITextStructureNavigatorSelectorService TextStructureNavigatorFactory { get; set; } #if WINDOWS @@ -76,7 +79,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // Validate if (textView == null) { - throw new ArgumentNullException("textView"); + throw new ArgumentNullException(nameof(textView)); } // Only one EditorOperations should be created per ITextView diff --git a/src/Text/Impl/EditorOperations/Strings.Designer.cs b/src/Text/Impl/EditorOperations/Strings.Designer.cs index ffebf8e..fd4a317 100644 --- a/src/Text/Impl/EditorOperations/Strings.Designer.cs +++ b/src/Text/Impl/EditorOperations/Strings.Designer.cs @@ -241,15 +241,6 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { } /// <summary> - /// Looks up a localized string similar to Expand/Contract Selection Command Handler. - /// </summary> - internal static string ExpandContractSelectionCommandHandlerName { - get { - return ResourceManager.GetString("ExpandContractSelectionCommandHandlerName", resourceCulture); - } - } - - /// <summary> /// Looks up a localized string similar to Increase line indent. /// </summary> internal static string IncreaseLineIndent { @@ -349,6 +340,15 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { } /// <summary> + /// Looks up a localized string similar to Next/Previous Issue. + /// </summary> + internal static string NextIssue { + get { + return ResourceManager.GetString("NextIssue", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Make Line Endings Consistent. /// </summary> internal static string NormalizeLineEndings { diff --git a/src/Text/Impl/EditorOperations/Strings.resx b/src/Text/Impl/EditorOperations/Strings.resx index 4792155..b53056d 100644 --- a/src/Text/Impl/EditorOperations/Strings.resx +++ b/src/Text/Impl/EditorOperations/Strings.resx @@ -261,10 +261,10 @@ <data name="DuplicateSelection" xml:space="preserve"> <value>Duplicate Selection</value> </data> - <data name="ExpandContractSelectionCommandHandlerName" xml:space="preserve"> - <value>Expand/Contract Selection Command Handler</value> - </data> <data name="DuplicateSelectionCommandHandlerName" xml:space="preserve"> <value>Duplicate Selection Command Handler</value> </data> + <data name="NextIssue" xml:space="preserve"> + <value>Next/Previous Issue</value> + </data> </root>
\ No newline at end of file diff --git a/src/Text/Impl/EditorOperations/TextTransactionMergePolicy.cs b/src/Text/Impl/EditorOperations/TextTransactionMergePolicy.cs index d913c0d..9c8086a 100644 --- a/src/Text/Impl/EditorOperations/TextTransactionMergePolicy.cs +++ b/src/Text/Impl/EditorOperations/TextTransactionMergePolicy.cs @@ -48,12 +48,12 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation // Validate if (newTransaction == null) { - throw new ArgumentNullException("newTransaction"); + throw new ArgumentNullException(nameof(newTransaction)); } if (oldTransaction == null) { - throw new ArgumentNullException("oldTransaction"); + throw new ArgumentNullException(nameof(oldTransaction)); } TextTransactionMergePolicy oldPolicy = oldTransaction.MergePolicy as TextTransactionMergePolicy; @@ -71,7 +71,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation } // Only merge text transactions that have the same description - if (newTransaction.Description != oldTransaction.Description) + if (!string.Equals(newTransaction.Description, oldTransaction.Description, StringComparison.Ordinal)) { return false; } @@ -92,9 +92,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation public void PerformTransactionMerge(ITextUndoTransaction existingTransaction, ITextUndoTransaction newTransaction) { if (existingTransaction == null) - throw new ArgumentNullException("existingTransaction"); + throw new ArgumentNullException(nameof(existingTransaction)); if (newTransaction == null) - throw new ArgumentNullException("newTransaction"); + throw new ArgumentNullException(nameof(newTransaction)); // Remove trailing AfterTextBufferChangeUndoPrimitive from previous transaction and skip copying // initial BeforeTextBufferChangeUndoPrimitive from newTransaction, as they are unnecessary. @@ -128,7 +128,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { if (other == null) { - throw new ArgumentNullException("other"); + throw new ArgumentNullException(nameof(other)); } // Only merge transaction if they are both a text transaction diff --git a/src/Text/Impl/EditorOptions/EditorOptions.cs b/src/Text/Impl/EditorOptions/EditorOptions.cs index 59a6e55..cb97410 100644 --- a/src/Text/Impl/EditorOptions/EditorOptions.cs +++ b/src/Text/Impl/EditorOptions/EditorOptions.cs @@ -10,6 +10,7 @@ namespace Microsoft.VisualStudio.Text.EditorOptions.Implementation using System; using System.Collections.Generic; using System.Collections.Specialized; + using System.Globalization; using System.Linq; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Utilities; @@ -60,7 +61,7 @@ namespace Microsoft.VisualStudio.Text.EditorOptions.Implementation throw new InvalidOperationException("Cannot change the Parent of the global options."); if (value == null) - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); if (value == this) throw new ArgumentException("The Parent of this instance of IEditorOptions cannot be set to itself."); @@ -124,7 +125,7 @@ namespace Microsoft.VisualStudio.Text.EditorOptions.Implementation object value; if (!TryGetOption(definition, out value)) - throw new ArgumentException(string.Format("The specified option is not valid in this scope: {0}", definition.Name)); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "The specified option is not valid in this scope: {0}", definition.Name)); return value; } @@ -136,12 +137,12 @@ namespace Microsoft.VisualStudio.Text.EditorOptions.Implementation // Make sure the type of the provided value is correct if (!definition.ValueType.IsAssignableFrom(value.GetType())) { - throw new ArgumentException("Specified option value is of an invalid type", "value"); + throw new ArgumentException("Specified option value is of an invalid type", nameof(value)); } // Make sure the option is valid, also else if(!definition.IsValid(ref value)) { - throw new ArgumentException("The supplied value failed validation for the option.", "value"); + throw new ArgumentException("The supplied value failed validation for the option.", nameof(value)); } // Finally, set the option value locally else diff --git a/src/Text/Impl/EditorOptions/EditorOptionsFactoryService.cs b/src/Text/Impl/EditorOptions/EditorOptionsFactoryService.cs index 733450b..a7f2686 100644 --- a/src/Text/Impl/EditorOptions/EditorOptionsFactoryService.cs +++ b/src/Text/Impl/EditorOptions/EditorOptionsFactoryService.cs @@ -35,7 +35,7 @@ namespace Microsoft.VisualStudio.Text.EditorOptions.Implementation public IEditorOptions GetOptions(IPropertyOwner scope) { if (scope == null) - throw new ArgumentNullException("scope"); + throw new ArgumentNullException(nameof(scope)); return scope.Properties.GetOrCreateSingletonProperty<IEditorOptions>(() => new EditorOptions(this.GlobalOptions as EditorOptions, scope, this)); } @@ -133,7 +133,7 @@ namespace Microsoft.VisualStudio.Text.EditorOptions.Implementation { var definition = this.GetOptionDefinition(optionId); if (definition == null) - throw new ArgumentException(string.Format("No EditorOptionDefinition export found for the given option name: {0}", optionId), "optionId"); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "No EditorOptionDefinition export found for the given option name: {0}", optionId), nameof(optionId)); return definition; } diff --git a/src/Text/Impl/EditorPrimitives/DefaultBufferPrimitive.cs b/src/Text/Impl/EditorPrimitives/DefaultBufferPrimitive.cs index 19923d5..9e383d9 100644 --- a/src/Text/Impl/EditorPrimitives/DefaultBufferPrimitive.cs +++ b/src/Text/Impl/EditorPrimitives/DefaultBufferPrimitive.cs @@ -30,7 +30,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if ((position < 0) || (position > _textBuffer.CurrentSnapshot.Length)) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } return _bufferPrimitivesFactory.CreateTextPoint(this, position); } @@ -39,14 +39,14 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if ((line < 0) || (line > _textBuffer.CurrentSnapshot.LineCount)) { - throw new ArgumentOutOfRangeException("line"); + throw new ArgumentOutOfRangeException(nameof(line)); } ITextSnapshotLine snapshotLine = _textBuffer.CurrentSnapshot.GetLineFromLineNumber(line); if ((column < 0) || (column > snapshotLine.Length)) { - throw new ArgumentOutOfRangeException("column"); + throw new ArgumentOutOfRangeException(nameof(column)); } return _bufferPrimitivesFactory.CreateTextPoint(this, snapshotLine.Start + column); } @@ -55,7 +55,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if ((line < 0) || (line > _textBuffer.CurrentSnapshot.LineCount)) { - throw new ArgumentOutOfRangeException("line"); + throw new ArgumentOutOfRangeException(nameof(line)); } ITextSnapshotLine snapshotLine = _textBuffer.CurrentSnapshot.GetLineFromLineNumber(line); @@ -67,11 +67,11 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if (startPoint == null) { - throw new ArgumentNullException("startPoint"); + throw new ArgumentNullException(nameof(startPoint)); } if (endPoint == null) { - throw new ArgumentNullException("endPoint"); + throw new ArgumentNullException(nameof(endPoint)); } if (!object.ReferenceEquals(startPoint.TextBuffer, this)) @@ -91,12 +91,12 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if ((startPosition < 0) || (startPosition > _textBuffer.CurrentSnapshot.Length)) { - throw new ArgumentOutOfRangeException("startPosition"); + throw new ArgumentOutOfRangeException(nameof(startPosition)); } if ((endPosition < 0) || (endPosition > _textBuffer.CurrentSnapshot.Length)) { - throw new ArgumentOutOfRangeException("endPosition"); + throw new ArgumentOutOfRangeException(nameof(endPosition)); } TextPoint startPoint = GetTextPoint(startPosition); diff --git a/src/Text/Impl/EditorPrimitives/DefaultDisplayTextPointPrimitive.cs b/src/Text/Impl/EditorPrimitives/DefaultDisplayTextPointPrimitive.cs index 0b64bc4..8ca865a 100644 --- a/src/Text/Impl/EditorPrimitives/DefaultDisplayTextPointPrimitive.cs +++ b/src/Text/Impl/EditorPrimitives/DefaultDisplayTextPointPrimitive.cs @@ -82,7 +82,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if (!object.ReferenceEquals(this.TextBuffer, otherPoint.TextBuffer)) { - throw new ArgumentException("The other point must have the same TextBuffer as this one", "otherPoint"); + throw new ArgumentException("The other point must have the same TextBuffer as this one", nameof(otherPoint)); } return TextView.GetTextRange(this, otherPoint); } @@ -184,7 +184,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if (text == null) { - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); } return _bufferPoint.InsertText(text); diff --git a/src/Text/Impl/EditorPrimitives/DefaultSelectionPrimitive.cs b/src/Text/Impl/EditorPrimitives/DefaultSelectionPrimitive.cs index 35231be..d9ce555 100644 --- a/src/Text/Impl/EditorPrimitives/DefaultSelectionPrimitive.cs +++ b/src/Text/Impl/EditorPrimitives/DefaultSelectionPrimitive.cs @@ -18,7 +18,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; - internal sealed class DefaultSelectionPrimitive : Selection + internal sealed class DefaultSelectionPrimitive : Text.Editor.LegacySelection { private TextView _textView; private IEditorOptions _editorOptions; diff --git a/src/Text/Impl/EditorPrimitives/DefaultTextPointPrimitive.cs b/src/Text/Impl/EditorPrimitives/DefaultTextPointPrimitive.cs index be6d268..69088f7 100644 --- a/src/Text/Impl/EditorPrimitives/DefaultTextPointPrimitive.cs +++ b/src/Text/Impl/EditorPrimitives/DefaultTextPointPrimitive.cs @@ -37,7 +37,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation if ((position < 0) || (position > textBuffer.AdvancedTextBuffer.CurrentSnapshot.Length)) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } _textBuffer = textBuffer; @@ -78,7 +78,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation for (int i = 0; i < lineTextInfo.LengthInTextElements; i++) { string textElement = lineTextInfo.SubstringByTextElements(i, 1); - if (textElement == "\t") + if (string.Equals(textElement, "\t", StringComparison.Ordinal)) { // If there is a tab in the text, then the column automatically jumps // to the next tab stop. @@ -291,7 +291,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if (otherPoint == null) { - throw new ArgumentNullException("otherPoint"); + throw new ArgumentNullException(nameof(otherPoint)); } if (otherPoint.TextBuffer != TextBuffer) @@ -306,7 +306,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if ((otherPosition < 0) || (otherPosition > TextBuffer.AdvancedTextBuffer.CurrentSnapshot.Length)) { - throw new ArgumentOutOfRangeException("otherPosition"); + throw new ArgumentOutOfRangeException(nameof(otherPosition)); } TextPoint otherPoint = this.Clone(); @@ -354,7 +354,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if (text == null) { - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); } if (text.Length > 0) @@ -410,7 +410,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { newPoint.MoveTo(i); string character = newPoint.GetNextCharacter(); - if (character != " " && character != "\t") + if (!string.Equals(character, " ", StringComparison.Ordinal) && !string.Equals(character, "\t", StringComparison.Ordinal)) { break; } @@ -510,7 +510,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if ((lineNumber < 0) || (lineNumber > _textBuffer.AdvancedTextBuffer.CurrentSnapshot.LineCount)) { - throw new ArgumentOutOfRangeException("lineNumber"); + throw new ArgumentOutOfRangeException(nameof(lineNumber)); } ITextSnapshot currentSnapshot = _textBuffer.AdvancedTextBuffer.CurrentSnapshot; @@ -737,11 +737,11 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if (pattern == null) { - throw new ArgumentNullException("pattern"); + throw new ArgumentNullException(nameof(pattern)); } if (endPoint == null) { - throw new ArgumentNullException("endPoint"); + throw new ArgumentNullException(nameof(endPoint)); } if (endPoint.TextBuffer != TextBuffer) { @@ -769,7 +769,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation if ((position < 0) || (position > snapshot.Length)) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } // If this is the end of the snapshot, we don't need to check anything. @@ -872,7 +872,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation if ((lineNumber < 0) || (lineNumber > _textBuffer.AdvancedTextBuffer.CurrentSnapshot.LineCount)) { - throw new ArgumentOutOfRangeException("lineNumber"); + throw new ArgumentOutOfRangeException(nameof(lineNumber)); } ITextSnapshotLine line = _textBuffer.AdvancedTextBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber); diff --git a/src/Text/Impl/EditorPrimitives/DefaultTextRangePrimitive.cs b/src/Text/Impl/EditorPrimitives/DefaultTextRangePrimitive.cs index 961db4f..e0abb1b 100644 --- a/src/Text/Impl/EditorPrimitives/DefaultTextRangePrimitive.cs +++ b/src/Text/Impl/EditorPrimitives/DefaultTextRangePrimitive.cs @@ -167,7 +167,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation newChar = char.ToUpper(newChar, CultureInfo.CurrentCulture); } - if (!textEdit.Replace(i, 1, newChar.ToString())) + if (!textEdit.Replace(i, 1, newChar.ToString(CultureInfo.CurrentCulture))) { textEdit.Cancel(); return false; // break out early if any edit fails to reduce the time of the failure case @@ -296,7 +296,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation { if (string.IsNullOrEmpty(newText)) { - throw new ArgumentNullException("newText"); + throw new ArgumentNullException(nameof(newText)); } int startPoint = _startPoint.CurrentPosition; diff --git a/src/Text/Impl/EditorPrimitives/DefaultTextViewPrimitive.cs b/src/Text/Impl/EditorPrimitives/DefaultTextViewPrimitive.cs index 80715de..4dca517 100644 --- a/src/Text/Impl/EditorPrimitives/DefaultTextViewPrimitive.cs +++ b/src/Text/Impl/EditorPrimitives/DefaultTextViewPrimitive.cs @@ -10,12 +10,13 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Formatting; + using LegacySelection = Microsoft.VisualStudio.Text.Editor.LegacySelection; internal sealed class DefaultTextViewPrimitive : TextView { private ITextView _textView; private Caret _caret; - private Selection _selection; + private LegacySelection _selection; private TextBuffer _textBuffer; private IViewPrimitivesFactoryService _viewPrimitivesFactory; @@ -144,7 +145,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation get { return _caret; } } - public override Selection Selection + public override LegacySelection Selection { get { return _selection; } } diff --git a/src/Text/Impl/EditorPrimitives/DefaultViewPrimitivesFactoryService.cs b/src/Text/Impl/EditorPrimitives/DefaultViewPrimitivesFactoryService.cs index 09e9d25..99400b7 100644 --- a/src/Text/Impl/EditorPrimitives/DefaultViewPrimitivesFactoryService.cs +++ b/src/Text/Impl/EditorPrimitives/DefaultViewPrimitivesFactoryService.cs @@ -44,7 +44,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation return new DefaultDisplayTextRangePrimitive(textView, textRange); } - public Selection CreateSelection(TextView textView) + public LegacySelection CreateSelection(TextView textView) { if (textView.Selection == null) { diff --git a/src/Text/Impl/EditorPrimitives/ViewPrimitives.cs b/src/Text/Impl/EditorPrimitives/ViewPrimitives.cs index 9b2b1dc..081fdb4 100644 --- a/src/Text/Impl/EditorPrimitives/ViewPrimitives.cs +++ b/src/Text/Impl/EditorPrimitives/ViewPrimitives.cs @@ -12,7 +12,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation internal sealed class ViewPrimitives : IViewPrimitives { private TextView _textView; - private Selection _selection; + private LegacySelection _selection; private Caret _caret; private TextBuffer _textBuffer; @@ -32,7 +32,7 @@ namespace Microsoft.VisualStudio.Text.EditorPrimitives.Implementation get { return _textView; } } - public Selection Selection + public LegacySelection Selection { get { return _selection; } } diff --git a/src/Text/Impl/Navigation/TextStructureNavigatorSelectorService.cs b/src/Text/Impl/Navigation/TextStructureNavigatorSelectorService.cs index e59bc8d..ae08b4f 100644 --- a/src/Text/Impl/Navigation/TextStructureNavigatorSelectorService.cs +++ b/src/Text/Impl/Navigation/TextStructureNavigatorSelectorService.cs @@ -32,7 +32,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } ITextStructureNavigator navigator = null; @@ -55,11 +55,11 @@ namespace Microsoft.VisualStudio.Text.Operations.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } if (contentType == null) { - throw new ArgumentNullException("contentType"); + throw new ArgumentNullException(nameof(contentType)); } return CreateNavigator(textBuffer, contentType); } diff --git a/src/Text/Impl/Outlining/OutliningManager.cs b/src/Text/Impl/Outlining/OutliningManager.cs index 71418ec..28decbe 100644 --- a/src/Text/Impl/Outlining/OutliningManager.cs +++ b/src/Text/Impl/Outlining/OutliningManager.cs @@ -244,7 +244,7 @@ namespace Microsoft.VisualStudio.Text.Outlining if (internalCollapsed == null) { throw new ArgumentException("The given collapsed region was not created by this outlining manager.", - "collapsed"); + nameof(collapsed)); } if (!internalCollapsed.IsValid) @@ -272,7 +272,7 @@ namespace Microsoft.VisualStudio.Text.Outlining internal IEnumerable<ICollapsed> InternalCollapseAll(SnapshotSpan span, Predicate<ICollapsible> match, CancellationToken? cancel) { if (match == null) - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); EnsureValid(span); @@ -312,7 +312,7 @@ namespace Microsoft.VisualStudio.Text.Outlining public IEnumerable<ICollapsible> ExpandAllInternal(bool removalPending, SnapshotSpan span, Predicate<ICollapsed> match) { if (match == null) - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); EnsureValid(span); @@ -676,19 +676,19 @@ namespace Microsoft.VisualStudio.Text.Outlining if (spans == null) { - throw new ArgumentNullException("spans"); + throw new ArgumentNullException(nameof(spans)); } if (spans.Count == 0) { - throw new ArgumentException("The given span collection is empty.", "spans"); + throw new ArgumentException("The given span collection is empty.", nameof(spans)); } if (spans[0].Snapshot.TextBuffer != this.editBuffer) { throw new ArgumentException("The given span collection is on an invalid buffer." + "Spans must be generated against the view model's edit buffer", - "spans"); + nameof(spans)); } } @@ -705,7 +705,7 @@ namespace Microsoft.VisualStudio.Text.Outlining { throw new ArgumentException("The given span is on an invalid buffer." + "Spans must be generated against the view model's edit buffer", - "span"); + nameof(span)); } } @@ -725,9 +725,9 @@ namespace Microsoft.VisualStudio.Text.Outlining public int Compare(ICollapsible x, ICollapsible y) { if (x == null) - throw new ArgumentNullException("x"); + throw new ArgumentNullException(nameof(x)); if (y == null) - throw new ArgumentNullException("y"); + throw new ArgumentNullException(nameof(y)); ITextSnapshot current = SourceBuffer.CurrentSnapshot; SnapshotSpan left = x.Extent.GetSpan(current); diff --git a/src/Text/Impl/Outlining/OutliningManagerService.cs b/src/Text/Impl/Outlining/OutliningManagerService.cs index 5e8a795..081672e 100644 --- a/src/Text/Impl/Outlining/OutliningManagerService.cs +++ b/src/Text/Impl/Outlining/OutliningManagerService.cs @@ -28,7 +28,7 @@ namespace Microsoft.VisualStudio.Text.Outlining public IOutliningManager GetOutliningManager(ITextView textView) { if (textView == null) - throw new ArgumentNullException("textView"); + throw new ArgumentNullException(nameof(textView)); if (!textView.Roles.Contains(PredefinedTextViewRoles.Structured)) return null; diff --git a/src/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs b/src/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs index adfacc8..6106223 100644 --- a/src/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs +++ b/src/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft. 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.Collections.Immutable; -using Microsoft.VisualStudio.Text; +using System.Globalization; using Microsoft.VisualStudio.Text.Utilities; using TextSpan = Microsoft.VisualStudio.Text.Span; @@ -66,8 +65,8 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation return GetKind(result.Value); } - private PatternMatchKind GetKind(CamelCaseResult result) - => PatternMatcher.GetCamelCaseKind(result, _candidateHumps); + private static PatternMatchKind GetKind(CamelCaseResult result) + => PatternMatcher.GetCamelCaseKind(result); private CamelCaseResult? TryMatch( int patternIndex, int candidateHumpIndex, bool? contiguous, int chunkOffset) @@ -99,7 +98,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation } var candidateHump = _candidateHumps[humpIndex]; - if (char.ToLower(_candidate[candidateHump.Start]) == patternCharacter) + if (char.ToLower(_candidate[candidateHump.Start], CultureInfo.CurrentCulture) == patternCharacter) { // Found a hump in the candidate string that matches the current pattern // character we're on. i.e. we matched the c in cofipro against the C in @@ -204,7 +203,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation /// If 'weight' is better than 'bestWeight' and matchSpanToAdd is not null, then /// matchSpanToAdd will be added to matchedSpansInReverse. /// </summary> - private bool UpdateBestResultIfBetter( + private static bool UpdateBestResultIfBetter( CamelCaseResult result, ref CamelCaseResult? bestResult, TextSpan? matchSpanToAdd) { if (matchSpanToAdd != null) @@ -234,7 +233,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation return GetKind(result) == PatternMatchKind.CamelCaseExact; } - private bool IsBetter(CamelCaseResult result, CamelCaseResult? currentBestResult) + private static bool IsBetter(CamelCaseResult result, CamelCaseResult? currentBestResult) { if (currentBestResult == null) { @@ -245,12 +244,12 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation return GetKind(result) < GetKind(currentBestResult.Value); } - private bool LowercaseSubstringsMatch( + private static bool LowercaseSubstringsMatch( string s1, int start1, string s2, int start2, int length) { for (var i = 0; i < length; i++) { - if (char.ToLower(s1[start1 + i]) != char.ToLower(s2[start2 + i])) + if (char.ToLower(s1[start1 + i], CultureInfo.CurrentCulture) != char.ToLower(s2[start2 + i], CultureInfo.CurrentCulture)) { return false; } diff --git a/src/Text/Impl/PatternMatching/ArraySlice.cs b/src/Text/Impl/PatternMatching/ArraySlice.cs index 6e7455e..1da2b03 100644 --- a/src/Text/Impl/PatternMatching/ArraySlice.cs +++ b/src/Text/Impl/PatternMatching/ArraySlice.cs @@ -41,12 +41,12 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation { if (start < 0) { - throw new ArgumentException(nameof(start), $"{start} < {0}"); + throw new ArgumentException($"{start} < {0}", nameof(start)); } if (start > _array.Length) { - throw new ArgumentException(nameof(start), $"{start} > {_array.Length}"); + throw new ArgumentException($"{start} > {_array.Length}", nameof(start)); } CheckLength(start, length); @@ -59,12 +59,12 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation { if (length < 0) { - throw new ArgumentException(nameof(length), $"{length} < {0}"); + throw new ArgumentException($"{length} < {0}", nameof(length)); } if (start + length > _array.Length) { - throw new ArgumentException(nameof(start), $"{start} + {length} > {_array.Length}"); + throw new ArgumentException($"{start} + {length} > {_array.Length}", nameof(start)); } } diff --git a/src/Text/Impl/PatternMatching/CamelCaseResult.cs b/src/Text/Impl/PatternMatching/CamelCaseResult.cs index f67572b..f11c61b 100644 --- a/src/Text/Impl/PatternMatching/CamelCaseResult.cs +++ b/src/Text/Impl/PatternMatching/CamelCaseResult.cs @@ -47,7 +47,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation } } - private static PatternMatchKind GetCamelCaseKind(CamelCaseResult result, StringBreaks candidateHumps) + private static PatternMatchKind GetCamelCaseKind(CamelCaseResult result) { /* CamelCase PatternMatchKind truth table: * | FromStart | ToEnd | Contiguous || PatternMatchKind | diff --git a/src/Text/Impl/PatternMatching/ContainerPatternMatcher.cs b/src/Text/Impl/PatternMatching/ContainerPatternMatcher.cs index 6048e0f..55aa996 100644 --- a/src/Text/Impl/PatternMatching/ContainerPatternMatcher.cs +++ b/src/Text/Impl/PatternMatching/ContainerPatternMatcher.cs @@ -43,6 +43,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation _invalidPattern = _patternSegments.Length == 0 || _patternSegments.Any(s => s.IsInvalid); } +#pragma warning disable CA1063 public override void Dispose() { base.Dispose(); @@ -52,6 +53,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation segment.Dispose(); } } +#pragma warning restore CA1063 public override PatternMatch? TryMatch(string candidate) { diff --git a/src/Text/Impl/PatternMatching/EditDistance.cs b/src/Text/Impl/PatternMatching/EditDistance.cs index 5eb25d2..d350be5 100644 --- a/src/Text/Impl/PatternMatching/EditDistance.cs +++ b/src/Text/Impl/PatternMatching/EditDistance.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Text; using System.Threading; namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation { +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional ///<summary> /// NOTE: Only use if you truly need an edit distance. /// @@ -24,7 +26,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation /// Specifically, this implementation satisfies the following inequality: D(x, y) + D(y, z) >= D(x, z) /// (where D is the edit distance). ///</summary> - internal class EditDistance : IDisposable + internal sealed class EditDistance : IDisposable { // Our edit distance algorithm makes use of an 'infinite' value. A value so high that it // could never participate in an edit distance (and effectively means the path through it @@ -556,7 +558,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation for (var i = 0; i < width; i++) { var v = matrix[i + 2, j + 2]; - sb.Append((v == Infinity ? "∞" : v.ToString()) + " "); + sb.Append((v == Infinity ? "∞" : v.ToString(CultureInfo.CurrentCulture)) + " "); } sb.AppendLine(); } @@ -666,4 +668,5 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation } } } +#pragma warning restore CA1814 // Prefer jagged arrays over multidimensional } diff --git a/src/Text/Impl/PatternMatching/PatternMatcher.cs b/src/Text/Impl/PatternMatching/PatternMatcher.cs index f781db3..793a632 100644 --- a/src/Text/Impl/PatternMatching/PatternMatcher.cs +++ b/src/Text/Impl/PatternMatching/PatternMatcher.cs @@ -58,7 +58,9 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation _allowSimpleSubstringMatching = allowSimpleSubstringMatching; } +#pragma warning disable CA1063 public virtual void Dispose() +#pragma warning restore CA1063 { foreach (var kvp in _stringToWordSpans) { @@ -138,7 +140,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation : NonFuzzyMatchPatternChunk(candidate, patternChunk, punctuationStripped, chunkOffset); } - private PatternMatch? FuzzyMatchPatternChunk( + private static PatternMatch? FuzzyMatchPatternChunk( string candidate, TextChunk patternChunk, bool punctuationStripped) @@ -167,7 +169,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation // a) Check if the part matches the candidate entirely, in an case insensitive or // sensitive manner. If it does, return that there was an exact match. return new PatternMatch( - PatternMatchKind.Exact, punctuationStripped, isCaseSensitive: candidate == patternChunk.Text, + PatternMatchKind.Exact, punctuationStripped, isCaseSensitive: string.Equals(candidate, patternChunk.Text, StringComparison.Ordinal), matchedSpans: GetMatchedSpans(chunkOffset, candidate.Length)); } else @@ -541,7 +543,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation matchedSpansInReverse: null, chunkOffset: chunkOffset ); - return GetCamelCaseKind(camelCaseResult, candidateHumps); + return GetCamelCaseKind(camelCaseResult); } else if (currentCandidateHump == candidateHumpCount) { diff --git a/src/Text/Impl/PatternMatching/WordSimilarityChecker.cs b/src/Text/Impl/PatternMatching/WordSimilarityChecker.cs index d049e6b..d2ad8b6 100644 --- a/src/Text/Impl/PatternMatching/WordSimilarityChecker.cs +++ b/src/Text/Impl/PatternMatching/WordSimilarityChecker.cs @@ -123,7 +123,7 @@ namespace Microsoft.VisualStudio.Text.PatternMatching.Implementation return false; } - if (_lastAreSimilarResult.CandidateText == candidateText) + if (string.Equals(_lastAreSimilarResult.CandidateText, candidateText, StringComparison.Ordinal)) { similarityWeight = _lastAreSimilarResult.SimilarityWeight; return _lastAreSimilarResult.AreSimilar; diff --git a/src/Text/Impl/StandaloneUndo/AutoEnclose.cs b/src/Text/Impl/StandaloneUndo/AutoEnclose.cs index 42af7d4..ad92cd3 100644 --- a/src/Text/Impl/StandaloneUndo/AutoEnclose.cs +++ b/src/Text/Impl/StandaloneUndo/AutoEnclose.cs @@ -20,7 +20,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone this.end = end; } +#pragma warning disable CA1063 // Implement IDisposable Correctly public void Dispose() +#pragma warning restore CA1063 // Implement IDisposable Correctly { if (end != null) end(); GC.SuppressFinalize(this); diff --git a/src/Text/Impl/StandaloneUndo/CatchOperationsFromHistoryForDelegatedPrimitive.cs b/src/Text/Impl/StandaloneUndo/CatchOperationsFromHistoryForDelegatedPrimitive.cs index b39446d..ace7773 100644 --- a/src/Text/Impl/StandaloneUndo/CatchOperationsFromHistoryForDelegatedPrimitive.cs +++ b/src/Text/Impl/StandaloneUndo/CatchOperationsFromHistoryForDelegatedPrimitive.cs @@ -28,7 +28,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone history.ForwardToUndoOperation(primitive); } +#pragma warning disable CA1063 // Implement IDisposable Correctly public void Dispose() +#pragma warning restore CA1063 // Implement IDisposable Correctly { history.EndForwardToUndoOperation(primitive); primitive.State = DelegatedUndoPrimitiveState.Inactive; diff --git a/src/Text/Impl/StandaloneUndo/DelegatedUndoPrimitiveImpl.cs b/src/Text/Impl/StandaloneUndo/DelegatedUndoPrimitiveImpl.cs index 87c83f9..3e678f9 100644 --- a/src/Text/Impl/StandaloneUndo/DelegatedUndoPrimitiveImpl.cs +++ b/src/Text/Impl/StandaloneUndo/DelegatedUndoPrimitiveImpl.cs @@ -110,10 +110,12 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone } } +#pragma warning disable CA1822 // Mark members as static public bool MergeWithPreviousOnly { get { return true; } } +#pragma warning restore CA1822 // Mark members as static public bool CanMerge(ITextUndoPrimitive primitive) { @@ -125,4 +127,4 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone throw new InvalidOperationException("Strings.DelegatedUndoPrimitiveCannotMerge"); } } -}
\ No newline at end of file +} diff --git a/src/Text/Impl/StandaloneUndo/UndoHistoryImpl.cs b/src/Text/Impl/StandaloneUndo/UndoHistoryImpl.cs index 9f5bac5..47fec01 100644 --- a/src/Text/Impl/StandaloneUndo/UndoHistoryImpl.cs +++ b/src/Text/Impl/StandaloneUndo/UndoHistoryImpl.cs @@ -8,13 +8,11 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Collections.ObjectModel; -using System.ComponentModel.Composition; using Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.Text.Operations.Standalone { - internal class UndoHistoryImpl : ITextUndoHistory + internal class UndoHistoryImpl : ITextUndoHistory2 { public event EventHandler<TextUndoRedoEventArgs> UndoRedoHappened; public event EventHandler<TextUndoTransactionCompletedEventArgs> UndoTransactionCompleted; @@ -25,7 +23,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone private Stack<ITextUndoTransaction> undoStack; private Stack<ITextUndoTransaction> redoStack; private DelegatedUndoPrimitiveImpl activeUndoOperationPrimitive; - private TextUndoHistoryState state; + internal TextUndoHistoryState state; private PropertyCollection properties; #endregion @@ -188,6 +186,13 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone get { return this.state; } } + public ITextUndoTransaction CreateInvisibleTransaction(string description) + { + // Standalone undo doesn't support invisible transactions so simply return + // a normal transaction. + return this.CreateTransaction(description); + } + /// <summary> /// Creates a new transaction, nests it in the previously current transaction, and marks it current. /// If there is a redo stack, it gets cleared. @@ -198,9 +203,9 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone /// <returns></returns> public ITextUndoTransaction CreateTransaction(string description) { - if (String.IsNullOrEmpty(description)) + if (string.IsNullOrEmpty(description)) { - throw new ArgumentNullException("description", String.Format(CultureInfo.CurrentUICulture, "Strings.ArgumentCannotBeNull", "CreateTransaction", "description")); + throw new ArgumentNullException(nameof(description)); } // If there is a pending transaction that has already been completed, we should not be permitted @@ -244,12 +249,12 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (count <= 0) { - throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, "Strings.RedoAndUndoAcceptOnlyPositiveCounts", "Undo", count), "count"); + throw new ArgumentOutOfRangeException(nameof(count)); } if (!IsThereEnoughVisibleTransactions(this.undoStack, count)) { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Strings.CannotUndoMoreTransactionsThanExist", "undo", count)); + throw new InvalidOperationException("Cannot undo more transactions than exist"); } TextUndoHistoryState originalState = this.state; @@ -321,12 +326,12 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (count <= 0) { - throw new ArgumentException(String.Format(CultureInfo.CurrentUICulture, "Strings.RedoAndUndoAcceptOnlyPositiveCounts", "Redo", count), "count"); + throw new ArgumentOutOfRangeException(nameof(count)); } if (!IsThereEnoughVisibleTransactions(this.redoStack, count)) { - throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Strings.CannotUndoMoreTransactionsThanExist", "redo", count)); + throw new InvalidOperationException("Cannot redo more transactions than exist"); } TextUndoHistoryState originalState = this.state; @@ -424,15 +429,19 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone throw new InvalidOperationException("Strings.EndTransactionOutOfOrder"); } + // Note that the VS undo history actually "pops" the nested undo stack on the Complete/Cancel + // (instead of in the Dispose). This shouldn't affect anything but we should consider adapting + // this code to follow the model in VS undo. + this.currentTransaction = (UndoTransactionImpl)(transaction.Parent); + // only add completed transactions to their parents (or the stack) - if (this.currentTransaction.State == UndoTransactionState.Completed) + if (transaction.State == UndoTransactionState.Completed) { - if (this.currentTransaction.Parent == null) // stack bottomed out! + if (transaction.Parent == null) // stack bottomed out! { - MergeOrPushToUndoStack(this.currentTransaction); + MergeOrPushToUndoStack((UndoTransactionImpl)transaction); } } - this.currentTransaction = this.currentTransaction.Parent as UndoTransactionImpl; } /// <summary> @@ -471,7 +480,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone transactionAdded = transaction; transactionResult = TextUndoTransactionCompletionResult.TransactionAdded; } - RaiseUndoTransactionCompleted(transactionAdded, transactionResult); + RaiseUndoTransactionCompleted(transactionAdded, transactionResult); } public bool ValidTransactionForMarkers(ITextUndoTransaction transaction) diff --git a/src/Text/Impl/StandaloneUndo/UndoHistoryRegistryImpl.cs b/src/Text/Impl/StandaloneUndo/UndoHistoryRegistryImpl.cs index 2e4742d..5aef378 100644 --- a/src/Text/Impl/StandaloneUndo/UndoHistoryRegistryImpl.cs +++ b/src/Text/Impl/StandaloneUndo/UndoHistoryRegistryImpl.cs @@ -18,7 +18,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone internal class UndoHistoryRegistryImpl : ITextUndoHistoryRegistry { #region Private Fields - private Dictionary<ITextUndoHistory, int> histories; + internal Dictionary<ITextUndoHistory, int> histories; private Dictionary<WeakReferenceForDictionaryKey, ITextUndoHistory> weakContextMapping; private Dictionary<object, ITextUndoHistory> strongContextMapping; #endregion // Private Fields @@ -50,7 +50,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (context == null) { - throw new ArgumentNullException("context", String.Format(CultureInfo.CurrentCulture, "Strings.ArgumentCannotBeNull", "RegisterHistory", "context")); + throw new ArgumentNullException(nameof(context)); } return RegisterHistory(context, false); @@ -66,7 +66,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (context == null) { - throw new ArgumentNullException("context", String.Format(CultureInfo.CurrentCulture, "Strings.ArgumentCannotBeNull", "RegisterHistory", "context")); + throw new ArgumentNullException(nameof(context)); } ITextUndoHistory result; @@ -118,7 +118,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (context == null) { - throw new ArgumentNullException("context", String.Format(CultureInfo.CurrentCulture, "Strings.ArgumentCannotBeNull", "GetHistory", "context")); + throw new ArgumentNullException(nameof(context)); } ITextUndoHistory result; @@ -149,7 +149,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (context == null) { - throw new ArgumentNullException("context", String.Format(CultureInfo.CurrentCulture, "Strings.ArgumentCannotBeNull", "TryGetHistory", "context")); + throw new ArgumentNullException(nameof(context)); } ITextUndoHistory result = null; @@ -176,12 +176,12 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (context == null) { - throw new ArgumentNullException("context", String.Format(CultureInfo.CurrentCulture, "Strings.ArgumentCannotBeNull", "AttachHistory", "context")); + throw new ArgumentNullException(nameof(context)); } if (history == null) { - throw new ArgumentNullException("context", String.Format(CultureInfo.CurrentCulture, "Strings.ArgumentCannotBeNull", "AttachHistory", "history")); + throw new ArgumentNullException(nameof(history)); } AttachHistory(context, history, false); @@ -197,12 +197,12 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (context == null) { - throw new ArgumentNullException("context", String.Format(CultureInfo.CurrentCulture, "Strings.ArgumentCannotBeNull", "AttachHistory", "context")); + throw new ArgumentNullException(nameof(context)); } if (history == null) { - throw new ArgumentNullException("context", String.Format(CultureInfo.CurrentCulture, "Strings.ArgumentCannotBeNull", "AttachHistory", "history")); + throw new ArgumentNullException(nameof(history)); } if (strongContextMapping.ContainsKey(context) || weakContextMapping.ContainsKey(new WeakReferenceForDictionaryKey(context))) @@ -237,7 +237,7 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (history == null) { - throw new ArgumentNullException("context", String.Format(CultureInfo.CurrentCulture, "Strings.ArgumentCannotBeNull", "RemoveHistory", "history")); + throw new ArgumentNullException(nameof(history)); } if (!histories.ContainsKey(history)) diff --git a/src/Text/Impl/StandaloneUndo/UndoTransactionImpl.cs b/src/Text/Impl/StandaloneUndo/UndoTransactionImpl.cs index aae1d06..7bc7db8 100644 --- a/src/Text/Impl/StandaloneUndo/UndoTransactionImpl.cs +++ b/src/Text/Impl/StandaloneUndo/UndoTransactionImpl.cs @@ -17,13 +17,14 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { #region Private Fields - private readonly UndoHistoryImpl history; + private UndoHistoryImpl history; private readonly UndoTransactionImpl parent; private string description; private UndoTransactionState state; private List<ITextUndoPrimitive> primitives; private IMergeTextUndoTransactionPolicy mergePolicy; + internal bool _isDisposed = false; #endregion @@ -31,12 +32,12 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (history == null) { - throw new ArgumentNullException("history", String.Format(CultureInfo.CurrentUICulture, "Strings.ArgumentCannotBeNull", "UndoTransactionImpl", "history")); + throw new ArgumentNullException(nameof(history)); } - if (String.IsNullOrEmpty(description)) + if (string.IsNullOrEmpty(description)) { - throw new ArgumentNullException("description", String.Format(CultureInfo.CurrentUICulture, "Strings.ArgumentCannotBeNull", "UndoTransactionImpl", "description")); + throw new ArgumentNullException(nameof(description)); } this.history = history as UndoHistoryImpl; @@ -363,35 +364,44 @@ namespace Microsoft.VisualStudio.Text.Operations.Standalone { if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } this.mergePolicy = value; } } - /// <summary> - /// Closes a transaction and disposes it. - /// </summary> +#pragma warning disable CA1063 // Implement IDisposable Correctly + /// <summary> + /// Closes a transaction and disposes it. + /// </summary> public void Dispose() +#pragma warning restore CA1063 // Implement IDisposable Correctly { - GC.SuppressFinalize(this); - switch (this.State) + if (!_isDisposed) { - case UndoTransactionState.Open: - Cancel(); - break; + _isDisposed = true; - case UndoTransactionState.Canceled: - case UndoTransactionState.Completed: - break; + GC.SuppressFinalize(this); + switch (this.State) + { + case UndoTransactionState.Open: + Cancel(); + break; + + case UndoTransactionState.Canceled: + case UndoTransactionState.Completed: + break; + + case UndoTransactionState.Redoing: + case UndoTransactionState.Undoing: + case UndoTransactionState.Undone: + throw new InvalidOperationException("Strings.ClosingAnOpenTransactionThatAppearsToBeUndoneOrUndoing"); + } - case UndoTransactionState.Redoing: - case UndoTransactionState.Undoing: - case UndoTransactionState.Undone: - throw new InvalidOperationException("Strings.ClosingAnOpenTransactionThatAppearsToBeUndoneOrUndoing"); + this.history.EndTransaction(this); } - history.EndTransaction(this); } + } } diff --git a/src/Text/Impl/TagAggregator/TagAggregator.cs b/src/Text/Impl/TagAggregator/TagAggregator.cs index 2f84741..90005ff 100644 --- a/src/Text/Impl/TagAggregator/TagAggregator.cs +++ b/src/Text/Impl/TagAggregator/TagAggregator.cs @@ -128,7 +128,7 @@ namespace Microsoft.VisualStudio.Text.Tagging.Implementation public IEnumerable<IMappingTagSpan<T>> GetTags(IMappingSpan span) { if (span == null) - throw new ArgumentNullException("span"); + throw new ArgumentNullException(nameof(span)); if (this.disposed) throw new ObjectDisposedException("TagAggregator"); @@ -186,7 +186,7 @@ namespace Microsoft.VisualStudio.Text.Tagging.Implementation public IEnumerable<IMappingTagSpan<T>> GetAllTags(IMappingSpan span, CancellationToken cancel) { if (span == null) - throw new ArgumentNullException("span"); + throw new ArgumentNullException(nameof(span)); if (this.disposed) throw new ObjectDisposedException("TagAggregator"); diff --git a/src/Text/Impl/TagAggregator/TagAggregatorFactoryService.cs b/src/Text/Impl/TagAggregator/TagAggregatorFactoryService.cs index 1b743a5..d4a62b3 100644 --- a/src/Text/Impl/TagAggregator/TagAggregatorFactoryService.cs +++ b/src/Text/Impl/TagAggregator/TagAggregatorFactoryService.cs @@ -57,7 +57,7 @@ namespace Microsoft.VisualStudio.Text.Tagging.Implementation public ITagAggregator<T> CreateTagAggregator<T>(ITextBuffer textBuffer, TagAggregatorOptions options) where T : ITag { if (textBuffer == null) - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); return new TagAggregator<T>(this, null, this.BufferGraphFactoryService.CreateBufferGraph(textBuffer), options); @@ -75,7 +75,7 @@ namespace Microsoft.VisualStudio.Text.Tagging.Implementation public ITagAggregator<T> CreateTagAggregator<T>(ITextView textView, TagAggregatorOptions options) where T : ITag { if (textView == null) - throw new ArgumentNullException("textView"); + throw new ArgumentNullException(nameof(textView)); return new TagAggregator<T>(this, textView, textView.BufferGraph, options); } diff --git a/src/Text/Impl/TextBufferUndoManager/Strings.Designer.cs b/src/Text/Impl/TextBufferUndoManager/Strings.Designer.cs index 5d442b7..1f71b36 100644 --- a/src/Text/Impl/TextBufferUndoManager/Strings.Designer.cs +++ b/src/Text/Impl/TextBufferUndoManager/Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 +// Runtime Version:2.0.50727.1426 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -19,7 +19,7 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Strings { @@ -39,8 +39,7 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation { internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.Text.Implementation.Text.Impl.TextBufferUndoManager.String" + - "s", typeof(Strings).Assembly); + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.VisualStudio.Text.Implementation.Text.Impl.TextBufferUndoManager.Strings", typeof(Strings).Assembly); resourceMan = temp; } return resourceMan; diff --git a/src/Text/Impl/TextBufferUndoManager/TextBufferChangeUndoPrimitive.cs b/src/Text/Impl/TextBufferUndoManager/TextBufferChangeUndoPrimitive.cs index 277c0a0..28b5233 100644 --- a/src/Text/Impl/TextBufferUndoManager/TextBufferChangeUndoPrimitive.cs +++ b/src/Text/Impl/TextBufferUndoManager/TextBufferChangeUndoPrimitive.cs @@ -8,16 +8,14 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation { using System; - using System.Text; + using System.Diagnostics; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Operations; - using System.Collections.Generic; - using System.Diagnostics; - + /// <summary> /// The UndoPrimitive for a text buffer change operation. /// </summary> - public class TextBufferChangeUndoPrimitive : TextUndoPrimitive + public class TextBufferChangeUndoPrimitive : TextUndoPrimitive, IEditOnlyTextUndoPrimitive { #region Private Data Members @@ -26,9 +24,9 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation private readonly ITextUndoHistory _undoHistory; private WeakReference _weakBufferReference; - private readonly INormalizedTextChangeCollection _textChanges; - private int? _beforeVersion; - private int? _afterVersion; + public INormalizedTextChangeCollection Changes { get; } + public int? BeforeReiteratedVersionNumber { get; private set; } + public int? AfterReiteratedVersionNumber { get; private set; } #if DEBUG private int _bufferLengthAfterChange; #endif @@ -52,17 +50,17 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation // Verify input parameters if (undoHistory == null) { - throw new ArgumentNullException("undoHistory"); + throw new ArgumentNullException(nameof(undoHistory)); } if (textVersion == null) { - throw new ArgumentNullException("textVersion"); + throw new ArgumentNullException(nameof(textVersion)); } - _textChanges = textVersion.Changes; - _beforeVersion = textVersion.ReiteratedVersionNumber; - _afterVersion = textVersion.Next.VersionNumber; + this.Changes = textVersion.Changes; + this.BeforeReiteratedVersionNumber = textVersion.ReiteratedVersionNumber; + this.AfterReiteratedVersionNumber = textVersion.Next.VersionNumber; Debug.Assert(textVersion.Next.VersionNumber == textVersion.Next.ReiteratedVersionNumber, "Creating a TextBufferChangeUndoPrimitive for a change that has previously been undone? This is probably wrong."); @@ -124,14 +122,14 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation { AttachedToNewBuffer = false; - _beforeVersion = TextBuffer.CurrentSnapshot.Version.VersionNumber; - _afterVersion = null; + this.BeforeReiteratedVersionNumber = TextBuffer.CurrentSnapshot.Version.VersionNumber; + this.AfterReiteratedVersionNumber = null; } bool editCanceled = false; - using (ITextEdit edit = TextBuffer.CreateEdit(EditOptions.None, _afterVersion, typeof(TextBufferChangeUndoPrimitive))) + using (ITextEdit edit = TextBuffer.CreateEdit(EditOptions.None, this.AfterReiteratedVersionNumber, UndoTag.Tag)) { - foreach (ITextChange textChange in _textChanges) + foreach (ITextChange textChange in this.Changes) { if (!edit.Replace(new Span(textChange.OldPosition, textChange.OldLength), textChange.NewText)) { @@ -157,9 +155,9 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation throw new OperationCanceledException("Redo failed due to readonly regions or canceled edit."); } - if (_afterVersion == null) + if (this.AfterReiteratedVersionNumber == null) { - _afterVersion = TextBuffer.CurrentSnapshot.Version.VersionNumber; + this.AfterReiteratedVersionNumber = TextBuffer.CurrentSnapshot.Version.VersionNumber; } #if DEBUG @@ -195,14 +193,14 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation { AttachedToNewBuffer = false; - _beforeVersion = null; - _afterVersion = TextBuffer.CurrentSnapshot.Version.VersionNumber; + this.BeforeReiteratedVersionNumber = null; + this.AfterReiteratedVersionNumber = TextBuffer.CurrentSnapshot.Version.VersionNumber; } bool editCanceled = false; - using (ITextEdit edit = TextBuffer.CreateEdit(EditOptions.None, _beforeVersion, typeof(TextBufferChangeUndoPrimitive))) + using (ITextEdit edit = TextBuffer.CreateEdit(EditOptions.None, this.BeforeReiteratedVersionNumber, UndoTag.Tag)) { - foreach (ITextChange textChange in _textChanges) + foreach (ITextChange textChange in this.Changes) { if (!edit.Replace(new Span(textChange.NewPosition, textChange.NewLength), textChange.OldText)) { @@ -228,9 +226,9 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation throw new OperationCanceledException("Undo failed due to readonly regions or canceled edit."); } - if (_beforeVersion == null) + if (this.BeforeReiteratedVersionNumber == null) { - _beforeVersion = TextBuffer.CurrentSnapshot.Version.VersionNumber; + this.BeforeReiteratedVersionNumber = TextBuffer.CurrentSnapshot.Version.VersionNumber; } _canUndo = false; @@ -283,5 +281,10 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation } } #endregion + + internal class UndoTag : IUndoEditTag + { + public static readonly UndoTag Tag = new UndoTag(); + } } } diff --git a/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs b/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs index 77f526c..646a47e 100644 --- a/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs +++ b/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs @@ -8,159 +8,165 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation { using System; - using System.Collections.Generic; - using System.Text; + using System.Diagnostics; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Operations; - using System.Diagnostics; - class TextBufferUndoManager : ITextBufferUndoManager, IDisposable + internal sealed class TextBufferUndoManager : ITextBufferUndoManager, IDisposable { #region Private Members - ITextBuffer _textBuffer; - ITextUndoHistoryRegistry _undoHistoryRegistry; - ITextUndoHistory _undoHistory; - Queue<ITextVersion> _editVersionList = new Queue<ITextVersion>(); - bool _inPostChanged; + private ITextBuffer _textBuffer; + private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; + private ITextUndoHistory _undoHistory; + + // The plan had been to add the IUndoMetadataEditTag to allow people to create simple edits + // that would restore carets. That is being pushed back to 16.0 (maybe) but I didn't want to + // abandon the work in progress. +#if false + private readonly IEditorOperationsFactoryService _editorOperationsFactoryService; - #endregion + IEditorOperations _initiatingOperations = null; +#endif + ITextUndoTransaction _createdTransaction = null; +#endregion public TextBufferUndoManager(ITextBuffer textBuffer, ITextUndoHistoryRegistry undoHistoryRegistry) { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } if (undoHistoryRegistry == null) { - throw new ArgumentNullException("undoHistoryRegistry"); + throw new ArgumentNullException(nameof(undoHistoryRegistry)); } _textBuffer = textBuffer; - _undoHistoryRegistry = undoHistoryRegistry; +#if false + if (editorOperationsFactoryService == null) + { + throw new ArgumentNullException(nameof(editorOperationsFactoryService)); + } + + _editorOperationsFactoryService = editorOperationsFactoryService; +#endif + // Register the undo history - _undoHistory = _undoHistoryRegistry.RegisterHistory(_textBuffer); + this.EnsureTextBufferUndoHistory(); // Listen for the buffer changed events so that we can make them undo/redo-able + _textBuffer.Changing += TextBufferChanging; _textBuffer.Changed += TextBufferChanged; _textBuffer.PostChanged += TextBufferPostChanged; - _textBuffer.Changing += TextBufferChanging; } - #region Private Methods +#region Private Methods private void TextBufferChanged(object sender, TextContentChangedEventArgs e) { - Debug.Assert((e.EditTag as Type) != typeof(TextBufferChangeUndoPrimitive) || - (_undoHistory.State != TextUndoHistoryState.Idle), - "We are undoing/redoing a change while UndoHistory.State is Idle. Something is wrong with the state."); - - // If this change didn't originate from undo, add a TextBufferChangeUndoPrimitive to our history. - if (_undoHistory.State == TextUndoHistoryState.Idle && - (e.EditTag as Type) != typeof(TextBufferChangeUndoPrimitive)) + if (!(e.EditTag is IUndoEditTag)) { - // With projection, we sometimes get Changed events with no changes, or for "" -> "". - // We don't want to create undo actions for these. - bool nonNullChange = false; - foreach (ITextChange c in e.BeforeVersion.Changes) + if (this.TextBufferUndoHistory.State != TextUndoHistoryState.Idle) { - if (c.OldLength != 0 || c.NewLength != 0) + Debug.Fail("We are doing a normal edit in a non-idle undo state. This is explicitly prohibited as it would corrupt the undo stack! Please fix your code."); + } + else + { + // With projection, we sometimes get Changed events with no changes, or for "" -> "". + // We don't want to create undo actions for these. + bool nonNullChange = false; + foreach (ITextChange c in e.BeforeVersion.Changes) { - nonNullChange = true; - break; + if (c.OldLength != 0 || c.NewLength != 0) + { + nonNullChange = true; + break; + } } - } - if (nonNullChange) - { - // Queue the edit, and actually add an undo primitive later (see comment on PostChanged). - _editVersionList.Enqueue(e.BeforeVersion); + if (nonNullChange) + { + // If there's an open undo transaction, add our edit (turned into a primitive) to it. Otherwise, create and undo transaction. + var currentTransaction = _undoHistory.CurrentTransaction; + if (currentTransaction == null) + { + // TODO remove this + // Hack to allow Cascade's local undo to light up if using v15.7 but behave using the old -- non-local -- undo before if running on 15.6. + // Cascade should really be marking its edits with IInvisibleEditTag (and will once it can take a hard requirement of VS 15.7). + if ((e.EditTag is IInvisibleEditTag) || ((e.EditTag != null) && (string.Equals(e.EditTag.ToString(), "CascadeRemoteEdit", StringComparison.Ordinal)))) + { + _createdTransaction = ((ITextUndoHistory2)_undoHistory).CreateInvisibleTransaction("<invisible>"); + } +#if false + else if (e.EditTag is IUndoMetadataEditTag metadata) + { + _createdTransaction = _undoHistory.CreateTransaction(metadata.Description); + if (_initiatingOperations == null) + { + var view = metadata.InitiatingView; + if (view != null) + { + _initiatingOperations = _editorOperationsFactoryService.GetEditorOperations(view); + _initiatingOperations.AddBeforeTextBufferChangePrimitive(); + } + } + } +#endif + else + { + _createdTransaction = _undoHistory.CreateTransaction(Strings.TextBufferChanged); + } + + currentTransaction = _createdTransaction; + } + + currentTransaction.AddUndo(new TextBufferChangeUndoPrimitive(_undoHistory, e.BeforeVersion)); + } } } } - /// <remarks> - /// Edits are queued up by our TextBufferChanged handler and then we finally add them to the - /// undo stack here in response to PostChanged. The reason and history behind why we do this - /// is as follows: - /// - /// Originally this was done for VB commit, which uses undo events (i.e. TransactionCompleted) to - /// trigger commit. Their commit logic relies on the buffer being in a state such that applying - /// an edit synchronously raises a Changed event (which is always the case for PostChanged, but - /// not for Changed if there are nested edits). - /// - /// JaredPar made a change (CS 1182244) that allowed VB to detect that UndoTransactionCompleted - /// was being fired from a nested edit, and therefore delay the actual commit until the following - /// PostChanged event. - /// - /// So this allowed us to move TextBufferUndoManager back to adding undo actions directly - /// from the TextBufferChanged handler (CS 1285117). This is preferable, as otherwise there's a - /// "delay" between when the edit happens and when we record the edit on the undo stack, - /// allowing other people to stick something on the undo stack (i.e. from - /// their ITextBuffer.Changed handler) in between. The result is actions being "out-of-order" - /// on the undo stack. - /// - /// Unfortunately, it turns out VB snippets actually rely on this "out-of-order" behavior - /// (see Dev10 834740) and so we are forced to revert CS 1285117) and return to the model - /// where we queue up edits and delay adding them to the undo stack until PostChanged. - /// - /// It would be good to revisit this at again, but we would need to work with VB - /// to fix their snippets / undo behavior, and verify that VB commit is also unaffected. - /// </remarks> - private void TextBufferPostChanged(object sender, EventArgs e) + void TextBufferChanging(object sender, TextContentChangingEventArgs e) { - // Only process a top level PostChanged event. Nested events will continue to process TextChange events - // which are added to the queue and will be processed below - if ( _inPostChanged ) - { - return; - } - - _inPostChanged = true; - try + // Note that VB explicitly forces undo edits to happen while the history is idle so we need to allow this here + // by always doing nothing for undo edits). This may be a bug in our code (e.g. not properly cleaning up when + // an undo transaction is cancelled in mid-flight) but changing that will require coordination with Roslyn. + if (!(e.EditTag is IUndoEditTag)) { - // Do not do a foreach loop here. It's perfectly possible, and in fact expected, that the Complete - // method below can trigger a series of events which leads to a nested edit and another - // ITextBuffer::Changed. That event will add to the _editVersionList queue and hence break a - // foreach loop - while ( _editVersionList.Count > 0 ) + if (this.TextBufferUndoHistory.State != TextUndoHistoryState.Idle) { - var cur = _editVersionList.Dequeue(); - using (ITextUndoTransaction undoTransaction = _undoHistory.CreateTransaction(Strings.TextBufferChanged)) - { - TextBufferChangeUndoPrimitive undoPrimitive = new TextBufferChangeUndoPrimitive(_undoHistory, cur); - undoTransaction.AddUndo(undoPrimitive); - - undoTransaction.Complete(); - } + Debug.Fail("We are doing a normal edit in a non-idle undo state. This is explicitly prohibited as it would corrupt the undo stack! Please fix your code."); + e.Cancel(); } } - finally - { - _editVersionList.Clear(); // Ensure we cleanup state in the face of an exception - _inPostChanged = false; - } } - void TextBufferChanging(object sender, TextContentChangingEventArgs e) + private void TextBufferPostChanged(object sender, EventArgs e) { - // See if somebody (other than us) is trying to edit the buffer during undo/redo. - if (_undoHistory.State != TextUndoHistoryState.Idle && - (e.EditTag as Type) != typeof(TextBufferChangeUndoPrimitive)) + if (_createdTransaction != null) { - Debug.Fail("Attempt to edit the buffer during undo/redo has been denied. This is explicitly prohibited as it would corrupt the undo stack! Please fix your code."); - e.Cancel(); +#if false + if (_initiatingOperations != null) + { + _initiatingOperations.AddAfterTextBufferChangePrimitive(); + } + + _initiatingOperations = null; +#endif + + _createdTransaction.Complete(); + _createdTransaction.Dispose(); + _createdTransaction = null; } } +#endregion - #endregion - - #region ITextBufferUndoManager Members +#region ITextBufferUndoManager Members public ITextBuffer TextBuffer { @@ -175,31 +181,50 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation // we are robust, always register the undo history. get { - _undoHistory = _undoHistoryRegistry.RegisterHistory(_textBuffer); - return _undoHistory; + this.EnsureTextBufferUndoHistory(); + return _undoHistory; } } public void UnregisterUndoHistory() { // Unregister the undo history - _undoHistoryRegistry.RemoveHistory(_undoHistory); + if (_undoHistory != null) + { + _undoHistoryRegistry.RemoveHistory(_undoHistory); + _undoHistory = null; + } } - #endregion +#endregion + + private void EnsureTextBufferUndoHistory() + { + if (_textBuffer == null) + throw new ObjectDisposedException("TextBufferUndoManager"); + + // Note, right now, there is no way for us to know if an ITextUndoHistory + // has been unregistered (ie it can be unregistered by a third party) + // An issue has been logged with the Undo team, but in the mean time, to ensure that + // we are robust, always register the undo history. + _undoHistory = _undoHistoryRegistry.RegisterHistory(_textBuffer); + } - #region IDisposable Members +#region IDisposable Members public void Dispose() { - UnregisterUndoHistory(); - _textBuffer.Changed -= TextBufferChanged; - _textBuffer.PostChanged -= TextBufferPostChanged; - _textBuffer.Changing -= TextBufferChanging; + if (_textBuffer != null) + { + _textBuffer.PostChanged -= TextBufferPostChanged; + _textBuffer.Changed -= TextBufferChanged; + _textBuffer.Changing -= TextBufferChanging; + _textBuffer = null; + } GC.SuppressFinalize(this); } - #endregion +#endregion } } diff --git a/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManagerProvider.cs b/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManagerProvider.cs index 72eb736..c5a4823 100644 --- a/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManagerProvider.cs +++ b/src/Text/Impl/TextBufferUndoManager/TextBufferUndoManagerProvider.cs @@ -8,18 +8,19 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation { using System; - using System.Collections.Generic; + using System.ComponentModel.Composition; using Microsoft.VisualStudio.Text; - using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.Text.Operations; - using System.ComponentModel.Composition; [Export(typeof(ITextBufferUndoManagerProvider))] internal sealed class TextBufferUndoManagerProvider : ITextBufferUndoManagerProvider { [Import] internal ITextUndoHistoryRegistry _undoHistoryRegistry { get; set; } - +#if false + [Import] + internal IEditorOperationsFactoryService _editorOperationsFactoryService { get; set; } +#endif /// <summary> /// Provides an <see cref="ITextBufferUndoManager"/> for the given <paramref name="textBuffer"/>. /// </summary> @@ -31,13 +32,16 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation // Validate if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } // See if there was already a TextBufferUndoManager created for the given textBuffer, we only ever want to create one ITextBufferUndoManager cachedBufferUndoManager; if (!textBuffer.Properties.TryGetProperty<ITextBufferUndoManager>(typeof(ITextBufferUndoManager), out cachedBufferUndoManager)) { +#if false + cachedBufferUndoManager = new TextBufferUndoManager(textBuffer, _undoHistoryRegistry, _editorOperationsFactoryService); +#endif cachedBufferUndoManager = new TextBufferUndoManager(textBuffer, _undoHistoryRegistry); textBuffer.Properties.AddProperty(typeof(ITextBufferUndoManager), cachedBufferUndoManager); } @@ -54,7 +58,7 @@ namespace Microsoft.VisualStudio.Text.BufferUndoManager.Implementation // Validate if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } ITextBufferUndoManager cachedBufferUndoManager; diff --git a/src/Text/Impl/TextModel/BaseBuffer.cs b/src/Text/Impl/TextModel/BaseBuffer.cs index 4d4ed82..a318a35 100644 --- a/src/Text/Impl/TextModel/BaseBuffer.cs +++ b/src/Text/Impl/TextModel/BaseBuffer.cs @@ -106,7 +106,9 @@ namespace Microsoft.VisualStudio.Text.Implementation } } +#pragma warning disable CA1063 // Implement IDisposable Correctly public void Dispose() +#pragma warning restore CA1063 // Implement IDisposable Correctly { if (!this.applied && !this.canceled) { @@ -207,11 +209,11 @@ namespace Microsoft.VisualStudio.Text.Implementation CheckActive(); if (position < 0 || position > this.bufferLength) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } if (text == null) { - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); } // Check for ReadOnly @@ -233,19 +235,19 @@ namespace Microsoft.VisualStudio.Text.Implementation CheckActive(); if (position < 0 || position > this.bufferLength) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } if (characterBuffer == null) { - throw new ArgumentNullException("characterBuffer"); + throw new ArgumentNullException(nameof(characterBuffer)); } if (startIndex < 0 || startIndex > characterBuffer.Length) { - throw new ArgumentOutOfRangeException("startIndex"); + throw new ArgumentOutOfRangeException(nameof(startIndex)); } if (length < 0 || startIndex + length > characterBuffer.Length) { - throw new ArgumentOutOfRangeException("length"); + throw new ArgumentOutOfRangeException(nameof(length)); } // Check for ReadOnly @@ -267,15 +269,15 @@ namespace Microsoft.VisualStudio.Text.Implementation CheckActive(); if (startPosition < 0 || startPosition > this.bufferLength) { - throw new ArgumentOutOfRangeException("startPosition"); + throw new ArgumentOutOfRangeException(nameof(startPosition)); } if (charsToReplace < 0 || startPosition + charsToReplace > this.bufferLength) { - throw new ArgumentOutOfRangeException("charsToReplace"); + throw new ArgumentOutOfRangeException(nameof(charsToReplace)); } if (replaceWith == null) { - throw new ArgumentNullException("replaceWith"); + throw new ArgumentNullException(nameof(replaceWith)); } // Check for ReadOnly @@ -297,11 +299,11 @@ namespace Microsoft.VisualStudio.Text.Implementation CheckActive(); if (replaceSpan.End > this.bufferLength) { - throw new ArgumentOutOfRangeException("replaceSpan"); + throw new ArgumentOutOfRangeException(nameof(replaceSpan)); } if (replaceWith == null) { - throw new ArgumentNullException("replaceWith"); + throw new ArgumentNullException(nameof(replaceWith)); } // Check for ReadOnly @@ -323,11 +325,11 @@ namespace Microsoft.VisualStudio.Text.Implementation CheckActive(); if (startPosition < 0 || startPosition > this.bufferLength) { - throw new ArgumentOutOfRangeException("startPosition"); + throw new ArgumentOutOfRangeException(nameof(startPosition)); } if (charsToDelete < 0 || startPosition + charsToDelete > this.bufferLength) { - throw new ArgumentOutOfRangeException("charsToDelete"); + throw new ArgumentOutOfRangeException(nameof(charsToDelete)); } // Check for ReadOnly @@ -349,7 +351,7 @@ namespace Microsoft.VisualStudio.Text.Implementation CheckActive(); if (deleteSpan.End > this.bufferLength) { - throw new ArgumentOutOfRangeException("deleteSpan"); + throw new ArgumentOutOfRangeException(nameof(deleteSpan)); } // Check for ReadOnly @@ -716,7 +718,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (newContentType == null) { - throw new ArgumentNullException("newContentType"); + throw new ArgumentNullException(nameof(newContentType)); } if (newContentType != this.contentType) @@ -764,7 +766,7 @@ namespace Microsoft.VisualStudio.Text.Implementation ReadOnlyQueryThreadCheck(); if ((position < 0) || (position > this.currentSnapshot.Length)) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } return IsReadOnlyImplementation(position, isEdit); @@ -780,7 +782,7 @@ namespace Microsoft.VisualStudio.Text.Implementation ReadOnlyQueryThreadCheck(); if (span.End > this.currentSnapshot.Length) { - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); } return IsReadOnlyImplementation(span, isEdit); @@ -809,7 +811,7 @@ namespace Microsoft.VisualStudio.Text.Implementation ReadOnlyQueryThreadCheck(); if (span.End > this.CurrentSnapshot.Length) { - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); } return GetReadOnlyExtentsImplementation(span); } diff --git a/src/Text/Impl/TextModel/BaseSnapshot.cs b/src/Text/Impl/TextModel/BaseSnapshot.cs index db723d3..b099424 100644 --- a/src/Text/Impl/TextModel/BaseSnapshot.cs +++ b/src/Text/Impl/TextModel/BaseSnapshot.cs @@ -9,6 +9,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { using System; using System.Collections.Generic; + using System.Globalization; using System.IO; using System.Text; using Microsoft.VisualStudio.Utilities; @@ -185,9 +186,11 @@ namespace Microsoft.VisualStudio.Text.Implementation public override string ToString() { - return String.Format("version: {0} lines: {1} length: {2} \r\n content: {3}", + return string.Format( + CultureInfo.InvariantCulture, + "version: {0} lines: {1} length: {2} \r\n content: {3}", Version.VersionNumber, LineCount, Length, - Microsoft.VisualStudio.Text.Utilities.TextUtilities.Escape(this.GetText(0, Math.Min(40, this.Length)))); + Utilities.TextUtilities.Escape(this.GetText(0, Math.Min(40, this.Length)))); } #if _DEBUG diff --git a/src/Text/Impl/TextModel/BufferFactoryService.cs b/src/Text/Impl/TextModel/BufferFactoryService.cs index 975cbf0..5ae8b2f 100644 --- a/src/Text/Impl/TextModel/BufferFactoryService.cs +++ b/src/Text/Impl/TextModel/BufferFactoryService.cs @@ -149,7 +149,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (contentType == null) { - throw new ArgumentNullException("contentType"); + throw new ArgumentNullException(nameof(contentType)); } return Make(contentType, StringRebuilder.Empty, false); } @@ -163,7 +163,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (contentType == null) { - throw new ArgumentNullException("contentType"); + throw new ArgumentNullException(nameof(contentType)); } StringRebuilder content = StringRebuilderFromSnapshotSpan(span); @@ -191,11 +191,11 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (text == null) { - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); } if (contentType == null) { - throw new ArgumentNullException("contentType"); + throw new ArgumentNullException(nameof(contentType)); } return Make(contentType, StringRebuilder.Create(text), spurnGroup); } @@ -204,11 +204,11 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (reader == null) { - throw new ArgumentNullException("reader"); + throw new ArgumentNullException(nameof(reader)); } if (contentType == null) { - throw new ArgumentNullException("contentType"); + throw new ArgumentNullException(nameof(contentType)); } if (length > int.MaxValue) { @@ -217,7 +217,7 @@ namespace Microsoft.VisualStudio.Text.Implementation bool hasConsistentLineEndings; int longestLineLength; - StringRebuilder content = TextImageLoader.Load(reader, length, traceId, out hasConsistentLineEndings, out longestLineLength); + StringRebuilder content = TextImageLoader.Load(reader, length, out hasConsistentLineEndings, out longestLineLength); ITextBuffer buffer = Make(contentType, content, false); if (!hasConsistentLineEndings) @@ -286,7 +286,7 @@ namespace Microsoft.VisualStudio.Text.Implementation bool hasConsistentLineEndings; int longestLineLength; - return CachingTextImage.Create(TextImageLoader.Load(reader, length, string.Empty, out hasConsistentLineEndings, out longestLineLength), null); + return CachingTextImage.Create(TextImageLoader.Load(reader, length, out hasConsistentLineEndings, out longestLineLength), null); } public ITextImage CreateTextImage(MemoryMappedFile source) @@ -318,11 +318,11 @@ namespace Microsoft.VisualStudio.Text.Implementation // projectionEditResolver is allowed to be null. if (trackingSpans == null) { - throw new ArgumentNullException("trackingSpans"); + throw new ArgumentNullException(nameof(trackingSpans)); } if (contentType == null) { - throw new ArgumentNullException("contentType"); + throw new ArgumentNullException(nameof(contentType)); } IProjectionBuffer buffer = new ProjectionBuffer(this, projectionEditResolver, contentType, trackingSpans, _differenceService, _textDifferencingSelectorService.DefaultTextDifferencingService, options, _guardedOperations); @@ -337,7 +337,7 @@ namespace Microsoft.VisualStudio.Text.Implementation // projectionEditResolver is allowed to be null. if (trackingSpans == null) { - throw new ArgumentNullException("trackingSpans"); + throw new ArgumentNullException(nameof(trackingSpans)); } IProjectionBuffer buffer = @@ -354,15 +354,15 @@ namespace Microsoft.VisualStudio.Text.Implementation // projectionEditResolver is allowed to be null. if (exposedSpans == null) { - throw new ArgumentNullException("exposedSpans"); + throw new ArgumentNullException(nameof(exposedSpans)); } if (exposedSpans.Count == 0) { - throw new ArgumentOutOfRangeException("exposedSpans"); // really? + throw new ArgumentOutOfRangeException(nameof(exposedSpans)); // really? } if (contentType == null) { - throw new ArgumentNullException("contentType"); + throw new ArgumentNullException(nameof(contentType)); } if (exposedSpans[0].Snapshot != exposedSpans[0].Snapshot.TextBuffer.CurrentSnapshot) diff --git a/src/Text/Impl/TextModel/EncodedStreamReader.cs b/src/Text/Impl/TextModel/EncodedStreamReader.cs index b11a34b..4e30901 100644 --- a/src/Text/Impl/TextModel/EncodedStreamReader.cs +++ b/src/Text/Impl/TextModel/EncodedStreamReader.cs @@ -32,7 +32,7 @@ namespace Microsoft.VisualStudio.Text.Implementation GuardedOperations guardedOperations) { if (stream == null) - throw new ArgumentNullException("stream"); + throw new ArgumentNullException(nameof(stream)); long position = stream.Position; diff --git a/src/Text/Impl/TextModel/FileNameKey.cs b/src/Text/Impl/TextModel/FileNameKey.cs new file mode 100644 index 0000000..3bbd354 --- /dev/null +++ b/src/Text/Impl/TextModel/FileNameKey.cs @@ -0,0 +1,52 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// This file contain implementations details that are subject to change without notice. +// Use at your own risk. +// +namespace Microsoft.VisualStudio.Text.Implementation +{ + using System; + using System.IO; + sealed class FileNameKey + { + private readonly string _fileName; + private readonly int _hashCode; + + public FileNameKey(string fileName) + { + //Gracefully catch errors getting the full path (which can happen if the file name is on a protected share). + try + { + _fileName = Path.GetFullPath(fileName); + } + catch + { + //This shouldn't happen (we are generally passed names associated with documents that we are expecting to open so + //we should have access). If we fail, we will, at worst not get the same underlying document when people create + //persistent spans using unnormalized names. + _fileName = fileName; + } + + _hashCode = StringComparer.OrdinalIgnoreCase.GetHashCode(_fileName); + } + + //Override equality and hash code + public override int GetHashCode() + { + return _hashCode; + } + + public override bool Equals(object obj) + { + var other = obj as FileNameKey; + return (other != null) && string.Equals(_fileName, other._fileName, StringComparison.OrdinalIgnoreCase); + } + + public override string ToString() + { + return _fileName; + } + } +} diff --git a/src/Text/Impl/TextModel/FileUtilities.cs b/src/Text/Impl/TextModel/FileUtilities.cs index 2c567c3..b6fbd82 100644 --- a/src/Text/Impl/TextModel/FileUtilities.cs +++ b/src/Text/Impl/TextModel/FileUtilities.cs @@ -185,7 +185,7 @@ namespace Microsoft.VisualStudio.Text.Implementation if (!(safeHandle.IsClosed || safeHandle.IsInvalid)) { BY_HANDLE_FILE_INFORMATION fi; - if (GetFileInformationByHandle(safeHandle, out fi)) + if (NativeMethods.GetFileInformationByHandle(safeHandle, out fi)) { if (fi.NumberOfLinks <= 1) { @@ -232,26 +232,5 @@ namespace Microsoft.VisualStudio.Text.Implementation temporaryPath = null; return new FileStream(filePath, fileMode, FileAccess.Write, FileShare.Read); } - - [StructLayout(LayoutKind.Sequential)] - struct BY_HANDLE_FILE_INFORMATION - { - public uint FileAttributes; - public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime; - public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime; - public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime; - public uint VolumeSerialNumber; - public uint FileSizeHigh; - public uint FileSizeLow; - public uint NumberOfLinks; - public uint FileIndexHigh; - public uint FileIndexLow; - } - - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool GetFileInformationByHandle( - Microsoft.Win32.SafeHandles.SafeFileHandle hFile, - out BY_HANDLE_FILE_INFORMATION lpFileInformation - ); } -}
\ No newline at end of file +} diff --git a/src/Text/Impl/TextModel/ForwardFidelityCustomTrackingSpan.cs b/src/Text/Impl/TextModel/ForwardFidelityCustomTrackingSpan.cs index 4b8f0c2..938be92 100644 --- a/src/Text/Impl/TextModel/ForwardFidelityCustomTrackingSpan.cs +++ b/src/Text/Impl/TextModel/ForwardFidelityCustomTrackingSpan.cs @@ -19,7 +19,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (behavior == null) { - throw new ArgumentNullException("behavior"); + throw new ArgumentNullException(nameof(behavior)); } this.behavior = behavior; this.customState = customState; diff --git a/src/Text/Impl/TextModel/HighFidelityTrackingPoint.cs b/src/Text/Impl/TextModel/HighFidelityTrackingPoint.cs index db784fb..5301a95 100644 --- a/src/Text/Impl/TextModel/HighFidelityTrackingPoint.cs +++ b/src/Text/Impl/TextModel/HighFidelityTrackingPoint.cs @@ -44,7 +44,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (fidelity != TrackingFidelityMode.UndoRedo && fidelity != TrackingFidelityMode.Backward) { - throw new ArgumentOutOfRangeException("fidelity"); + throw new ArgumentOutOfRangeException(nameof(fidelity)); } List<VersionNumberPosition> initialHistory = null; if (fidelity == TrackingFidelityMode.UndoRedo && version.VersionNumber > 0) diff --git a/src/Text/Impl/TextModel/HighFidelityTrackingSpan.cs b/src/Text/Impl/TextModel/HighFidelityTrackingSpan.cs index 6eb6ca4..e889804 100644 --- a/src/Text/Impl/TextModel/HighFidelityTrackingSpan.cs +++ b/src/Text/Impl/TextModel/HighFidelityTrackingSpan.cs @@ -50,7 +50,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (fidelity != TrackingFidelityMode.UndoRedo && fidelity != TrackingFidelityMode.Backward) { - throw new ArgumentOutOfRangeException("fidelity"); + throw new ArgumentOutOfRangeException(nameof(fidelity)); } List<VersionNumberPosition> startHistory = null; List<VersionNumberPosition> endHistory = null; diff --git a/src/Text/Impl/TextModel/MappingPoint.cs b/src/Text/Impl/TextModel/MappingPoint.cs index 46e34ec..2497607 100644 --- a/src/Text/Impl/TextModel/MappingPoint.cs +++ b/src/Text/Impl/TextModel/MappingPoint.cs @@ -20,15 +20,15 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (anchorPoint.Snapshot == null) { - throw new ArgumentNullException("anchorPoint"); + throw new ArgumentNullException(nameof(anchorPoint)); } if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } if (bufferGraph == null) { - throw new ArgumentNullException("bufferGraph"); + throw new ArgumentNullException(nameof(bufferGraph)); } this.anchorPoint = anchorPoint; this.trackingMode = trackingMode; @@ -49,7 +49,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (targetBuffer == null) { - throw new ArgumentNullException("targetBuffer"); + throw new ArgumentNullException(nameof(targetBuffer)); } ITextBuffer anchorBuffer = this.AnchorBuffer; SnapshotPoint currentPoint = this.anchorPoint.TranslateTo(anchorBuffer.CurrentSnapshot, this.trackingMode); @@ -86,7 +86,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public SnapshotPoint? GetPoint(ITextSnapshot targetSnapshot, PositionAffinity affinity) { if (targetSnapshot == null) - throw new ArgumentNullException("targetSnapshot"); + throw new ArgumentNullException(nameof(targetSnapshot)); SnapshotPoint? result = GetPoint(targetSnapshot.TextBuffer, affinity); if (result.HasValue && (result.Value.Snapshot != targetSnapshot)) @@ -101,7 +101,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } ITextBuffer anchorBuffer = this.AnchorBuffer; SnapshotPoint currentPoint = this.anchorPoint.TranslateTo(anchorBuffer.CurrentSnapshot, this.trackingMode); @@ -143,7 +143,7 @@ namespace Microsoft.VisualStudio.Text.Implementation // always maps down if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } ITextBuffer anchorBuffer = this.AnchorBuffer; SnapshotPoint currentPoint = this.anchorPoint.TranslateTo(anchorBuffer.CurrentSnapshot, this.trackingMode); diff --git a/src/Text/Impl/TextModel/MappingSpan.cs b/src/Text/Impl/TextModel/MappingSpan.cs index a42f0fe..741972c 100644 --- a/src/Text/Impl/TextModel/MappingSpan.cs +++ b/src/Text/Impl/TextModel/MappingSpan.cs @@ -9,6 +9,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { using System; using System.Collections.Generic; + using System.Globalization; using Microsoft.VisualStudio.Text.Projection; using Microsoft.VisualStudio.Text.Utilities; @@ -22,15 +23,15 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (anchorSpan.Snapshot == null) { - throw new ArgumentNullException("anchorSpan"); + throw new ArgumentNullException(nameof(anchorSpan)); } if (trackingMode < SpanTrackingMode.EdgeExclusive || trackingMode > SpanTrackingMode.EdgeNegative) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } if (bufferGraph == null) { - throw new ArgumentNullException("bufferGraph"); + throw new ArgumentNullException(nameof(bufferGraph)); } this.anchorSpan = anchorSpan; this.trackingMode = trackingMode; @@ -105,7 +106,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public NormalizedSnapshotSpanCollection GetSpans(ITextSnapshot targetSnapshot) { if (targetSnapshot == null) - throw new ArgumentNullException("targetSnapshot"); + throw new ArgumentNullException(nameof(targetSnapshot)); NormalizedSnapshotSpanCollection results = GetSpans(targetSnapshot.TextBuffer); if ((results.Count > 0) && (results[0].Snapshot != targetSnapshot)) @@ -126,7 +127,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } ITextBuffer anchorBuffer = this.AnchorBuffer; @@ -156,7 +157,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override string ToString() { - return String.Format("MappingSpan anchored at {0}", this.anchorSpan); + return String.Format(CultureInfo.CurrentCulture, "MappingSpan anchored at {0}", this.anchorSpan); } } -}
\ No newline at end of file +} diff --git a/src/Text/Impl/TextModel/NativeMethods.cs b/src/Text/Impl/TextModel/NativeMethods.cs new file mode 100644 index 0000000..78d507b --- /dev/null +++ b/src/Text/Impl/TextModel/NativeMethods.cs @@ -0,0 +1,28 @@ +using System.Runtime.InteropServices; + +namespace Microsoft.VisualStudio.Text.Implementation +{ + [StructLayout(LayoutKind.Sequential)] + internal struct BY_HANDLE_FILE_INFORMATION + { + public uint FileAttributes; + public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime; + public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime; + public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime; + public uint VolumeSerialNumber; + public uint FileSizeHigh; + public uint FileSizeLow; + public uint NumberOfLinks; + public uint FileIndexHigh; + public uint FileIndexLow; + } + + internal static class NativeMethods + { + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool GetFileInformationByHandle( + Microsoft.Win32.SafeHandles.SafeFileHandle hFile, + out BY_HANDLE_FILE_INFORMATION lpFileInformation + ); + } +} diff --git a/src/Text/Impl/TextModel/NormalizedTextChangeCollection.cs b/src/Text/Impl/TextModel/NormalizedTextChangeCollection.cs index 8e3352f..5a9e03b 100644 --- a/src/Text/Impl/TextModel/NormalizedTextChangeCollection.cs +++ b/src/Text/Impl/TextModel/NormalizedTextChangeCollection.cs @@ -16,7 +16,7 @@ namespace Microsoft.VisualStudio.Text.Implementation internal partial class NormalizedTextChangeCollection : INormalizedTextChangeCollection { - public static readonly NormalizedTextChangeCollection Empty = new NormalizedTextChangeCollection(new TextChange[0]); + public static readonly NormalizedTextChangeCollection Empty = new NormalizedTextChangeCollection(Array.Empty<TextChange>()); private readonly IReadOnlyList<TextChange> _changes; public static INormalizedTextChangeCollection Create(IReadOnlyList<TextChange> changes) @@ -36,7 +36,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (changes == null) { - throw new ArgumentNullException("changes"); + throw new ArgumentNullException(nameof(changes)); } if (changes.Count == 0) @@ -122,7 +122,7 @@ namespace Microsoft.VisualStudio.Text.Implementation } else if (changes.Count == 0) { - return new TextChange[0]; + return Array.Empty<TextChange>(); } TextChange[] work = TextUtilities.StableSort(changes, TextChange.Compare); @@ -215,10 +215,7 @@ namespace Microsoft.VisualStudio.Text.Implementation if (differenceOptions.HasValue) { - if (textDifferencingService == null) - { - throw new ArgumentNullException("stringDifferenceUtility"); - } + Requires.NotNull(textDifferencingService, nameof(textDifferencingService)); foreach (TextChange change in work) { if (change == null) continue; @@ -259,7 +256,7 @@ namespace Microsoft.VisualStudio.Text.Implementation string oldText = change.OldText; string newText = change.NewText; - if (oldText == newText) + if (string.Equals(oldText, newText, StringComparison.Ordinal)) { // This change simply evaporates. This case occurs frequently in Venus and it is much // better to short circuit it here than to fire up the differencing engine. diff --git a/src/Text/Impl/TextModel/PersistentSpan.cs b/src/Text/Impl/TextModel/PersistentSpan.cs index 79f205d..6ec563a 100644 --- a/src/Text/Impl/TextModel/PersistentSpan.cs +++ b/src/Text/Impl/TextModel/PersistentSpan.cs @@ -7,45 +7,42 @@ // namespace Microsoft.VisualStudio.Text.Implementation { - using Microsoft.VisualStudio.Text; - using Microsoft.VisualStudio.Utilities; using System; - using System.Diagnostics; internal sealed class PersistentSpan : IPersistentSpan { #region members - private PersistentSpanFactory _factory; + public PersistentSpanSet SpanSet; private ITrackingSpan _span; //null for spans on closed documents or disposed spans - private ITextDocument _document; //null for spans on closed documents or disposed spans - private string _filePath; //null for spans on opened documents or disposed spans private int _startLine; //these parameters are valid whether or not the document is open (but _start*,_end* may be stale). private int _startIndex; private int _endLine; private int _endIndex; - private Span _nonTrackingSpan; + private ITextVersion _originalVersion = null; + private Span _originalSpan; // This is either the span when this was created or when the document was reopened. + // It is default(Span) if either we were created (on an unopened document) with line/column indices or after the document was closed. private bool _useLineIndex; private readonly SpanTrackingMode _trackingMode; #endregion - internal PersistentSpan(ITextDocument document, SnapshotSpan span, SpanTrackingMode trackingMode, PersistentSpanFactory factory) + internal PersistentSpan(SnapshotSpan span, SpanTrackingMode trackingMode, PersistentSpanSet spanSet) { - //Arguments verified in factory - _document = document; - _span = span.Snapshot.CreateTrackingSpan(span, trackingMode); - _trackingMode = trackingMode; - _factory = factory; + _originalVersion = span.Snapshot.Version; + _originalSpan = span; + + PersistentSpan.SnapshotPointToLineIndex(span.Start, out _startLine, out _startIndex); + PersistentSpan.SnapshotPointToLineIndex(span.End, out _endLine, out _endIndex); + + _trackingMode = trackingMode; + this.SpanSet = spanSet; } - internal PersistentSpan(string filePath, int startLine, int startIndex, int endLine, int endIndex, SpanTrackingMode trackingMode, PersistentSpanFactory factory) + internal PersistentSpan(int startLine, int startIndex, int endLine, int endIndex, SpanTrackingMode trackingMode, PersistentSpanSet spanSet) { - //Arguments verified in factory - _filePath = filePath; - _useLineIndex = true; _startLine = startLine; _startIndex = startIndex; @@ -53,27 +50,22 @@ namespace Microsoft.VisualStudio.Text.Implementation _endIndex = endIndex; _trackingMode = trackingMode; - - _factory = factory; + this.SpanSet = spanSet; } - internal PersistentSpan(string filePath, Span span, SpanTrackingMode trackingMode, PersistentSpanFactory factory) + internal PersistentSpan(Span span, SpanTrackingMode trackingMode, PersistentSpanSet spanSet) { - //Arguments verified in factory - _filePath = filePath; - _useLineIndex = false; - _nonTrackingSpan = span; + _originalSpan = span; _trackingMode = trackingMode; - - _factory = factory; + this.SpanSet = spanSet; } #region IPersistentSpan members - public bool IsDocumentOpen { get { return _document != null; } } + public bool IsDocumentOpen { get { return this.SpanSet.Document != null; } } - public ITextDocument Document { get { return _document; } } + public ITextDocument Document { get { return this.SpanSet.Document; } } public ITrackingSpan Span { get { return _span; } } @@ -81,84 +73,129 @@ namespace Microsoft.VisualStudio.Text.Implementation { get { - return (_document != null) ? _document.FilePath : _filePath; + if (this.SpanSet == null) + throw new ObjectDisposedException("PersistentSpan"); + + return (this.SpanSet.Document != null) ? this.SpanSet.Document.FilePath : this.SpanSet.FileKey.ToString(); } } public bool TryGetStartLineIndex(out int startLine, out int startIndex) { - if ((_document == null) && (_filePath == null)) + if (this.SpanSet == null) throw new ObjectDisposedException("PersistentSpan"); if (_span != null) - this.UpdateStartEnd(); - - startLine = _startLine; - startIndex = _startIndex; + { + SnapshotSpan span = _span.GetSpan(_span.TextBuffer.CurrentSnapshot); + PersistentSpan.SnapshotPointToLineIndex(span.Start, out startLine, out startIndex); + return true; + } + else if (_useLineIndex) + { + startLine = _startLine; + startIndex = _startIndex; + return true; + } - return ((_span != null) || _useLineIndex); + startLine = startIndex = 0; + return false; } public bool TryGetEndLineIndex(out int endLine, out int endIndex) { - if ((_document == null) && (_filePath == null)) + if (this.SpanSet == null) throw new ObjectDisposedException("PersistentSpan"); if (_span != null) - this.UpdateStartEnd(); + { + SnapshotSpan span = _span.GetSpan(_span.TextBuffer.CurrentSnapshot); + PersistentSpan.SnapshotPointToLineIndex(span.End, out endLine, out endIndex); + return true; + } + else if (_useLineIndex) + { + endLine = _endLine; + endIndex = _endIndex; + return true; + } - endLine = _endLine; - endIndex = _endIndex; - return ((_span != null) || _useLineIndex); + endLine = endIndex = 0; + return false; } public bool TryGetSpan(out Span span) { - if ((_document == null) && (_filePath == null)) + if (this.SpanSet == null) throw new ObjectDisposedException("PersistentSpan"); if (_span != null) - this.UpdateStartEnd(); + { + span = _span.GetSpan(_span.TextBuffer.CurrentSnapshot); + return true; + } + else if (!_useLineIndex) + { + span = _originalSpan; + return true; + } - span = _nonTrackingSpan; - return ((_span != null) || !_useLineIndex); + span = new Span(); + return false; } #endregion #region IDisposable members public void Dispose() { - if ((_document != null) || (_filePath != null)) + if (this.SpanSet != null) { - _factory.Delete(this); - + this.SpanSet.Delete(this); + this.SpanSet = null; + _originalVersion = null; _span = null; - _document = null; - _filePath = null; } } #endregion - #region private helpers - internal void DocumentClosed() + internal void SetSpanSet(PersistentSpanSet spanSet) { - this.UpdateStartEnd(); + if (this.SpanSet == null) + throw new ObjectDisposedException("PersistentSpan"); + + this.SpanSet = spanSet; + } + + internal void DocumentClosed(ITextSnapshot savedSnapshot) + { + Assumes.NotNull(_originalVersion); + + if ((savedSnapshot != null) && (savedSnapshot.Version.VersionNumber > _originalVersion.VersionNumber)) + { + // The document was saved and we want to line/column indices in the saved snapshot (& not the current snapshot) + var savedSpan = new SnapshotSpan(savedSnapshot, Tracking.TrackSpanForwardInTime(_trackingMode, _originalSpan, _originalVersion, savedSnapshot.Version)); + + PersistentSpan.SnapshotPointToLineIndex(savedSpan.Start, out _startLine, out _startIndex); + PersistentSpan.SnapshotPointToLineIndex(savedSpan.End, out _endLine, out _endIndex); + } + else + { + // The document was never saved (or was saved before we created) so continue to use the old line/column indices. + // Since those are set when either the span is created (against an open document) or when the document is reopened, + // they don't need to be changed. + } //We set this to false when the document is closed because we have an accurate line/index and that is more stable //than a simple offset. _useLineIndex = true; - _nonTrackingSpan = new Span(0, 0); - - _filePath = _document.FilePath; - _document = null; + _originalSpan = default(Span); + _originalVersion = null; _span = null; } - internal void DocumentReopened(ITextDocument document) + internal void DocumentReopened() { - _document = document; - - ITextSnapshot snapshot = document.TextBuffer.CurrentSnapshot; + ITextSnapshot snapshot = this.SpanSet.Document.TextBuffer.CurrentSnapshot; SnapshotPoint start; SnapshotPoint end; @@ -178,23 +215,27 @@ namespace Microsoft.VisualStudio.Text.Implementation } else { - start = new SnapshotPoint(snapshot, Math.Min(_nonTrackingSpan.Start, snapshot.Length)); - end = new SnapshotPoint(snapshot, Math.Min(_nonTrackingSpan.End, snapshot.Length)); + start = new SnapshotPoint(snapshot, Math.Min(_originalSpan.Start, snapshot.Length)); + end = new SnapshotPoint(snapshot, Math.Min(_originalSpan.End, snapshot.Length)); } - _span = snapshot.CreateTrackingSpan(new SnapshotSpan(start, end), _trackingMode); + var snapshotSpan = new SnapshotSpan(start, end); + _span = snapshot.CreateTrackingSpan(snapshotSpan, _trackingMode); + _originalSpan = snapshotSpan; - _filePath = null; + _originalVersion = snapshot.Version; + PersistentSpan.SnapshotPointToLineIndex(snapshotSpan.Start, out _startLine, out _startIndex); + PersistentSpan.SnapshotPointToLineIndex(snapshotSpan.End, out _endLine, out _endIndex); } - private void UpdateStartEnd() + private SnapshotSpan UpdateStartEnd() { SnapshotSpan span = _span.GetSpan(_span.TextBuffer.CurrentSnapshot); - _nonTrackingSpan = span; - PersistentSpan.SnapshotPointToLineIndex(span.Start, out _startLine, out _startIndex); PersistentSpan.SnapshotPointToLineIndex(span.End, out _endLine, out _endIndex); + + return span; } private static void SnapshotPointToLineIndex(SnapshotPoint p, out int line, out int index) @@ -207,10 +248,13 @@ namespace Microsoft.VisualStudio.Text.Implementation internal static SnapshotPoint LineIndexToSnapshotPoint(int line, int index, ITextSnapshot snapshot) { - ITextSnapshotLine l = snapshot.GetLineFromLineNumber(Math.Min(line, snapshot.LineCount - 1)); + if (line >= snapshot.LineCount) + { + return new SnapshotPoint(snapshot, snapshot.Length); + } + ITextSnapshotLine l = snapshot.GetLineFromLineNumber(line); return l.Start + Math.Min(index, l.Length); } - #endregion } -}
\ No newline at end of file +} diff --git a/src/Text/Impl/TextModel/PersistentSpanFactory.cs b/src/Text/Impl/TextModel/PersistentSpanFactory.cs index 6ea3a3d..7e62ae1 100644 --- a/src/Text/Impl/TextModel/PersistentSpanFactory.cs +++ b/src/Text/Impl/TextModel/PersistentSpanFactory.cs @@ -7,14 +7,8 @@ // namespace Microsoft.VisualStudio.Text.Implementation { - using Microsoft.VisualStudio.Text; - using Microsoft.VisualStudio.Utilities; - using Microsoft.VisualStudio.Text.Utilities; - using System; using System.Collections.Generic; using System.ComponentModel.Composition; - using System.Diagnostics; - using System.IO; [Export(typeof(IPersistentSpanFactory))] internal class PersistentSpanFactory : IPersistentSpanFactory @@ -22,17 +16,13 @@ namespace Microsoft.VisualStudio.Text.Implementation [Import] internal ITextDocumentFactoryService TextDocumentFactoryService; - private readonly Dictionary<object, FrugalList<PersistentSpan>> _spansOnDocuments = new Dictionary<object, FrugalList<PersistentSpan>>(); //Used for lock - + private readonly Dictionary<object, PersistentSpanSet> _spansOnDocuments = new Dictionary<object, PersistentSpanSet>(); //Used for lock private bool _eventsHooked; #region IPersistentSpanFactory members public bool CanCreate(ITextBuffer buffer) { - if (buffer == null) - { - throw new ArgumentNullException("buffer"); - } + Requires.NotNull(buffer, nameof(buffer)); ITextDocument document; return this.TextDocumentFactoryService.TryGetTextDocument(buffer, out document); @@ -40,14 +30,16 @@ namespace Microsoft.VisualStudio.Text.Implementation public IPersistentSpan Create(SnapshotSpan span, SpanTrackingMode trackingMode) { + Requires.NotNull(span.Snapshot, nameof(span.Snapshot)); + ITextDocument document; if (this.TextDocumentFactoryService.TryGetTextDocument(span.Snapshot.TextBuffer, out document)) { - PersistentSpan persistentSpan = new PersistentSpan(document, span, trackingMode, this); - - this.AddSpan(document, persistentSpan); - - return persistentSpan; + lock (_spansOnDocuments) + { + var spanSet = this.GetOrCreateSpanSet(null, document); + return spanSet.Create(span, trackingMode); + } } return null; @@ -55,21 +47,21 @@ namespace Microsoft.VisualStudio.Text.Implementation public IPersistentSpan Create(ITextSnapshot snapshot, int startLine, int startIndex, int endLine, int endIndex, SpanTrackingMode trackingMode) { + Requires.NotNull(snapshot, nameof(snapshot)); + Requires.Argument(startLine >= 0, nameof(startLine), "Must be non-negative."); + Requires.Argument(startIndex >= 0, nameof(startIndex), "Must be non-negative."); + Requires.Argument(endLine >= startLine, nameof(endLine), "Must be >= startLine."); + Requires.Argument((endIndex >= 0) && ((startLine != endLine) || (endIndex >= startIndex)), nameof(endIndex), "Must be non-negative and (endLine,endIndex) may not be before (startLine,startIndex)."); + Requires.Range(((int)trackingMode >= (int)SpanTrackingMode.EdgeExclusive) || ((int)trackingMode <= (int)(SpanTrackingMode.EdgeNegative)), nameof(trackingMode)); + ITextDocument document; if (this.TextDocumentFactoryService.TryGetTextDocument(snapshot.TextBuffer, out document)) { - var start = PersistentSpan.LineIndexToSnapshotPoint(startLine, startIndex, snapshot); - var end = PersistentSpan.LineIndexToSnapshotPoint(endLine, endIndex, snapshot); - if (end < start) + lock (_spansOnDocuments) { - end = start; + var spanSet = this.GetOrCreateSpanSet(null, document); + return spanSet.Create(snapshot, startLine, startIndex, endLine, endIndex, trackingMode); } - - PersistentSpan persistentSpan = new PersistentSpan(document, new SnapshotSpan(start, end), trackingMode, this); - - this.AddSpan(document, persistentSpan); - - return persistentSpan; } return null; @@ -77,218 +69,134 @@ namespace Microsoft.VisualStudio.Text.Implementation public IPersistentSpan Create(string filePath, int startLine, int startIndex, int endLine, int endIndex, SpanTrackingMode trackingMode) { - if (string.IsNullOrEmpty(filePath)) - { - throw new ArgumentException("filePath"); - } - if (startLine < 0) - { - throw new ArgumentOutOfRangeException("startLine", "Must be non-negative."); - } - if (startIndex < 0) - { - throw new ArgumentOutOfRangeException("startIndex", "Must be non-negative."); - } - if (endLine < startLine) - { - throw new ArgumentOutOfRangeException("endLine", "Must be >= startLine."); - } - if ((endIndex < 0) || ((startLine == endLine) && (endIndex < startIndex))) - { - throw new ArgumentOutOfRangeException("endIndex", "Must be non-negative and (endLine,endIndex) may not be before (startLine,startIndex)."); - } - if (((int)trackingMode < (int)SpanTrackingMode.EdgeExclusive) || ((int)trackingMode > (int)(SpanTrackingMode.EdgeNegative))) + Requires.NotNullOrEmpty(filePath, nameof(filePath)); + Requires.Argument(startLine >= 0, nameof(startLine), "Must be non-negative."); + Requires.Argument(startIndex >= 0, nameof(startIndex), "Must be non-negative."); + Requires.Argument(endLine >= startLine, nameof(endLine), "Must be >= startLine."); + Requires.Argument((endIndex >= 0) && ((startLine != endLine) || (endIndex >= startIndex)), nameof(endIndex), "Must be non-negative and (endLine,endIndex) may not be before (startLine,startIndex)."); + Requires.Range(((int)trackingMode >= (int)SpanTrackingMode.EdgeExclusive) || ((int)trackingMode <= (int)(SpanTrackingMode.EdgeNegative)), nameof(trackingMode)); + + var key = new FileNameKey(filePath); + lock (_spansOnDocuments) { - throw new ArgumentOutOfRangeException("trackingMode"); + var spanSet = this.GetOrCreateSpanSet(key, null); + return spanSet.Create(startLine, startIndex, endLine, endIndex, trackingMode); } - - PersistentSpan persistentSpan = new PersistentSpan(filePath, startLine, startIndex, endLine, endIndex, trackingMode, this); - - this.AddSpan(new FileNameKey(filePath), persistentSpan); - - return persistentSpan; } public IPersistentSpan Create(string filePath, Span span, SpanTrackingMode trackingMode) { - if (string.IsNullOrEmpty(filePath)) - { - throw new ArgumentException("filePath"); - } - if (((int)trackingMode < (int)SpanTrackingMode.EdgeExclusive) || ((int)trackingMode > (int)(SpanTrackingMode.EdgeNegative))) + Requires.NotNullOrEmpty(filePath, nameof(filePath)); + Requires.Range(((int)trackingMode >= (int)SpanTrackingMode.EdgeExclusive) || ((int)trackingMode <= (int)(SpanTrackingMode.EdgeNegative)), nameof(trackingMode)); + + var key = new FileNameKey(filePath); + lock (_spansOnDocuments) { - throw new ArgumentOutOfRangeException("trackingMode"); + var spanSet = this.GetOrCreateSpanSet(key, null); + return spanSet.Create(span, trackingMode); } - - PersistentSpan persistentSpan = new PersistentSpan(filePath, span, trackingMode, this); - - this.AddSpan(new FileNameKey(filePath), persistentSpan); - - return persistentSpan; } #endregion - internal bool IsEmpty { get { return _spansOnDocuments.Count == 0; } } //For unit tests + internal bool IsEmpty { get { return _spansOnDocuments.Count == 0; } } //For unit tests - private void AddSpan(object key, PersistentSpan persistentSpan) + private PersistentSpanSet GetOrCreateSpanSet(FileNameKey filePath, ITextDocument document) { - lock (_spansOnDocuments) + object key = ((object)document) ?? filePath; + if (!_spansOnDocuments.TryGetValue(key, out PersistentSpanSet spanSet)) { - FrugalList<PersistentSpan> spans; - if (!_spansOnDocuments.TryGetValue(key, out spans)) + if (!_eventsHooked) { - this.EnsureEventsHooked(); + _eventsHooked = true; - spans = new FrugalList<PersistentSpan>(); - _spansOnDocuments.Add(key, spans); + this.TextDocumentFactoryService.TextDocumentCreated += OnTextDocumentCreated; + this.TextDocumentFactoryService.TextDocumentDisposed += OnTextDocumentDisposed; } - spans.Add(persistentSpan); + spanSet = new PersistentSpanSet(filePath, document, this); + _spansOnDocuments.Add(key, spanSet); } - } - - private void EnsureEventsHooked() - { - if (!_eventsHooked) - { - _eventsHooked = true; - this.TextDocumentFactoryService.TextDocumentCreated += OnTextDocumentCreated; - this.TextDocumentFactoryService.TextDocumentDisposed += OnTextDocumentDisposed; - } + return spanSet; } private void OnTextDocumentCreated(object sender, TextDocumentEventArgs e) { var path = new FileNameKey(e.TextDocument.FilePath); - FrugalList<PersistentSpan> spans; lock (_spansOnDocuments) { - if (_spansOnDocuments.TryGetValue(path, out spans)) + if (_spansOnDocuments.TryGetValue(path, out PersistentSpanSet spanSet)) { - foreach (var span in spans) - { - span.DocumentReopened(e.TextDocument); - } + spanSet.DocumentReopened(e.TextDocument); _spansOnDocuments.Remove(path); - _spansOnDocuments.Add(e.TextDocument, spans); + _spansOnDocuments.Add(e.TextDocument, spanSet); } } } private void OnTextDocumentDisposed(object sender, TextDocumentEventArgs e) { - FrugalList<PersistentSpan> spans; lock (_spansOnDocuments) { - if (_spansOnDocuments.TryGetValue(e.TextDocument, out spans)) + if (_spansOnDocuments.TryGetValue(e.TextDocument, out PersistentSpanSet spanSet)) { - foreach (var span in spans) - { - span.DocumentClosed(); - } - + spanSet.DocumentClosed(); _spansOnDocuments.Remove(e.TextDocument); - var path = new FileNameKey(e.TextDocument.FilePath); - FrugalList<PersistentSpan> existingSpansOnPath; - if (_spansOnDocuments.TryGetValue(path, out existingSpansOnPath)) + if (_spansOnDocuments.TryGetValue(spanSet.FileKey, out PersistentSpanSet existingSpansOnPath)) { - //Handle (badly) the case where a document is renamed to an existing closed document & then closed. - existingSpansOnPath.AddRange(spans); + // Handle (badly) the case where a document is renamed to an existing closed document & then closed. + // We should only end up in this case if we had spans on two open documents that were both renamed + // to the same file name & then closed. + foreach (var s in spanSet.Spans) + { + s.SetSpanSet(existingSpansOnPath); + existingSpansOnPath.Spans.Add(s); + } + + spanSet.Spans.Clear(); + spanSet.Dispose(); } else { - _spansOnDocuments.Add(path, spans); + _spansOnDocuments.Add(spanSet.FileKey, spanSet); } } } } - internal void Delete(PersistentSpan span) + internal void DocumentRenamed(PersistentSpanSet spanSet) { lock (_spansOnDocuments) { - ITextDocument document = span.Document; - if (document != null) + if (_spansOnDocuments.TryGetValue(spanSet.FileKey, out PersistentSpanSet existingSpansOnPath)) { - FrugalList<PersistentSpan> spans; - if (_spansOnDocuments.TryGetValue(document, out spans)) + // There were spans on a closed document with the same name as this one. Move all of those spans to this one + // and "open" them (note that this will probably do bad things to their positions but it is the best we + // can do). + foreach (var s in existingSpansOnPath.Spans) { - spans.Remove(span); + s.SetSpanSet(spanSet); + spanSet.Spans.Add(s); - if (spans.Count == 0) - { - //Last one ... remove all references to document. - _spansOnDocuments.Remove(document); - } + s.DocumentReopened(); } - else - { - Debug.Fail("There should have been an entry in SpanOnDocuments."); - } - } - else - { - var path = new FileNameKey(span.FilePath); - FrugalList<PersistentSpan> spans; - if (_spansOnDocuments.TryGetValue(path, out spans)) - { - spans.Remove(span); - if (spans.Count == 0) - { - //Last one ... remove all references to path. - _spansOnDocuments.Remove(path); - } - } - else - { - Debug.Fail("There should have been an entry in SpanOnDocuments."); - } + existingSpansOnPath.Spans.Clear(); + existingSpansOnPath.Dispose(); } } } - - private class FileNameKey + internal void Delete(PersistentSpanSet spanSet, PersistentSpan span) { - private readonly string _fileName; - private readonly int _hashCode; - - public FileNameKey(string fileName) + lock (_spansOnDocuments) { - //Gracefully catch errors getting the full path (which can happen if the file name is on a protected share). - try + if (spanSet.Spans.Remove(span) && (spanSet.Spans.Count == 0)) { - _fileName = Path.GetFullPath(fileName); + _spansOnDocuments.Remove(((object)(spanSet.Document)) ?? spanSet.FileKey); + spanSet.Dispose(); } - catch - { - //This shouldn't happen (we are generally passed names associated with documents that we are expecting to open so - //we should have access). If we fail, we will, at worst not get the same underlying document when people create - //persistent spans using unnormalized names. - _fileName = fileName; - } - - _hashCode = StringComparer.OrdinalIgnoreCase.GetHashCode(_fileName); - } - - //Override equality and hash code - public override int GetHashCode() - { - return _hashCode; - } - - public override bool Equals(object obj) - { - var other = obj as FileNameKey; - return (other != null) && string.Equals(_fileName, other._fileName, StringComparison.OrdinalIgnoreCase); - } - - public override string ToString() - { - return _fileName; } } } diff --git a/src/Text/Impl/TextModel/PersistentSpanSet.cs b/src/Text/Impl/TextModel/PersistentSpanSet.cs new file mode 100644 index 0000000..8370eeb --- /dev/null +++ b/src/Text/Impl/TextModel/PersistentSpanSet.cs @@ -0,0 +1,125 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// +// This file contain implementations details that are subject to change without notice. +// Use at your own risk. +// +namespace Microsoft.VisualStudio.Text.Implementation +{ + using System; + using System.Collections.Generic; + + sealed class PersistentSpanSet : IDisposable + { + internal FileNameKey FileKey; + internal ITextDocument Document; + internal readonly HashSet<PersistentSpan> Spans = new HashSet<PersistentSpan>(); + private readonly PersistentSpanFactory Factory; + + private ITextSnapshot _savedSnapshot = null; + + internal PersistentSpanSet(FileNameKey filePath, ITextDocument document, PersistentSpanFactory factory) + { + this.FileKey = filePath; + this.Document = document; + this.Factory = factory; + + if (document != null) + { + document.FileActionOccurred += this.OnFileActionOccurred; + } + } + + public void Dispose() + { + Assumes.True(this.Spans.Count == 0); + + if (this.Document != null) + { + this.Document.FileActionOccurred -= this.OnFileActionOccurred; + this.Document = null; + } + } + + internal PersistentSpan Create(int startLine, int startIndex, int endLine, int endIndex, SpanTrackingMode trackingMode) + { + PersistentSpan persistentSpan = new PersistentSpan(startLine, startIndex, endLine, endIndex, trackingMode, this); + this.Spans.Add(persistentSpan); + return persistentSpan; + } + + internal PersistentSpan Create(Span span, SpanTrackingMode trackingMode) + { + var persistentSpan = new PersistentSpan(span, trackingMode, this); + this.Spans.Add(persistentSpan); + return persistentSpan; + } + + internal PersistentSpan Create(ITextSnapshot snapshot, int startLine, int startIndex, int endLine, int endIndex, SpanTrackingMode trackingMode) + { + var start = PersistentSpan.LineIndexToSnapshotPoint(startLine, startIndex, snapshot); + var end = PersistentSpan.LineIndexToSnapshotPoint(endLine, endIndex, snapshot); + if (end < start) + { + end = start; + } + + return this.Create(new SnapshotSpan(start, end), trackingMode); + } + + internal PersistentSpan Create(SnapshotSpan span, SpanTrackingMode trackingMode) + { + var persistentSpan = new PersistentSpan(span, trackingMode, this); + this.Spans.Add(persistentSpan); + return persistentSpan; + } + + internal void Delete(PersistentSpan span) + { + this.Factory.Delete(this, span); + } + + internal void DocumentReopened(ITextDocument document) + { + Requires.NotNull(document, nameof(document)); + Assumes.Null(this.Document); + + this.Document = document; + document.FileActionOccurred += this.OnFileActionOccurred; + + foreach (var s in this.Spans) + { + s.DocumentReopened(); + } + } + + internal void DocumentClosed() + { + Assumes.NotNull(this.Document); + + this.FileKey = new FileNameKey(this.Document.FilePath); + + foreach (var s in this.Spans) + { + s.DocumentClosed(_savedSnapshot); + } + + this.Document.FileActionOccurred -= this.OnFileActionOccurred; + this.Document = null; + } + + private void OnFileActionOccurred(object sender, TextDocumentFileActionEventArgs e) + { + if (e.FileActionType == FileActionTypes.ContentSavedToDisk) + { + _savedSnapshot = this.Document.TextBuffer.CurrentSnapshot; + } + else if (e.FileActionType == FileActionTypes.DocumentRenamed) + { + this.FileKey = new FileNameKey(this.Document.FilePath); + this.Factory.DocumentRenamed(this); + } + } + } +} diff --git a/src/Text/Impl/TextModel/Projection/BaseProjectionBuffer.cs b/src/Text/Impl/TextModel/Projection/BaseProjectionBuffer.cs index 5dbdfc3..4a3572c 100644 --- a/src/Text/Impl/TextModel/Projection/BaseProjectionBuffer.cs +++ b/src/Text/Impl/TextModel/Projection/BaseProjectionBuffer.cs @@ -17,6 +17,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation using Microsoft.VisualStudio.Text.Differencing; using Microsoft.VisualStudio.Text.Utilities; using System.Collections.ObjectModel; + using System.Globalization; internal abstract class BaseProjectionBuffer : BaseBuffer, IProjectionBufferBase { @@ -184,7 +185,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation #endregion #region Debug support - [Conditional("_DEBUG")] + [Conditional("DEBUG")] protected void DumpPendingChanges(List<Tuple<ITextBuffer, List<TextChange>>> pendingSourceChanges) { if (BufferGroup.Tracing) @@ -203,7 +204,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation } } - [Conditional("_DEBUG")] + [Conditional("DEBUG")] protected void DumpPendingContentChangedEventArgs() { if (BufferGroup.Tracing) @@ -213,7 +214,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { sb.Append(TextUtilities.GetTag(args.Before.TextBuffer)); sb.Append(" V"); - sb.AppendLine(args.After.Version.VersionNumber.ToString()); + sb.AppendLine(args.After.Version.VersionNumber.ToString(CultureInfo.InvariantCulture)); foreach (var change in args.Changes) { sb.AppendLine(change.ToString()); diff --git a/src/Text/Impl/TextModel/Projection/BufferGraph.cs b/src/Text/Impl/TextModel/Projection/BufferGraph.cs index 87f42e8..da4d4eb 100644 --- a/src/Text/Impl/TextModel/Projection/BufferGraph.cs +++ b/src/Text/Impl/TextModel/Projection/BufferGraph.cs @@ -29,11 +29,11 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (topBuffer == null) { - throw new ArgumentNullException("topBuffer"); + throw new ArgumentNullException(nameof(topBuffer)); } if (guardedOperations == null) { - throw new ArgumentNullException("guardedOperations"); + throw new ArgumentNullException(nameof(guardedOperations)); } this.topBuffer = topBuffer; @@ -69,7 +69,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } FrugalList<ITextBuffer> buffers = new FrugalList<ITextBuffer>(); foreach (ITextBuffer buffer in this.importingProjectionBufferMap.Keys) @@ -100,19 +100,19 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (position.Snapshot == null) { - throw new ArgumentNullException("position"); + throw new ArgumentNullException(nameof(position)); } if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } if (affinity < PositionAffinity.Predecessor || affinity > PositionAffinity.Successor) { - throw new ArgumentOutOfRangeException("affinity"); + throw new ArgumentOutOfRangeException(nameof(affinity)); } if (!this.importingProjectionBufferMap.ContainsKey(position.Snapshot.TextBuffer)) { @@ -146,15 +146,15 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (position.Snapshot == null) { - throw new ArgumentNullException("position"); + throw new ArgumentNullException(nameof(position)); } if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } ITextBuffer currentBuffer = position.Snapshot.TextBuffer; @@ -184,19 +184,19 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (position.Snapshot == null) { - throw new ArgumentNullException("position"); + throw new ArgumentNullException(nameof(position)); } if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } if (targetBuffer == null) { - throw new ArgumentNullException("targetBuffer"); + throw new ArgumentNullException(nameof(targetBuffer)); } if (affinity < PositionAffinity.Predecessor || affinity > PositionAffinity.Successor) { - throw new ArgumentOutOfRangeException("affinity"); + throw new ArgumentOutOfRangeException(nameof(affinity)); } ITextBuffer currentBuffer = position.Snapshot.TextBuffer; @@ -228,7 +228,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (targetSnapshot == null) { - throw new ArgumentNullException("targetSnapshot"); + throw new ArgumentNullException(nameof(targetSnapshot)); } SnapshotPoint? result = MapDownToBuffer(position, trackingMode, targetSnapshot.TextBuffer, affinity); @@ -250,7 +250,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (targetSnapshot == null) { - throw new ArgumentNullException("targetSnapshot"); + throw new ArgumentNullException(nameof(targetSnapshot)); } SnapshotPoint? result = MapUpToBuffer(position, trackingMode, affinity, targetSnapshot.TextBuffer); @@ -266,7 +266,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } return CheckedMapUpToBuffer(point, trackingMode, match, affinity); } @@ -275,15 +275,15 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (point.Snapshot == null) { - throw new ArgumentNullException("point"); + throw new ArgumentNullException(nameof(point)); } if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } if (affinity < PositionAffinity.Predecessor || affinity > PositionAffinity.Successor) { - throw new ArgumentOutOfRangeException("affinity"); + throw new ArgumentOutOfRangeException(nameof(affinity)); } if (!this.importingProjectionBufferMap.ContainsKey(point.Snapshot.TextBuffer)) @@ -328,15 +328,15 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (span.Snapshot == null) { - throw new ArgumentNullException("span"); + throw new ArgumentNullException(nameof(span)); } if (trackingMode < SpanTrackingMode.EdgeExclusive || trackingMode > SpanTrackingMode.EdgeNegative) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } if (!this.importingProjectionBufferMap.ContainsKey(span.Snapshot.TextBuffer)) @@ -372,7 +372,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (targetBuffer == null) { - throw new ArgumentNullException("targetBuffer"); + throw new ArgumentNullException(nameof(targetBuffer)); } if (!this.importingProjectionBufferMap.ContainsKey(targetBuffer)) @@ -389,7 +389,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (targetSnapshot == null) { - throw new ArgumentNullException("targetSnapshot"); + throw new ArgumentNullException(nameof(targetSnapshot)); } NormalizedSnapshotSpanCollection results = MapDownToBuffer(span, trackingMode, targetSnapshot.TextBuffer); @@ -411,7 +411,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (targetSnapshot == null) { - throw new ArgumentNullException("targetSnapshot"); + throw new ArgumentNullException(nameof(targetSnapshot)); } NormalizedSnapshotSpanCollection results = MapUpToBuffer(span, trackingMode, targetSnapshot.TextBuffer); @@ -482,7 +482,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } return CheckedMapUpToBuffer(span, trackingMode, match); } @@ -491,11 +491,11 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (span.Snapshot == null) { - throw new ArgumentNullException("span"); + throw new ArgumentNullException(nameof(span)); } if (trackingMode < SpanTrackingMode.EdgeExclusive || trackingMode > SpanTrackingMode.EdgeNegative) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } ITextBuffer buffer = span.Snapshot.TextBuffer; if (!this.importingProjectionBufferMap.ContainsKey(buffer)) diff --git a/src/Text/Impl/TextModel/Projection/BufferGraphFactoryService.cs b/src/Text/Impl/TextModel/Projection/BufferGraphFactoryService.cs index 7c47c06..f4b1cc1 100644 --- a/src/Text/Impl/TextModel/Projection/BufferGraphFactoryService.cs +++ b/src/Text/Impl/TextModel/Projection/BufferGraphFactoryService.cs @@ -21,7 +21,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } return textBuffer.Properties.GetOrCreateSingletonProperty<BufferGraph>(() => (new BufferGraph(textBuffer, GuardedOperations))); } diff --git a/src/Text/Impl/TextModel/Projection/ElisionBuffer.cs b/src/Text/Impl/TextModel/Projection/ElisionBuffer.cs index f47748e..6d30821 100644 --- a/src/Text/Impl/TextModel/Projection/ElisionBuffer.cs +++ b/src/Text/Impl/TextModel/Projection/ElisionBuffer.cs @@ -123,6 +123,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation this.sourceBuffer = sourceBuffer; this.sourceSnapshot = sourceBuffer.CurrentSnapshot; + Debug.Assert(sourceBuffer is BaseBuffer); BaseBuffer baseSourceBuffer = (BaseBuffer)sourceBuffer; this.eventHook = new WeakEventHook(this, baseSourceBuffer); @@ -219,11 +220,11 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if ((spansToElide.Count > 0) && (spansToElide[spansToElide.Count - 1].End > this.elBuffer.sourceSnapshot.Length)) { - throw new ArgumentOutOfRangeException("spansToElide"); + throw new ArgumentOutOfRangeException(nameof(spansToElide)); } if ((spansToExpand.Count > 0) && (spansToExpand[spansToExpand.Count - 1].End > this.elBuffer.sourceSnapshot.Length)) { - throw new ArgumentOutOfRangeException("spansToExpand"); + throw new ArgumentOutOfRangeException(nameof(spansToExpand)); } ElisionSourceSpansChangedEventArgs args = this.elBuffer.ApplySpanChanges(spansToElide, spansToExpand); if (args != null) @@ -251,7 +252,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (spansToElide == null) { - throw new ArgumentNullException("spansToElide"); + throw new ArgumentNullException(nameof(spansToElide)); } return ModifySpans(spansToElide, null); } @@ -260,7 +261,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (spansToExpand == null) { - throw new ArgumentNullException("spansToExpand"); + throw new ArgumentNullException(nameof(spansToExpand)); } return ModifySpans(null, spansToExpand); } diff --git a/src/Text/Impl/TextModel/Projection/ElisionSnapshot.cs b/src/Text/Impl/TextModel/Projection/ElisionSnapshot.cs index 36b0779..8c70f24 100644 --- a/src/Text/Impl/TextModel/Projection/ElisionSnapshot.cs +++ b/src/Text/Impl/TextModel/Projection/ElisionSnapshot.cs @@ -91,7 +91,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } return this.sourceSnapshot.TextBuffer == textBuffer ? this.sourceSnapshot : null; } @@ -100,7 +100,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } if (this.sourceSnapshot.TextBuffer == textBuffer) @@ -121,7 +121,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } if (match(this.sourceSnapshot.TextBuffer)) @@ -142,11 +142,11 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (startSpanIndex < 0) { - throw new ArgumentOutOfRangeException("startSpanIndex"); + throw new ArgumentOutOfRangeException(nameof(startSpanIndex)); } if (count < 0 || startSpanIndex + count > SpanCount) { - throw new ArgumentOutOfRangeException("count"); + throw new ArgumentOutOfRangeException(nameof(count)); } return new ReadOnlyCollection<SnapshotSpan>(this.content.GetSourceSpans(this.sourceSnapshot, startSpanIndex, count)); } @@ -162,7 +162,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (position < 0 || position > this.totalLength) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } FrugalList<SnapshotPoint> points = this.content.MapInsertionPointToSourceSnapshots(this, position); if (points.Count == 1) @@ -183,11 +183,11 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (position < 0 || position > this.totalLength) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } if (affinity < PositionAffinity.Predecessor || affinity > PositionAffinity.Successor) { - throw new ArgumentOutOfRangeException("affinity"); + throw new ArgumentOutOfRangeException(nameof(affinity)); } return this.content.MapToSourceSnapshot(this.sourceSnapshot, position, affinity); } @@ -200,7 +200,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation } if (affinity < PositionAffinity.Predecessor || affinity > PositionAffinity.Successor) { - throw new ArgumentOutOfRangeException("affinity"); + throw new ArgumentOutOfRangeException(nameof(affinity)); } return this.content.MapFromSourceSnapshot(this, point.Position); } @@ -209,7 +209,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (span.End > this.totalLength) { - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); } FrugalList<SnapshotSpan> result = new FrugalList<SnapshotSpan>(); if (fillIn) diff --git a/src/Text/Impl/TextModel/Projection/ProjectionBuffer.cs b/src/Text/Impl/TextModel/Projection/ProjectionBuffer.cs index 67b747e..368db2f 100644 --- a/src/Text/Impl/TextModel/Projection/ProjectionBuffer.cs +++ b/src/Text/Impl/TextModel/Projection/ProjectionBuffer.cs @@ -554,10 +554,8 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { foreach (object spanToInsert in this.RawSpansToInsert) { - if (spanToInsert == null) - { - throw new ArgumentNullException("spansToInsert"); - } + Requires.NotNull(spanToInsert, nameof(spanToInsert)); + ITrackingSpan trackingSpanToInsert = spanToInsert as ITrackingSpan; if (trackingSpanToInsert != null) { @@ -731,15 +729,15 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (position < 0 || position > this.projBuffer.sourceSpans.Count) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } if (spansToReplace < 0 || position + spansToReplace > this.projBuffer.sourceSpans.Count) { - throw new ArgumentOutOfRangeException("spansToReplace"); + throw new ArgumentOutOfRangeException(nameof(spansToReplace)); } if (spansToInsert == null) { - throw new ArgumentNullException("spansToInsert"); + throw new ArgumentNullException(nameof(spansToInsert)); } this.spanManager = new SpanManager(this.projBuffer, position, spansToReplace, spansToInsert, true, (this.projBuffer.bufferOptions & ProjectionBufferOptions.WritableLiteralSpans) != 0); @@ -1596,12 +1594,15 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation } } +#pragma warning disable CA1801 // Review unused parameters private StringRebuilder InsertionLiesInCustomSpan(ITextSnapshot afterSourceSnapshot, int spanPosition, ITextChange incomingChange, HashSet<SnapshotPoint> urPoints, int accumulatedDelta) { +#pragma warning disable CA1801 // Review unused parameters + // just evaluate the new span and see if it overlaps the insertion. ITrackingSpan sourceTrackingSpan = this.sourceSpans[spanPosition]; SnapshotSpan afterSpan = sourceTrackingSpan.GetSpan(afterSourceSnapshot); diff --git a/src/Text/Impl/TextModel/Projection/ProjectionSnapshot.cs b/src/Text/Impl/TextModel/Projection/ProjectionSnapshot.cs index 5414af1..67a4473 100644 --- a/src/Text/Impl/TextModel/Projection/ProjectionSnapshot.cs +++ b/src/Text/Impl/TextModel/Projection/ProjectionSnapshot.cs @@ -17,6 +17,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation using Microsoft.VisualStudio.Text.Utilities; using Strings = Microsoft.VisualStudio.Text.Implementation.Strings; + using System.Globalization; internal partial class ProjectionSnapshot : BaseProjectionSnapshot, IProjectionSnapshot { @@ -192,7 +193,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } foreach (ITextSnapshot snappy in this.sourceSnapshotMap.Keys) { @@ -208,7 +209,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } foreach (ITextSnapshot snappy in this.sourceSnapshotMap.Keys) { @@ -233,7 +234,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (match == null) { - throw new ArgumentNullException("match"); + throw new ArgumentNullException(nameof(match)); } foreach (ITextSnapshot snappy in this.sourceSnapshotMap.Keys) { @@ -263,11 +264,11 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (startSpanIndex < 0 || startSpanIndex > this.SpanCount) { - throw new ArgumentOutOfRangeException("startSpanIndex"); + throw new ArgumentOutOfRangeException(nameof(startSpanIndex)); } if (count < 0 || startSpanIndex + count > this.SpanCount) { - throw new ArgumentOutOfRangeException("count"); + throw new ArgumentOutOfRangeException(nameof(count)); } // better using iterator or explicit successor func eventually @@ -290,7 +291,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (span.End > this.Length) { - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); } FrugalList<SnapshotSpan> mappedSpans = new FrugalList<SnapshotSpan>(); @@ -464,7 +465,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (position < 0 || position > this.totalLength) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } ReadOnlyCollection<SnapshotPoint> points = this.MapInsertionPointToSourceSnapshots(position, this.projectionBuffer.literalBuffer); // should this be conditional on writable literal buffer? if (points.Count == 1) @@ -485,11 +486,11 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (position < 0 || position > this.Length) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } if (affinity < PositionAffinity.Predecessor || affinity > PositionAffinity.Successor) { - throw new ArgumentOutOfRangeException("affinity"); + throw new ArgumentOutOfRangeException(nameof(affinity)); } int rover = affinity == PositionAffinity.Predecessor ? FindLowestSpanIndexOfPosition(position) @@ -508,7 +509,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (affinity < PositionAffinity.Predecessor || affinity > PositionAffinity.Successor) { - throw new ArgumentOutOfRangeException("affinity"); + throw new ArgumentOutOfRangeException(nameof(affinity)); } List<InvertedSource> orderedSources; @@ -576,7 +577,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation { if (position < 0 || position > this.Length) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } int rover = FindLowestSpanIndexOfPosition(position); @@ -740,7 +741,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation "{0,12} {1,10} {2,4} {3,12} {4}\r\n", new Span(cumulativeLength, sourceSpan.Length), TextUtilities.GetTagOrContentType(sourceSpan.Snapshot.TextBuffer), - "V" + sourceSpan.Snapshot.Version.VersionNumber.ToString(), + "V" + sourceSpan.Snapshot.Version.VersionNumber.ToString(CultureInfo.InvariantCulture), sourceSpan.Span, TextUtilities.Escape(sourceSpan.GetText())); cumulativeLength += sourceSpan.Length; diff --git a/src/Text/Impl/TextModel/Projection/ProjectionSpanToChangeConverter.cs b/src/Text/Impl/TextModel/Projection/ProjectionSpanToChangeConverter.cs index 06b0306..9e62620 100644 --- a/src/Text/Impl/TextModel/Projection/ProjectionSpanToChangeConverter.cs +++ b/src/Text/Impl/TextModel/Projection/ProjectionSpanToChangeConverter.cs @@ -48,7 +48,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation private void ConstructChanges() { - IDifferenceCollection<SnapshotSpan> diffs = differ.GetDifferences(); + var diffs = differ.GetDifferences(); List<TextChange> changes = new List<TextChange>(); int pos = this.textPosition; @@ -56,10 +56,10 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation // each difference generates a text change foreach (Difference diff in diffs) { - pos += GetMatchSize(differ.DeletedSpans, diff.Before); + pos += GetMatchSize(diffs.LeftSequence, diff.Before); TextChange change = TextChange.Create(pos, - BufferFactoryService.StringRebuilderFromSnapshotSpans(differ.DeletedSpans, diff.Left), - BufferFactoryService.StringRebuilderFromSnapshotSpans(differ.InsertedSpans, diff.Right), + BufferFactoryService.StringRebuilderFromSnapshotSpans(diffs.LeftSequence, diff.Left), + BufferFactoryService.StringRebuilderFromSnapshotSpans(diffs.RightSequence, diff.Right), this.currentSnapshot); changes.Add(change); pos += change.OldLength; @@ -67,7 +67,7 @@ namespace Microsoft.VisualStudio.Text.Projection.Implementation this.normalizedChanges = NormalizedTextChangeCollection.Create(changes); } - private static int GetMatchSize(ReadOnlyCollection<SnapshotSpan> spans, Match match) + private static int GetMatchSize(IList<SnapshotSpan> spans, Match match) { int size = 0; if (match != null) diff --git a/src/Text/Impl/TextModel/Storage/CharStream.cs b/src/Text/Impl/TextModel/Storage/CharStream.cs index 2179e9d..9a8e0f8 100644 --- a/src/Text/Impl/TextModel/Storage/CharStream.cs +++ b/src/Text/Impl/TextModel/Storage/CharStream.cs @@ -119,12 +119,12 @@ namespace Microsoft.VisualStudio.Text.Implementation } } - private char Make(byte hi, byte lo) + private static char Make(byte hi, byte lo) { return (char)((hi << 8) | lo); } - private void Split(char c, out byte hi, out byte lo) + private static void Split(char c, out byte hi, out byte lo) { hi = (byte)(c >> 8); lo = (byte)(c & 255); diff --git a/src/Text/Impl/TextModel/Storage/ILineBreaks.cs b/src/Text/Impl/TextModel/Storage/ILineBreaks.cs index ccf1358..2100860 100644 --- a/src/Text/Impl/TextModel/Storage/ILineBreaks.cs +++ b/src/Text/Impl/TextModel/Storage/ILineBreaks.cs @@ -32,4 +32,16 @@ namespace Microsoft.VisualStudio.Text.Implementation /// </summary> void Add(int start, int length); } + + public interface IPooledLineBreaksEditor : ILineBreaksEditor + { + /// <summary> + /// If the internal list of line breaks has excess capacity, copy it to a correctly sized list and return the oversized + /// list to a pool that can be reused. + /// </summary> + /// <remarks> + /// This method should be called when using calling <see cref="LineBreakManager.CreatePooledLineBreakEditor(int)"/>. + /// </remarks> + void ReleasePooledLineBreaks(); + } } diff --git a/src/Text/Impl/TextModel/Storage/LineBreakManager.cs b/src/Text/Impl/TextModel/Storage/LineBreakManager.cs index 2d3071b..f12a9a4 100644 --- a/src/Text/Impl/TextModel/Storage/LineBreakManager.cs +++ b/src/Text/Impl/TextModel/Storage/LineBreakManager.cs @@ -1,23 +1,39 @@ using System; -using System.Collections.Generic; +using System.Threading; using Microsoft.VisualStudio.Text.Utilities; namespace Microsoft.VisualStudio.Text.Implementation { public static class LineBreakManager { - public readonly static ILineBreaks Empty = new ShortLineBreaksEditor(0); + public readonly static ILineBreaks Empty = new ShortLineBreaksEditor(Array.Empty<ushort>()); + + /// <summary> + /// Create a line break editor using the pooled line break lists (which should have excess capacity). + /// </summary> + /// <remarks> + /// <para>ILineBreakEditor.ReleasePooledLineBreaks() should be called on the returne editors once all line breaks have been added.</para> + /// <para>Note that this method is thread-safe. If multiple PooledLineBreakEditor are created simultaneously on different threads then + /// only one will use the pooled line breaks (and the others will get freshly allocated line breaks).</para> + /// </remarks> + public static IPooledLineBreaksEditor CreatePooledLineBreakEditor(int maxLength) + { + return (maxLength <= short.MaxValue) + ? (IPooledLineBreaksEditor)(new ShortLineBreaksEditor(LineBreakListManager<ushort>.AcquireLineBreaks(ShortLineBreaksEditor.ExpectedNumberOfLines))) + : (IPooledLineBreaksEditor)(new IntLineBreaksEditor(LineBreakListManager<int>.AcquireLineBreaks(IntLineBreaksEditor.ExpectedNumberOfLines))); + } - public static ILineBreaksEditor CreateLineBreakEditor(int maxLength, int initialCapacity = 0) + // Create a line break editor using an allocated list (which should be sized to hold all the expected line breaks without reallocations), + public static ILineBreaksEditor CreateLineBreakEditor(int maxLength, int initialCapacity) { return (maxLength < short.MaxValue) - ? (ILineBreaksEditor)(new ShortLineBreaksEditor(initialCapacity)) - : (ILineBreaksEditor)(new IntLineBreaksEditor(initialCapacity)); + ? (ILineBreaksEditor)(new ShortLineBreaksEditor(new ushort[initialCapacity])) + : (ILineBreaksEditor)(new IntLineBreaksEditor(new int[initialCapacity])); } public static ILineBreaks CreateLineBreaks(string source) { - ILineBreaksEditor lineBreaks = null; + IPooledLineBreaksEditor lineBreaks = null; int index = 0; while (index < source.Length) @@ -30,112 +46,167 @@ namespace Microsoft.VisualStudio.Text.Implementation else { if (lineBreaks == null) - lineBreaks = LineBreakManager.CreateLineBreakEditor(source.Length); + lineBreaks = LineBreakManager.CreatePooledLineBreakEditor(source.Length); lineBreaks.Add(index, breakLength); index += breakLength; } } - return lineBreaks ?? Empty; + if (lineBreaks != null) + { + lineBreaks.ReleasePooledLineBreaks(); + return lineBreaks; + } + + return Empty; } - private class ShortLineBreaksEditor : ILineBreaksEditor + internal abstract class LineBreakListManager<T> : IPooledLineBreaksEditor { - private const ushort MaskForPosition = 0x7fff; - private const ushort MaskForLength = 0x8000; + internal static T[] _pooledLineBreaks = null; + + internal protected T[] LineBreaks; + + private int _length; - private readonly static List<ushort> Empty = new List<ushort>(0); - private List<ushort> _lineBreaks = Empty; + public int Length => _length; - public ShortLineBreaksEditor(int initialCapacity) + public LineBreakListManager(T[] lineBreaks) { - if (initialCapacity > 0) - _lineBreaks = new List<ushort>(initialCapacity); + this.LineBreaks = lineBreaks; } - public int Length => _lineBreaks.Count; + protected void Add(T value) + { + if (_length == this.LineBreaks.Length) + { + // Simulate a List.Add() + var newLineBreaks = new T[_length * 2]; + Array.Copy(this.LineBreaks, newLineBreaks, _length); - public int LengthOfLineBreak(int index) + this.LineBreaks = newLineBreaks; + } + + this.LineBreaks[_length++] = value; + } + + // In single threaded operations, we'll always be getting/reusing the same list of line breaks. We, however, need to handle + // the case of a file being simultaneously read on multiple threads (at which point one thread will get the pooled list, + // the other threads will allocate, and the largest list will end up back in the shared pool). + internal static T[] AcquireLineBreaks(int size) { - return ((_lineBreaks[index] & MaskForLength) != 0 ? 2 : 1); + T[] buffer = Volatile.Read(ref _pooledLineBreaks); + if (buffer != null && buffer.Length >= size) + { + if (buffer == Interlocked.CompareExchange(ref _pooledLineBreaks, null, buffer)) + { + return buffer; + } + } + + return new T[size]; } - public int StartOfLineBreak(int index) + public void ReleasePooledLineBreaks() { - return (int)(_lineBreaks[index] & MaskForPosition); + if (this.LineBreaks.Length != _length) + { + // We have excess capacity, so make an accurately sized copy of this.LineBreaks and return it to the pool. + T[] newLineBreaks; + if (_length > 0) + { + newLineBreaks = new T[_length]; + Array.Copy(this.LineBreaks, newLineBreaks, _length); + } + else + { + newLineBreaks = Array.Empty<T>(); + } + + T[] buffer = Volatile.Read(ref _pooledLineBreaks); + if ((buffer == null) || (buffer.Length < this.LineBreaks.Length)) + { + // We're done with this.LineBreaks and either there is nothing in the pool or + // this.LineBreaks are larger than the array in the pool so replace it with + // this.LineBreaks. + Interlocked.CompareExchange(ref _pooledLineBreaks, this.LineBreaks, buffer); + } + + this.LineBreaks = newLineBreaks; + } } - public int EndOfLineBreak(int index) + + public abstract void Add(int start, int length); + public abstract int StartOfLineBreak(int index); + public abstract int EndOfLineBreak(int index); + } + + private class ShortLineBreaksEditor : LineBreakListManager<ushort> + { + public const int ExpectedNumberOfLines = 500; // Guestimate on how many lines will be in a typical 16k block. + + private const ushort MaskForPosition = 0x7fff; + private const ushort MaskForLength = 0x8000; + + public ShortLineBreaksEditor(ushort[] lineBreaks) + : base(lineBreaks) + { } + + public override int StartOfLineBreak(int index) + { + return (int)(this.LineBreaks[index] & MaskForPosition); + } + + public override int EndOfLineBreak(int index) { - int lineBreak = _lineBreaks[index]; - return (lineBreak & MaskForPosition) + + int lineBreak = this.LineBreaks[index]; + return (lineBreak & MaskForPosition) + (((lineBreak & MaskForLength) != 0) ? 2 : 1); } - public void Add(int start, int length) + public override void Add(int start, int length) { if ((start < 0) || (start > short.MaxValue)) throw new ArgumentOutOfRangeException(nameof(start)); if ((length < 1) || (length > 2)) throw new ArgumentOutOfRangeException(nameof(length)); - if (_lineBreaks == Empty) - _lineBreaks = new List<ushort>(); - - if (length == 1) - _lineBreaks.Add((ushort)start); - else if (length == 2) - _lineBreaks.Add((ushort)(start | MaskForLength)); + this.Add((length == 1) ? (ushort)start : (ushort)(start | MaskForLength)); } } - private class IntLineBreaksEditor : ILineBreaksEditor + private class IntLineBreaksEditor : LineBreakListManager<int> { - private const uint MaskForPosition = 0x7fffffff; - private const uint MaskForLength = 0x80000000; - - private readonly static List<uint> Empty = new List<uint>(0); - private List<uint> _lineBreaks = Empty; - - public IntLineBreaksEditor(int initialCapacity) - { - if (initialCapacity > 0) - _lineBreaks = new List<uint>(initialCapacity); - } + public const int ExpectedNumberOfLines = 32000; // Guestimate on how many lines will be in a typical 1MB block. - public int Length => _lineBreaks.Count; + private const int MaskForPosition = int.MaxValue; //0x7fffffff + private const int MaskForLength = int.MinValue; //0x80000000 in an int-friendly way - public int LengthOfLineBreak(int index) - { - return (_lineBreaks[index] & MaskForLength) != 0 ? 2 : 1; - } + public IntLineBreaksEditor(int[] lineBreaks) + : base(lineBreaks) + { } - public int StartOfLineBreak(int index) + public override int StartOfLineBreak(int index) { - return (int)(_lineBreaks[index] & MaskForPosition); + return (int)(this.LineBreaks[index] & MaskForPosition); } - public int EndOfLineBreak(int index) + public override int EndOfLineBreak(int index) { - uint lineBreak = _lineBreaks[index]; - return (int)((lineBreak & MaskForPosition) + - (((lineBreak & MaskForLength) != 0) ? 2 : 1)); + int lineBreak = this.LineBreaks[index]; + return (lineBreak & MaskForPosition) + + (((lineBreak & MaskForLength) != 0) ? 2 : 1); } - public void Add(int start, int length) + public override void Add(int start, int length) { - if ((start < 0) || (start > int.MaxValue)) + if (start < 0) throw new ArgumentOutOfRangeException(nameof(start)); if ((length < 1) || (length > 2)) throw new ArgumentOutOfRangeException(nameof(length)); - if (_lineBreaks == Empty) - _lineBreaks = new List<uint>(); - - if (length == 1) - _lineBreaks.Add((uint)start); - else if (length == 2) - _lineBreaks.Add((uint)(start | MaskForLength)); + this.Add((length == 1) ? start : (start | MaskForLength)); } } } diff --git a/src/Text/Impl/TextModel/Storage/TextImageLoader.cs b/src/Text/Impl/TextModel/Storage/TextImageLoader.cs index 428d29c..da7646d 100644 --- a/src/Text/Impl/TextModel/Storage/TextImageLoader.cs +++ b/src/Text/Impl/TextModel/Storage/TextImageLoader.cs @@ -6,7 +6,6 @@ // Use at your own risk. // using System; -using System.Collections.Generic; using System.IO; using System.Threading; using Microsoft.VisualStudio.Text.Utilities; @@ -17,7 +16,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { public const int BlockSize = 16384; - internal static StringRebuilder Load(TextReader reader, long fileSize, string id, + internal static StringRebuilder Load(TextReader reader, long fileSize, out bool hasConsistentLineEndings, out int longestLineLength, int blockSize = 0, int minCompressedBlockSize = TextImageLoader.BlockSize) // Exposed for unit tests @@ -52,8 +51,7 @@ namespace Microsoft.VisualStudio.Text.Implementation if (read == 0) break; - var lineBreaks = LineBreakManager.CreateLineBreakEditor(read); - TextImageLoader.ParseBlock(buffer, read, lineBreaks, ref lineEnding, ref currentLineLength, ref longestLineLength); + var lineBreaks = TextImageLoader.ParseBlock(buffer, read, ref lineEnding, ref currentLineLength, ref longestLineLength); char[] bufferForStringBuilder = buffer; if (read < (buffer.Length / 2)) @@ -64,7 +62,7 @@ namespace Microsoft.VisualStudio.Text.Implementation } else { - // We're using most of bufferForStringRebuilder so allocate a new block for the next chunk. + // We're using most of buffer so allocate a new block for the next chunk. buffer = new char[blockSize]; } @@ -76,7 +74,6 @@ namespace Microsoft.VisualStudio.Text.Implementation } longestLineLength = Math.Max(longestLineLength, currentLineLength); - hasConsistentLineEndings = lineEnding != LineEndingState.Inconsistent; } finally { @@ -86,6 +83,8 @@ namespace Microsoft.VisualStudio.Text.Implementation } } + hasConsistentLineEndings = lineEnding != LineEndingState.Inconsistent; + return content; } @@ -110,9 +109,12 @@ namespace Microsoft.VisualStudio.Text.Implementation return read; } - private static void ParseBlock(char[] buffer, int length, ILineBreaksEditor lineBreaks, - ref LineEndingState lineEnding, ref int currentLineLength, ref int longestLineLength) + private static ILineBreaks ParseBlock(char[] buffer, int length, + ref LineEndingState lineEnding, ref int currentLineLength, ref int longestLineLength) { + // Note that the lineBreaks created here will (internally) use the pooled list of line breaks. + IPooledLineBreaksEditor lineBreaks = LineBreakManager.CreatePooledLineBreakEditor(length); + int index = 0; while (index < length) { @@ -161,6 +163,10 @@ namespace Microsoft.VisualStudio.Text.Implementation index += breakLength; } } + + lineBreaks.ReleasePooledLineBreaks(); + + return lineBreaks; } internal enum LineEndingState diff --git a/src/Text/Impl/TextModel/StringRebuilder/BinaryStringRebuilder.cs b/src/Text/Impl/TextModel/StringRebuilder/BinaryStringRebuilder.cs index e3e3581..26139fc 100644 --- a/src/Text/Impl/TextModel/StringRebuilder/BinaryStringRebuilder.cs +++ b/src/Text/Impl/TextModel/StringRebuilder/BinaryStringRebuilder.cs @@ -164,9 +164,9 @@ namespace Microsoft.VisualStudio.Text.Implementation public static StringRebuilder Create(StringRebuilder left, StringRebuilder right) { if (left == null) - throw new ArgumentNullException("left"); + throw new ArgumentNullException(nameof(left)); if (right == null) - throw new ArgumentNullException("right"); + throw new ArgumentNullException(nameof(right)); if (left.Length == 0) return right; @@ -203,7 +203,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override int GetLineNumberFromPosition(int position) { if ((position < 0) || (position > this.Length)) - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); return (position <= _left.Length) ? _left.GetLineNumberFromPosition(position) @@ -214,7 +214,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override void GetLineFromLineNumber(int lineNumber, out Span extent, out int lineBreakLength) { if ((lineNumber < 0) || (lineNumber > this.LineBreakCount)) - throw new ArgumentOutOfRangeException("lineNumber"); + throw new ArgumentOutOfRangeException(nameof(lineNumber)); if (lineNumber < _left.LineBreakCount) { @@ -273,7 +273,7 @@ namespace Microsoft.VisualStudio.Text.Implementation get { if ((index < 0) || (index >= this.Length)) - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); return (index < _left.Length) ? _left[index] @@ -284,7 +284,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override string GetText(Span span) { if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); if (span.End <= _left.Length) return _left.GetText(span); @@ -338,9 +338,9 @@ namespace Microsoft.VisualStudio.Text.Implementation public override void Write(TextWriter writer, Span span) { if (writer == null) - throw new ArgumentNullException("writer"); + throw new ArgumentNullException(nameof(writer)); if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); if (span.Start >= _left.Length) _right.Write(writer, new Span(span.Start - _left.Length, span.Length)); @@ -356,7 +356,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override StringRebuilder GetSubText(Span span) { if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); if (span.Length == this.Length) return this; diff --git a/src/Text/Impl/TextModel/StringRebuilder/StringRebuilder.cs b/src/Text/Impl/TextModel/StringRebuilder/StringRebuilder.cs index 1fe8b63..9134e56 100644 --- a/src/Text/Impl/TextModel/StringRebuilder/StringRebuilder.cs +++ b/src/Text/Impl/TextModel/StringRebuilder/StringRebuilder.cs @@ -34,7 +34,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public static StringRebuilder Create(string text) { if (text == null) - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); #if DEBUG Interlocked.Add(ref _totalCharactersScanned, text.Length); #endif @@ -258,10 +258,10 @@ namespace Microsoft.VisualStudio.Text.Implementation public char[] ToCharArray(int startIndex, int length) { if (startIndex < 0) - throw new ArgumentOutOfRangeException("startIndex"); + throw new ArgumentOutOfRangeException(nameof(startIndex)); if ((length < 0) || (startIndex + length > this.Length) || (startIndex + length < 0)) - throw new ArgumentOutOfRangeException("length"); + throw new ArgumentOutOfRangeException(nameof(length)); char[] copy = new char[length]; this.CopyTo(startIndex, copy, 0, length); @@ -331,9 +331,9 @@ namespace Microsoft.VisualStudio.Text.Implementation public StringRebuilder Insert(int position, StringRebuilder text) { if ((position < 0) || (position > this.Length)) - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); if (text == null) - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); return this.Assemble(Span.FromBounds(0, position), text, Span.FromBounds(position, this.Length)); } @@ -351,7 +351,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public StringRebuilder Delete(Span span) { if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); return this.Assemble(Span.FromBounds(0, span.Start), Span.FromBounds(span.End, this.Length)); } @@ -402,9 +402,9 @@ namespace Microsoft.VisualStudio.Text.Implementation public StringRebuilder Replace(Span span, StringRebuilder text) { if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); if (text == null) - throw new ArgumentNullException("text"); + throw new ArgumentNullException(nameof(text)); return this.Assemble(Span.FromBounds(0, span.Start), text, Span.FromBounds(span.End, this.Length)); } diff --git a/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForChars.cs b/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForChars.cs index a4f4293..70e932a 100644 --- a/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForChars.cs +++ b/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForChars.cs @@ -71,7 +71,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override StringRebuilder GetSubText(Span span) { if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); if (span.Length == 0) return StringRebuilder.Empty; diff --git a/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForCompressedChars.cs b/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForCompressedChars.cs index ca80b09..59dd801 100644 --- a/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForCompressedChars.cs +++ b/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForCompressedChars.cs @@ -62,7 +62,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override StringRebuilder GetSubText(Span span) { if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); if (span.Length == this.Length) return this; diff --git a/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForString.cs b/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForString.cs index 56e5c8a..75838f1 100644 --- a/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForString.cs +++ b/src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForString.cs @@ -96,7 +96,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override StringRebuilder GetSubText(Span span) { if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); if (span.Length == 0) return StringRebuilder.Empty; diff --git a/src/Text/Impl/TextModel/StringRebuilder/UnaryStringRebuilder.cs b/src/Text/Impl/TextModel/StringRebuilder/UnaryStringRebuilder.cs index 3f53283..41f11fb 100644 --- a/src/Text/Impl/TextModel/StringRebuilder/UnaryStringRebuilder.cs +++ b/src/Text/Impl/TextModel/StringRebuilder/UnaryStringRebuilder.cs @@ -57,7 +57,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override int GetLineNumberFromPosition(int position) { if ((position < 0) || (position > this.Length)) - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); //Convert position to a position relative to the start of _text. if (position == this.Length) @@ -87,7 +87,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override void GetLineFromLineNumber(int lineNumber, out Span extent, out int lineBreakLength) { if ((lineNumber < 0) || (lineNumber > this.LineBreakCount)) - throw new ArgumentOutOfRangeException("lineNumber"); + throw new ArgumentOutOfRangeException(nameof(lineNumber)); int absoluteLineNumber = _lineBreakSpanStart + lineNumber; @@ -122,7 +122,7 @@ namespace Microsoft.VisualStudio.Text.Implementation protected char GetChar(char[] content, int index) { if ((index < 0) || (index >= this.Length)) - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); #if DEBUG Interlocked.Increment(ref _totalCharactersReturned); @@ -134,7 +134,7 @@ namespace Microsoft.VisualStudio.Text.Implementation protected string GetText(char[] content, Span span) { if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); #if DEBUG Interlocked.Add(ref _totalCharactersReturned, span.Length); @@ -146,19 +146,19 @@ namespace Microsoft.VisualStudio.Text.Implementation protected void CopyTo(char[] content, int sourceIndex, char[] destination, int destinationIndex, int count) { if (sourceIndex < 0) - throw new ArgumentOutOfRangeException("sourceIndex"); + throw new ArgumentOutOfRangeException(nameof(sourceIndex)); if (destination == null) - throw new ArgumentNullException("destination"); + throw new ArgumentNullException(nameof(destination)); if (destinationIndex < 0) - throw new ArgumentOutOfRangeException("destinationIndex"); + throw new ArgumentOutOfRangeException(nameof(destinationIndex)); if (count < 0) - throw new ArgumentOutOfRangeException("count"); + throw new ArgumentOutOfRangeException(nameof(count)); if ((sourceIndex + count > this.Length) || (sourceIndex + count < 0)) - throw new ArgumentOutOfRangeException("count"); + throw new ArgumentOutOfRangeException(nameof(count)); if ((destinationIndex + count > destination.Length) || (destinationIndex + count < 0)) - throw new ArgumentOutOfRangeException("count"); + throw new ArgumentOutOfRangeException(nameof(count)); #if DEBUG Interlocked.Add(ref _totalCharactersCopied, count); @@ -170,9 +170,9 @@ namespace Microsoft.VisualStudio.Text.Implementation protected void Write(char[] content, TextWriter writer, Span span) { if (writer == null) - throw new ArgumentNullException("writer"); + throw new ArgumentNullException(nameof(writer)); if (span.End > this.Length) - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); writer.Write(content, span.Start + _textSpanStart, span.Length); } diff --git a/src/Text/Impl/TextModel/TextChange.cs b/src/Text/Impl/TextModel/TextChange.cs index 972854e..b1f3383 100644 --- a/src/Text/Impl/TextModel/TextChange.cs +++ b/src/Text/Impl/TextModel/TextChange.cs @@ -53,7 +53,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (oldPosition < 0) { - throw new ArgumentOutOfRangeException("oldPosition"); + throw new ArgumentOutOfRangeException(nameof(oldPosition)); } _oldPosition = oldPosition; @@ -106,7 +106,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (value < 0) { - throw new ArgumentOutOfRangeException("value"); + throw new ArgumentOutOfRangeException(nameof(value)); } _oldPosition = value; } @@ -119,7 +119,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (value < 0) { - throw new ArgumentOutOfRangeException("value"); + throw new ArgumentOutOfRangeException(nameof(value)); } _newPosition = value; } @@ -226,7 +226,7 @@ namespace Microsoft.VisualStudio.Text.Implementation internal void RecordMasterChangeOffset(int masterChangeOffset) { if (masterChangeOffset < 0) - throw new ArgumentOutOfRangeException("masterChangeOffset", "MasterChangeOffset should be non-negative."); + throw new ArgumentOutOfRangeException(nameof(masterChangeOffset), "MasterChangeOffset should be non-negative."); if (_masterChangeOffset != -1) throw new InvalidOperationException("MasterChangeOffset has already been set."); diff --git a/src/Text/Impl/TextModel/TextDocument.cs b/src/Text/Impl/TextModel/TextDocument.cs index e546e97..62d0f5a 100644 --- a/src/Text/Impl/TextModel/TextDocument.cs +++ b/src/Text/Impl/TextModel/TextDocument.cs @@ -15,7 +15,7 @@ namespace Microsoft.VisualStudio.Text.Implementation using Microsoft.VisualStudio.Utilities; using Microsoft.VisualStudio.Text.Editor; - internal partial class TextDocument : ITextDocument + internal sealed partial class TextDocument : ITextDocument { #region Private Members @@ -50,19 +50,19 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } if (filePath == null) { - throw new ArgumentNullException("filePath"); + throw new ArgumentNullException(nameof(filePath)); } if (textDocumentFactoryService == null) { - throw new ArgumentNullException("textDocumentFactoryService"); + throw new ArgumentNullException(nameof(textDocumentFactoryService)); } if (encoding == null) { - throw new ArgumentNullException("encoding"); + throw new ArgumentNullException(nameof(encoding)); } _textBuffer = textBuffer; @@ -125,7 +125,7 @@ namespace Microsoft.VisualStudio.Text.Implementation } if (newFilePath == null) { - throw new ArgumentNullException("newFilePath"); + throw new ArgumentNullException(nameof(newFilePath)); } _filePath = newFilePath; @@ -147,7 +147,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { bool hasConsistentLineEndings; int longestLineLength; - StringRebuilder newContent = TextImageLoader.Load(streamReader, fileSize, _filePath, out hasConsistentLineEndings, out longestLineLength); + StringRebuilder newContent = TextImageLoader.Load(streamReader, fileSize, out hasConsistentLineEndings, out longestLineLength); if (!hasConsistentLineEndings) { @@ -361,11 +361,11 @@ namespace Microsoft.VisualStudio.Text.Implementation } if (filePath == null) { - throw new ArgumentNullException("filePath"); + throw new ArgumentNullException(nameof(filePath)); } PerformSave(overwrite ? FileMode.Create : FileMode.CreateNew, filePath, createFolder); - UpdateSaveStatus(filePath, _filePath != filePath); + UpdateSaveStatus(filePath, !string.Equals(_filePath, filePath, StringComparison.Ordinal)); // file path won't be updated if the save fails (in which case PerformSave will throw an exception) @@ -376,7 +376,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (newContentType == null) { - throw new ArgumentNullException("newContentType"); + throw new ArgumentNullException(nameof(newContentType)); } SaveAs(filePath, overwrite, createFolder); // content type won't be changed if the save fails (in which case SaveAs will throw an exception) @@ -391,7 +391,7 @@ namespace Microsoft.VisualStudio.Text.Implementation } if (filePath == null) { - throw new ArgumentNullException("filePath"); + throw new ArgumentNullException(nameof(filePath)); } PerformSave(overwrite ? FileMode.Create : FileMode.CreateNew, filePath, createFolder); @@ -453,7 +453,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (value == null) { - throw new ArgumentNullException("value"); + throw new ArgumentNullException(nameof(value)); } Encoding oldEncoding = _encoding; diff --git a/src/Text/Impl/TextModel/TextDocumentFactoryService.cs b/src/Text/Impl/TextModel/TextDocumentFactoryService.cs index ff59ae3..67c75f9 100644 --- a/src/Text/Impl/TextModel/TextDocumentFactoryService.cs +++ b/src/Text/Impl/TextModel/TextDocumentFactoryService.cs @@ -48,17 +48,17 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (filePath == null) { - throw new ArgumentNullException("filePath"); + throw new ArgumentNullException(nameof(filePath)); } if (contentType == null) { - throw new ArgumentNullException("contentType"); + throw new ArgumentNullException(nameof(contentType)); } if (encoding == null) { - throw new ArgumentNullException("encoding"); + throw new ArgumentNullException(nameof(encoding)); } var fallbackDetector = new FallbackDetector(encoding.DecoderFallback); @@ -191,12 +191,12 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } if (filePath == null) { - throw new ArgumentNullException("filePath"); + throw new ArgumentNullException(nameof(filePath)); } TextDocument textDocument = new TextDocument(textBuffer, filePath, DateTime.UtcNow, this, Encoding.UTF8); @@ -209,7 +209,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (textBuffer == null) { - throw new ArgumentNullException("textBuffer"); + throw new ArgumentNullException(nameof(textBuffer)); } textDocument = null; diff --git a/src/Text/Impl/TextModel/TextImageVersion.cs b/src/Text/Impl/TextModel/TextImageVersion.cs index ef3e44a..783a32a 100644 --- a/src/Text/Impl/TextModel/TextImageVersion.cs +++ b/src/Text/Impl/TextModel/TextImageVersion.cs @@ -98,7 +98,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public int TrackTo(VersionedPosition other, PointTrackingMode mode) { if (other.Version == null) - throw new ArgumentException(nameof(other)); + throw new ArgumentException(nameof(other) + " version cannot be null"); if (other.Version.VersionNumber == this.VersionNumber) return other.Position; @@ -112,7 +112,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public Span TrackTo(VersionedSpan span, SpanTrackingMode mode) { if (span.Version == null) - throw new ArgumentException(nameof(span)); + throw new ArgumentException(nameof(span) + " version cannot be null"); if (span.Version.VersionNumber == this.VersionNumber) return span.Span; diff --git a/src/Text/Impl/TextModel/TextVersion.cs b/src/Text/Impl/TextModel/TextVersion.cs index 55727c5..3e16443 100644 --- a/src/Text/Impl/TextModel/TextVersion.cs +++ b/src/Text/Impl/TextModel/TextVersion.cs @@ -8,6 +8,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { using System; + using System.Globalization; /// <summary> /// An internal implementation of ITextVersion @@ -131,7 +132,7 @@ namespace Microsoft.VisualStudio.Text.Implementation // Forward fidelity is implicit if (trackingMode == SpanTrackingMode.Custom) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } return new ForwardFidelityTrackingSpan(this, new Span(start, length), trackingMode); } @@ -146,7 +147,7 @@ namespace Microsoft.VisualStudio.Text.Implementation // Forward fidelity is implicit if (trackingMode == SpanTrackingMode.Custom) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } return new ForwardFidelityTrackingSpan(this, span, trackingMode); } @@ -155,7 +156,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (trackingMode == SpanTrackingMode.Custom) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } if (trackingFidelity == TrackingFidelityMode.Forward) { @@ -171,7 +172,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (behavior == null) { - throw new ArgumentNullException("behavior"); + throw new ArgumentNullException(nameof(behavior)); } if (trackingFidelity != TrackingFidelityMode.Forward) { @@ -183,7 +184,7 @@ namespace Microsoft.VisualStudio.Text.Implementation public override string ToString() { - return String.Format("V{0} (r{1})", VersionNumber, ReiteratedVersionNumber); + return String.Format(CultureInfo.CurrentCulture, "V{0} (r{1})", this.VersionNumber, ReiteratedVersionNumber); } } } diff --git a/src/Text/Impl/TextModel/TrackingPoint.cs b/src/Text/Impl/TextModel/TrackingPoint.cs index 02750f3..3549d03 100644 --- a/src/Text/Impl/TextModel/TrackingPoint.cs +++ b/src/Text/Impl/TextModel/TrackingPoint.cs @@ -21,15 +21,15 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (version == null) { - throw new ArgumentNullException("version"); + throw new ArgumentNullException(nameof(version)); } if (position < 0 | position > version.Length) { - throw new ArgumentOutOfRangeException("position"); + throw new ArgumentOutOfRangeException(nameof(position)); } if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } this.trackingMode = trackingMode; @@ -50,7 +50,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (version == null) { - throw new ArgumentNullException("version"); + throw new ArgumentNullException(nameof(version)); } if (version.TextBuffer != this.TextBuffer) { @@ -63,7 +63,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (snapshot == null) { - throw new ArgumentNullException("snapshot"); + throw new ArgumentNullException(nameof(snapshot)); } if (snapshot.TextBuffer != this.TextBuffer) { diff --git a/src/Text/Impl/TextModel/TrackingSpan.cs b/src/Text/Impl/TextModel/TrackingSpan.cs index 8ffb5fe..82d5d55 100644 --- a/src/Text/Impl/TextModel/TrackingSpan.cs +++ b/src/Text/Impl/TextModel/TrackingSpan.cs @@ -21,15 +21,15 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (version == null) { - throw new ArgumentNullException("version"); + throw new ArgumentNullException(nameof(version)); } if (span.End > version.Length) { - throw new ArgumentOutOfRangeException("span"); + throw new ArgumentOutOfRangeException(nameof(span)); } if (trackingMode < SpanTrackingMode.EdgeExclusive || trackingMode > SpanTrackingMode.Custom) { - throw new ArgumentOutOfRangeException("trackingMode"); + throw new ArgumentOutOfRangeException(nameof(trackingMode)); } this.trackingMode = trackingMode; @@ -50,7 +50,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (version == null) { - throw new ArgumentNullException("version"); + throw new ArgumentNullException(nameof(version)); } if (version.TextBuffer != this.TextBuffer) { @@ -63,7 +63,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (snapshot == null) { - throw new ArgumentNullException("snapshot"); + throw new ArgumentNullException(nameof(snapshot)); } if (snapshot.TextBuffer != this.TextBuffer) { diff --git a/src/Text/Impl/TextModel/TrivialNormalizedTextChangeCollection.cs b/src/Text/Impl/TextModel/TrivialNormalizedTextChangeCollection.cs index bb70ebd..e92dbf9 100644 --- a/src/Text/Impl/TextModel/TrivialNormalizedTextChangeCollection.cs +++ b/src/Text/Impl/TextModel/TrivialNormalizedTextChangeCollection.cs @@ -39,7 +39,7 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (index != 0) { - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); } return this; } @@ -95,11 +95,11 @@ namespace Microsoft.VisualStudio.Text.Implementation { if (array == null) { - throw new ArgumentNullException("array"); + throw new ArgumentNullException(nameof(array)); } if (arrayIndex < 0) { - throw new ArgumentOutOfRangeException("arrayIndex"); + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); } if (array.Rank > 1 || arrayIndex >= array.Length) { diff --git a/src/Text/Impl/TextSearch/BackgroundSearch.cs b/src/Text/Impl/TextSearch/BackgroundSearch.cs index f0e6db9..3bbe3ec 100644 --- a/src/Text/Impl/TextSearch/BackgroundSearch.cs +++ b/src/Text/Impl/TextSearch/BackgroundSearch.cs @@ -25,7 +25,7 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation /// Once we've searched a section of the buffer we don't search it again unless it is modified. /// Even if we get multiple, nearly simultaneous requests to search a section of the buffer, we only search it once. /// </remarks> - internal class BackgroundSearch<T> : IDisposable where T : ITag + internal sealed class BackgroundSearch<T> : IDisposable where T : ITag { private ITextBuffer _buffer; private readonly ITextSearchService2 _textSearchService; @@ -429,6 +429,7 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation public void Dispose() { _isDisposed = true; + GC.SuppressFinalize(this); } #endregion diff --git a/src/Text/Impl/TextSearch/TextSearchNavigatorFactoryService.cs b/src/Text/Impl/TextSearch/TextSearchNavigatorFactoryService.cs index 278532f..a555f9b 100644 --- a/src/Text/Impl/TextSearch/TextSearchNavigatorFactoryService.cs +++ b/src/Text/Impl/TextSearch/TextSearchNavigatorFactoryService.cs @@ -25,7 +25,7 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (buffer == null) { - throw new ArgumentNullException("buffer"); + throw new ArgumentNullException(nameof(buffer)); } // Don't return a singleton since it's allowed to have multiple search navigators on the same buffer diff --git a/src/Text/Impl/TextSearch/TextSearchService.cs b/src/Text/Impl/TextSearch/TextSearchService.cs index 71c44e1..5146f4f 100644 --- a/src/Text/Impl/TextSearch/TextSearchService.cs +++ b/src/Text/Impl/TextSearch/TextSearchService.cs @@ -8,16 +8,15 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { using System; - using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.Composition; using System.Diagnostics; + using System.Linq; using System.Text.RegularExpressions; + using System.Threading; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Text.Utilities; - using System.Linq; - using System.Threading; [Export(typeof(ITextSearchService))] [Export(typeof(ITextSearchService2))] @@ -30,18 +29,12 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation // Cache of recently used Regex expressions to save on construction // of Regex objects. - static IDictionary<string, WeakReference> _cachedRegexEngines; - static ReaderWriterLockSlim _regexCacheLock; + static IDictionary<string, WeakReference> _cachedRegexEngines = new Dictionary<string, WeakReference>(_maxCachedRegexEngines); + static ReaderWriterLockSlim _regexCacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); // Maximum number of Regex engines to cache const int _maxCachedRegexEngines = 10; - static TextSearchService() - { - _cachedRegexEngines = new Dictionary<string, WeakReference>(_maxCachedRegexEngines); - _regexCacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); - } - #region ITextSearchService Members public SnapshotSpan? FindNext(int startIndex, bool wraparound, FindData findData) @@ -49,12 +42,12 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation // We allow startIndex to be at the end of the buffer if ((startIndex < 0) || (startIndex > findData.TextSnapshotToSearch.Length)) { - throw new ArgumentOutOfRangeException("startIndex"); + throw new ArgumentOutOfRangeException(nameof(startIndex)); } if (string.IsNullOrEmpty(findData.SearchString)) { - throw new ArgumentException("Search pattern can't be empty or null", "findData"); + throw new ArgumentException("Search pattern can't be empty or null", nameof(findData)); } FindOptions options = findData.FindOptions; @@ -102,7 +95,7 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (string.IsNullOrEmpty(findData.SearchString)) { - throw new ArgumentException("Search pattern can't be empty or null", "findData"); + throw new ArgumentException("Search pattern can't be empty or null", nameof(findData)); } FindOptions options = findData.FindOptions; @@ -140,7 +133,7 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (string.IsNullOrEmpty(searchPattern)) { - throw new ArgumentException("Pattern can't be empty or null", "searchPattern"); + throw new ArgumentException("Pattern can't be empty or null", nameof(searchPattern)); } return Find(startingPosition, new SnapshotSpan(startingPosition.Snapshot, Span.FromBounds(0, startingPosition.Snapshot.Length)), searchPattern, options); @@ -150,7 +143,7 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (string.IsNullOrEmpty(searchPattern)) { - throw new ArgumentException("Pattern can't be empty or null", "searchPattern"); + throw new ArgumentException("Pattern can't be empty or null", nameof(searchPattern)); } if (searchRange.Snapshot != startingPosition.Snapshot) @@ -170,13 +163,10 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (string.IsNullOrEmpty(searchPattern)) { - throw new ArgumentException("Pattern can't be empty or null", "searchPattern"); + throw new ArgumentException("Pattern can't be empty or null", nameof(searchPattern)); } - if (replacePattern == null) - { - throw new ArgumentNullException("Replace pattern can't be null.", "replacePattern"); - } + Requires.NotNull(replacePattern, nameof(replacePattern)); return FindForReplace(startingPosition, new SnapshotSpan(startingPosition.Snapshot, Span.FromBounds(0, startingPosition.Snapshot.Length)), searchPattern, replacePattern, options, out expandedReplacePattern); @@ -186,13 +176,10 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (string.IsNullOrEmpty(searchPattern)) { - throw new ArgumentException("Pattern can't be empty or null", "searchPattern"); + throw new ArgumentException("Pattern can't be empty or null", nameof(searchPattern)); } - if (replacePattern == null) - { - throw new ArgumentNullException("Replace pattern can't be null.", "replacePattern"); - } + Requires.NotNull(replacePattern, nameof(replacePattern)); return FindForReplace(((options & FindOptions.SearchReverse) != FindOptions.SearchReverse) ? searchRange.Start : searchRange.End, searchRange, searchPattern, replacePattern, options, out expandedReplacePattern); } @@ -201,12 +188,12 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (string.IsNullOrEmpty(searchPattern)) { - throw new ArgumentException("Pattern can't be empty or null", "searchPattern"); + throw new ArgumentException("Pattern can't be empty or null", nameof(searchPattern)); } if (searchRange.Length == 0) { - return new SnapshotSpan[] { }; + return Array.Empty<SnapshotSpan>(); } return FindAllForReplace(searchRange.Start, searchRange, searchPattern, null, options).Select(r => r.Item1); @@ -216,12 +203,12 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (string.IsNullOrEmpty(searchPattern)) { - throw new ArgumentException("Pattern can't be empty or null", "searchPattern"); + throw new ArgumentException("Pattern can't be empty or null", nameof(searchPattern)); } if (searchRange.Length == 0) { - return new SnapshotSpan[] { }; + return Array.Empty<SnapshotSpan>(); } if (searchRange.Snapshot != startingPosition.Snapshot) @@ -241,13 +228,10 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (string.IsNullOrEmpty(searchPattern)) { - throw new ArgumentException("Search pattern can't be null or empty.", "searchPattern"); + throw new ArgumentException("Search pattern can't be null or empty.", nameof(searchPattern)); } - if (replacePattern == null) - { - throw new ArgumentNullException("Replace pattern can't be null.", "replacePattern"); - } + Requires.NotNull(replacePattern, nameof(replacePattern)); return FindAllForReplace(searchRange.Start, searchRange, searchPattern, replacePattern, options); } diff --git a/src/Text/Impl/TextSearch/TextSearchTagger.cs b/src/Text/Impl/TextSearch/TextSearchTagger.cs index 6a8852b..150a67b 100644 --- a/src/Text/Impl/TextSearch/TextSearchTagger.cs +++ b/src/Text/Impl/TextSearch/TextSearchTagger.cs @@ -19,7 +19,7 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation /// <remarks> /// This tagger -- like most others -- will not raise a TagsChanged event when the buffer changes. /// </remarks> - class TextSearchTagger<T> : ITextSearchTagger<T> where T : ITag + internal sealed class TextSearchTagger<T> : ITextSearchTagger<T> where T : ITag { // search service to use for doing the real search ITextSearchService2 _searchService; @@ -103,12 +103,12 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if ((searchOptions & FindOptions.SearchReverse) == FindOptions.SearchReverse) { - throw new ArgumentException("FindOptions.SearchReverse is invalid as searches are performed forwards to ensure all matches in a requested search span are found.", "searchOptions"); + throw new ArgumentException("FindOptions.SearchReverse is invalid as searches are performed forwards to ensure all matches in a requested search span are found.", nameof(searchOptions)); } if ((searchOptions & FindOptions.Wrap) == FindOptions.Wrap) { - throw new ArgumentException("FindOptions.Wrap is invalid as searches are performed forwards with no wrapping to ensure all matches in a requested span are found.", "searchOptions"); + throw new ArgumentException("FindOptions.Wrap is invalid as searches are performed forwards with no wrapping to ensure all matches in a requested span are found.", nameof(searchOptions)); } _searchTerms.Add(new BackgroundSearch<T>(_searchService, _buffer, searchTerm, searchOptions, tagFactory, this.ResultsCalculated)); diff --git a/src/Text/Impl/TextSearch/TextSearchTaggerFactoryService.cs b/src/Text/Impl/TextSearch/TextSearchTaggerFactoryService.cs index fca8282..711fda8 100644 --- a/src/Text/Impl/TextSearch/TextSearchTaggerFactoryService.cs +++ b/src/Text/Impl/TextSearch/TextSearchTaggerFactoryService.cs @@ -25,7 +25,7 @@ namespace Microsoft.VisualStudio.Text.Find.Implementation { if (buffer == null) { - throw new ArgumentNullException("buffer"); + throw new ArgumentNullException(nameof(buffer)); } // Don't return singleton instances since multiple taggers can exist per buffer |