Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vs-editor-api.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Core/Def/BaseUtility/BaseDefinitionAttribute.cs2
-rw-r--r--src/Core/Def/BaseUtility/DefaultOrderings.cs28
-rw-r--r--src/Core/Def/BaseUtility/DisplayNameAttribute.cs18
-rw-r--r--src/Core/Def/BaseUtility/IGuardedOperations.cs122
-rw-r--r--src/Core/Def/BaseUtility/ITelemetryIdProvider.cs6
-rw-r--r--src/Core/Def/BaseUtility/LocalizedNameAttribute.cs78
-rw-r--r--src/Core/Def/BaseUtility/NameAttribute.cs15
-rw-r--r--src/Core/Def/BaseUtility/OptionUserModifiableAttribute.cs26
-rw-r--r--src/Core/Def/BaseUtility/OptionUserVisibleAttribute.cs26
-rw-r--r--src/Core/Def/BaseUtility/OrderAttribute.cs16
-rw-r--r--src/Core/Def/BaseUtility/Orderer.cs140
-rw-r--r--src/Core/Def/BaseUtility/PriorityAttribute.cs28
-rw-r--r--src/Core/Def/BaseUtility/PropertyCollection.cs2
-rw-r--r--src/Core/Def/ContentType/ContentTypeAttribute.cs13
-rw-r--r--src/Core/Def/ContentType/FileExtensionAttribute.cs13
-rw-r--r--src/Core/Def/ContentType/FileExtensionToContentTypeDefinition.cs6
-rw-r--r--src/Core/Def/ContentType/IFileExtensionRegistryService2.cs2
-rw-r--r--src/Core/Def/ContentType/IFilePathToContentTypeProvider.cs32
-rw-r--r--src/Core/Def/ContentType/IFileToContentTypeService.cs107
-rw-r--r--src/Core/Def/Features/FeatureDefinition.cs22
-rw-r--r--src/Core/Def/Features/FeatureEventArgs.cs56
-rw-r--r--src/Core/Def/Features/IFeatureController.cs15
-rw-r--r--src/Core/Def/Features/IFeatureCookie.cs31
-rw-r--r--src/Core/Def/Features/IFeatureDisableToken.cs16
-rw-r--r--src/Core/Def/Features/IFeatureService.cs61
-rw-r--r--src/Core/Def/Features/IFeatureServiceFactory.cs45
-rw-r--r--src/Core/Def/Features/PredefinedEditorFeatureNames.cs30
-rw-r--r--src/Core/Def/ImageId.cs25
-rw-r--r--src/Core/Impl/ContentType/ContentTypeImpl.cs20
-rw-r--r--src/Core/Impl/ContentType/ContentTypeRegistryServiceImpl.cs186
-rw-r--r--src/Core/Impl/ContentType/IFileExtensionToContentTypeMetadata.cs19
-rw-r--r--src/Core/Impl/ContentType/IFileNameToContentTypeMetadata.cs19
-rw-r--r--src/Core/Impl/ContentType/IFilePathRegistryService.cs12
-rw-r--r--src/Core/Impl/ContentType/IFilePathToContentTypeMetadata.cs19
-rw-r--r--src/Core/Impl/ContentType/IFilePathToContentTypeProvider.cs12
-rw-r--r--src/Core/Impl/ContentType/StringToContentTypesMap.cs129
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/AsyncCompletionSessionDataSnapshot.cs76
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/AsyncCompletionSessionInitialDataSnapshot.cs44
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CommitBehavior.cs38
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CommitResult.cs53
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionClosedEventArgs.cs27
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionContext.cs81
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionFilter.cs57
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionFilterEventArgs.cs29
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionFilterWithState.cs82
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionItem.cs169
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionItemEventArgs.cs25
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionItemSelectedEventArgs.cs34
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionItemWithHighlight.cs59
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionItemsWithHighlightEventArgs.cs26
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionPresentationViewModel.cs100
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionPresenterOptions.cs25
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/CompletionTriggeredEventArgs.cs34
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/ComputedCompletionItems.cs92
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/FilteredCompletionModel.cs96
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/InitialSelectionHint.cs31
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/InitialTrigger.cs52
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/InitialTriggerReason.cs35
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/SuggestionItemOptions.cs42
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/UpdateSelectionHint.cs29
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/UpdateTrigger.cs50
-rw-r--r--src/Language/Def/Language/AsyncCompletion/Data/UpdateTriggerReason.cs31
-rw-r--r--src/Language/Def/Language/AsyncCompletion/IAsyncCompletionBroker.cs75
-rw-r--r--src/Language/Def/Language/AsyncCompletion/IAsyncCompletionCommitManager.cs55
-rw-r--r--src/Language/Def/Language/AsyncCompletion/IAsyncCompletionCommitManagerProvider.cs36
-rw-r--r--src/Language/Def/Language/AsyncCompletion/IAsyncCompletionItemManager.cs52
-rw-r--r--src/Language/Def/Language/AsyncCompletion/IAsyncCompletionItemManagerProvider.cs34
-rw-r--r--src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSession.cs106
-rw-r--r--src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSource.cs60
-rw-r--r--src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSourceProvider.cs37
-rw-r--r--src/Language/Def/Language/AsyncCompletion/ICompletionPresenter.cs52
-rw-r--r--src/Language/Def/Language/AsyncCompletion/ICompletionPresenterProvider.cs41
-rw-r--r--src/Language/Def/Language/AsyncCompletion/PredefinedCompletionNames.cs35
-rw-r--r--src/Language/Def/Language/QuickInfo/IAsyncQuickInfoBroker.cs90
-rw-r--r--src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSession.cs81
-rw-r--r--src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSource.cs34
-rw-r--r--src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSourceProvider.cs29
-rw-r--r--src/Language/Def/Language/QuickInfo/IInteractiveQuickInfoContent.cs31
-rw-r--r--src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoBrokerSupport.cs30
-rw-r--r--src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoMetadata.cs56
-rw-r--r--src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoRecalculateSupport.cs15
-rw-r--r--src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoSource.cs22
-rw-r--r--src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoSourcesSupport.cs21
-rw-r--r--src/Language/Def/Language/QuickInfo/QuickInfoItem.cs37
-rw-r--r--src/Language/Def/Language/QuickInfo/QuickInfoItemsCollection.cs35
-rw-r--r--src/Language/Def/Language/QuickInfo/QuickInfoSessionOptions.cs21
-rw-r--r--src/Language/Def/Language/QuickInfo/QuickInfoSessionState.cs28
-rw-r--r--src/Language/Def/Language/QuickInfo/QuickInfoStateChangedEventArgs.cs31
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/AsyncCompletionBroker.cs423
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs1030
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/CaretPreservingEditTransaction.cs (renamed from src/Language/Impl/Language/Completion/CaretPreservingEditTransaction.cs)4
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/CompletionAvailabilityUtility.cs116
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/CompletionCommandHandlers.cs564
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/CompletionModel.cs330
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/CompletionTelemetry.cs478
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/CompletionUtilities.cs92
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/DefaultCompletionItemManager.cs134
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/IAsyncCompletionSessionOperations.cs65
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/IModelComputationCallbackHandler.cs11
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/ImportBucket.cs (renamed from src/Language/Impl/Language/Completion/ImportBucket.cs)4
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/MetadataUtilities.cs (renamed from src/Language/Impl/Language/Completion/CompletionUtilities.cs)70
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/ModelComputation.cs137
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/PrioritizedTaskScheduler.cs (renamed from src/Language/Impl/Language/Completion/PrioritizedTaskScheduler.cs)2
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/SuggestionModeCompletionItemSource.cs39
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/TextUndoTransactionThatRollsBackProperly.cs (renamed from src/Language/Impl/Language/Completion/TextUndoTransactionThatRollsBackProperly.cs)2
-rw-r--r--src/Language/Impl/Language/AsyncCompletion/UndoUtilities.cs (renamed from src/Language/Impl/Language/Completion/UndoUtilities.cs)4
-rw-r--r--src/Language/Impl/Language/Completion/AsyncCompletionBroker.cs382
-rw-r--r--src/Language/Impl/Language/Completion/AsyncCompletionSession.cs776
-rw-r--r--src/Language/Impl/Language/Completion/CompletionCommandHandlers.cs431
-rw-r--r--src/Language/Impl/Language/Completion/CompletionModel.cs461
-rw-r--r--src/Language/Impl/Language/Completion/CompletionTelemetry.cs239
-rw-r--r--src/Language/Impl/Language/Completion/DefaultCompletionService.cs283
-rw-r--r--src/Language/Impl/Language/Completion/ICompletionComputationCallbackHandler.cs14
-rw-r--r--src/Language/Impl/Language/Completion/ModernCompletionFeature.cs35
-rw-r--r--src/Language/Impl/Language/Completion/SuggestionModeCompletionItemSource.cs61
-rw-r--r--src/Language/Impl/Language/Strings.Designer.cs9
-rw-r--r--src/Language/Impl/Language/Strings.resx4
-rw-r--r--src/Microsoft.VisualStudio.Text.Implementation.csproj7
-rw-r--r--src/Text/Def/Internal/TextData/ExtensionMethods.cs2
-rw-r--r--src/Text/Def/Internal/TextData/IStructureSpanningTreeManager.cs60
-rw-r--r--src/Text/Def/Internal/TextData/IStructureSpanningTreeService.cs35
-rw-r--r--src/Text/Def/Internal/TextData/JoinableTaskHelper.cs23
-rw-r--r--src/Text/Def/Internal/TextData/LazyObservableCollection.cs10
-rw-r--r--src/Text/Def/Internal/TextData/TextBufferOperationHelpers.cs2
-rw-r--r--src/Text/Def/Internal/TextData/TrackingSpanTree.cs10
-rw-r--r--src/Text/Def/Internal/TextData/UnicodeWordExtent.cs4
-rw-r--r--src/Text/Def/Internal/TextLogic/IBypassUndoEditTag.cs22
-rw-r--r--src/Text/Def/Internal/TextLogic/IEditOnlyTextUndoPrimitive.cs20
-rw-r--r--src/Text/Def/Internal/TextLogic/IElisionTag.cs4
-rw-r--r--src/Text/Def/Internal/TextLogic/ILoggingServiceInternal.cs2
-rw-r--r--src/Text/Def/Internal/TextLogic/ITextSearchNavigator2.cs2
-rw-r--r--src/Text/Def/Internal/TextLogic/ITextSearchTagger.cs2
-rw-r--r--src/Text/Def/Internal/TextLogic/ITextUndoHistory2.cs31
-rw-r--r--src/Text/Def/Internal/TextLogic/TagAggregatorOptions2.cs4
-rw-r--r--src/Text/Def/Internal/TextLogic/TelemetryComplexProperty.cs23
-rw-r--r--src/Text/Def/Internal/TextUI/DisplayTextRange.cs2
-rw-r--r--src/Text/Def/Internal/TextUI/IViewPrimitives.cs2
-rw-r--r--src/Text/Def/Internal/TextUI/IViewPrimitivesFactoryService.cs6
-rw-r--r--src/Text/Def/Internal/TextUI/LegacySelection.cs (renamed from src/Text/Def/Internal/TextUI/Selection.cs)4
-rw-r--r--src/Text/Def/Internal/TextUI/OverviewFormatDefinitions.cs17
-rw-r--r--src/Text/Def/Internal/TextUI/TextRange.cs2
-rw-r--r--src/Text/Def/Internal/TextUI/TextView.cs2
-rw-r--r--src/Text/Def/Internal/TextUI/ViewRelativePosition2.cs13
-rw-r--r--src/Text/Def/TextData/Differencing/ITokenizedStringList.cs4
-rw-r--r--src/Text/Def/TextData/Differencing/Match.cs2
-rw-r--r--src/Text/Def/TextData/Differencing/StringDifferenceOptions.cs49
-rw-r--r--src/Text/Def/TextData/Document/FileUtilities.cs253
-rw-r--r--src/Text/Def/TextData/Document/TextDocumentFileActionEventArgs.cs2
-rw-r--r--src/Text/Def/TextData/Model/ContentTypeChangedEventArgs.cs34
-rw-r--r--src/Text/Def/TextData/Model/EditOptions.cs28
-rw-r--r--src/Text/Def/TextData/Model/EditTags.cs66
-rw-r--r--src/Text/Def/TextData/Model/NormalizedSnapshotSpanCollection.cs65
-rw-r--r--src/Text/Def/TextData/Model/NormalizedSpanCollection.cs39
-rw-r--r--src/Text/Def/TextData/Model/Projection/ElisionSourceSpansChangedEventArgs.cs4
-rw-r--r--src/Text/Def/TextData/Model/Projection/GraphBufferContentTypeChangedEventArgs.cs6
-rw-r--r--src/Text/Def/TextData/Model/Projection/GraphBuffersChangedEventArgs.cs4
-rw-r--r--src/Text/Def/TextData/Model/Projection/ProjectionSourceBuffersChangedEventArgs.cs4
-rw-r--r--src/Text/Def/TextData/Model/Projection/ProjectionSourceSpansChangedEventArgs.cs4
-rw-r--r--src/Text/Def/TextData/Model/SnapshotPoint.cs54
-rw-r--r--src/Text/Def/TextData/Model/SnapshotSpan.cs26
-rw-r--r--src/Text/Def/TextData/Model/Span.cs6
-rw-r--r--src/Text/Def/TextData/Model/TextBufferCreatedEventArgs.cs2
-rw-r--r--src/Text/Def/TextData/Model/TextContentChangingEventArgs.cs2
-rw-r--r--src/Text/Def/TextData/Model/TextImageLine.cs10
-rw-r--r--src/Text/Def/TextData/Model/TextSnapshotChangedEventArgs.cs4
-rw-r--r--src/Text/Def/TextData/Model/TextSnapshotToTextReader.cs10
-rw-r--r--src/Text/Def/TextData/Model/Tracking.cs81
-rw-r--r--src/Text/Def/TextData/Model/VersionedPosition.cs7
-rw-r--r--src/Text/Def/TextData/Model/VersionedSpan.cs6
-rw-r--r--src/Text/Def/TextData/TextData.csproj5
-rw-r--r--src/Text/Def/TextLogic/Classification/ClassificationSpan.cs2
-rw-r--r--src/Text/Def/TextLogic/Classification/ClassificationTypeAttribute.cs4
-rw-r--r--src/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs51
-rw-r--r--src/Text/Def/TextLogic/EditorOptions/DeferCreationAttribute.cs2
-rw-r--r--src/Text/Def/TextLogic/EditorOptions/EditorOptionDefinition.cs12
-rw-r--r--src/Text/Def/TextLogic/EditorOptions/EditorOptionKey.cs16
-rw-r--r--src/Text/Def/TextLogic/Find/FindData.cs51
-rw-r--r--src/Text/Def/TextLogic/Find/ITextSearchService2.cs14
-rw-r--r--src/Text/Def/TextLogic/Navigation/TextExtent.cs2
-rw-r--r--src/Text/Def/TextLogic/PatternMatching/PatternMatch.cs4
-rw-r--r--src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationOptions.cs9
-rw-r--r--src/Text/Def/TextLogic/Tagging/BatchedTagsChangedEventArgs.cs2
-rw-r--r--src/Text/Def/TextLogic/Tagging/ITag.cs4
-rw-r--r--src/Text/Def/TextLogic/Tagging/ITagAggregator.cs2
-rw-r--r--src/Text/Def/TextLogic/Tagging/MappingTagSpan.cs4
-rw-r--r--src/Text/Def/TextLogic/Tagging/SimpleTagger.cs10
-rw-r--r--src/Text/Def/TextLogic/Tagging/TagSpan.cs2
-rw-r--r--src/Text/Def/TextLogic/Tagging/TagTypeAttribute.cs4
-rw-r--r--src/Text/Def/TextLogic/Tagging/TagsChangedEventArgs.cs2
-rw-r--r--src/Text/Def/TextLogic/Tagging/TrackingTagSpan.cs4
-rw-r--r--src/Text/Def/TextLogic/Tags/ClassificationTag.cs2
-rw-r--r--src/Text/Def/TextLogic/Tags/UrlTag.cs2
-rw-r--r--src/Text/Def/TextLogic/TextLogic.csproj4
-rw-r--r--src/Text/Def/TextLogic/TextModel/VirtualSnapshotPoint.cs12
-rw-r--r--src/Text/Def/TextLogic/TextModel/VirtualSnapshotSpan.cs8
-rw-r--r--src/Text/Def/TextUI/Adornments/ToolTipService/IViewElementFactoryService.cs2
-rw-r--r--src/Text/Def/TextUI/Adornments/ToolTipService/ToolTipParameters.cs2
-rw-r--r--src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ContainerElementStyle.cs14
-rw-r--r--src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ImageElement.cs24
-rw-r--r--src/Text/Def/TextUI/Editor/CaretPosition.cs4
-rw-r--r--src/Text/Def/TextUI/Editor/ITextView2.cs (renamed from src/Text/Def/Internal/TextUI/ITextView2.cs)30
-rw-r--r--src/Text/Def/TextUI/Editor/ITextViewRoleSet.cs2
-rw-r--r--src/Text/Def/TextUI/Editor/MarginContainerAttribute.cs2
-rw-r--r--src/Text/Def/TextUI/Editor/MouseHoverEventArgs.cs6
-rw-r--r--src/Text/Def/TextUI/Editor/ReplacesAttribute.cs4
-rw-r--r--src/Text/Def/TextUI/Editor/TextViewCreatedEventArgs.cs2
-rw-r--r--src/Text/Def/TextUI/Editor/TextViewExtensions.cs90
-rw-r--r--src/Text/Def/TextUI/Editor/TextViewLayoutChangedEventArgs.cs8
-rw-r--r--src/Text/Def/TextUI/Editor/TextViewRoleAttribute.cs4
-rw-r--r--src/Text/Def/TextUI/Editor/ViewRelativePosition.cs4
-rw-r--r--src/Text/Def/TextUI/Editor/ViewState.cs2
-rw-r--r--src/Text/Def/TextUI/EditorOptions/ViewOptions.cs108
-rw-r--r--src/Text/Def/TextUI/Find/IncrementalSearchResult.cs4
-rw-r--r--src/Text/Def/TextUI/Formatting/LineTransform.cs10
-rw-r--r--src/Text/Def/TextUI/Formatting/TextAndAdornmentSequenceChangedEventArgs.cs2
-rw-r--r--src/Text/Def/TextUI/Formatting/TextBounds.cs14
-rw-r--r--src/Text/Def/TextUI/MultiCaret/AbstractSelectionPresentationProperties.cs49
-rw-r--r--src/Text/Def/TextUI/MultiCaret/IMultiSelectionBroker.cs292
-rw-r--r--src/Text/Def/TextUI/MultiCaret/ISelectionTransformer.cs82
-rw-r--r--src/Text/Def/TextUI/MultiCaret/PredefinedSelectionTransformations.cs158
-rw-r--r--src/Text/Def/TextUI/MultiCaret/Selection.cs313
-rw-r--r--src/Text/Def/TextUI/Operations/IUndoMetadataEditTag.cs28
-rw-r--r--src/Text/Def/TextUI/Strings.Designer.cs81
-rw-r--r--src/Text/Def/TextUI/Strings.resx126
-rw-r--r--src/Text/Def/TextUI/Tags/ErrorTag.cs2
-rw-r--r--src/Text/Def/TextUI/Tags/TextMarkerTag.cs2
-rw-r--r--src/Text/Def/TextUI/TextUI.csproj5
-rw-r--r--src/Text/Def/TextUI/Utilities/AbstractUIThreadOperationContext.cs7
-rw-r--r--src/Text/Def/TextUI/Utilities/IUIThreadOperationScope.cs2
-rw-r--r--src/Text/Def/TextUI/Utilities/UIThreadOperationStatus.cs2
-rw-r--r--src/Text/Impl/BraceCompletion/BraceCompletionAggregator.cs2
-rw-r--r--src/Text/Impl/BraceCompletion/BraceCompletionAggregatorFactory.cs4
-rw-r--r--src/Text/Impl/BraceCompletion/BraceCompletionDefaultSession.cs5
-rw-r--r--src/Text/Impl/BraceCompletion/BraceCompletionEnabledOption.cs (renamed from src/Text/Impl/BraceCompletion/Options.cs)4
-rw-r--r--src/Text/Impl/BraceCompletion/BraceCompletionManager.cs7
-rw-r--r--src/Text/Impl/BraceCompletion/BraceCompletionStack.cs5
-rw-r--r--src/Text/Impl/ClassificationAggregator/ClassifierAggregator.cs14
-rw-r--r--src/Text/Impl/ClassificationAggregator/ClassifierTagger.cs2
-rw-r--r--src/Text/Impl/ClassificationAggregator/ProjectionWorkaround.cs91
-rw-r--r--src/Text/Impl/ClassificationType/ClassificationTypeImpl.cs2
-rw-r--r--src/Text/Impl/ClassificationType/ClassificationTypeRegistryService.cs12
-rw-r--r--src/Text/Impl/Commanding/EditorCommandHandlerService.cs25
-rw-r--r--src/Text/Impl/DifferenceAlgorithm/DefaultTextDifferencingService.cs12
-rw-r--r--src/Text/Impl/DifferenceAlgorithm/HierarchicalDifferenceCollection.cs6
-rw-r--r--src/Text/Impl/DifferenceAlgorithm/MaximalSubsequenceAlgorithm.cs6
-rw-r--r--src/Text/Impl/DifferenceAlgorithm/SnapshotLineList.cs4
-rw-r--r--src/Text/Impl/DifferenceAlgorithm/TFS/DiffFinder.cs11
-rw-r--r--src/Text/Impl/DifferenceAlgorithm/TFS/LCSDiff.cs20
-rw-r--r--src/Text/Impl/DifferenceAlgorithm/TokenizedStringList.cs2
-rw-r--r--src/Text/Impl/EditorOperations/AfterTextBufferChangeUndoPrimitive.cs136
-rw-r--r--src/Text/Impl/EditorOperations/BeforeTextBufferChangeUndoPrimitive.cs166
-rw-r--r--src/Text/Impl/EditorOperations/CollapsedMoveUndoPrimitive.cs6
-rw-r--r--src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionCommandHandler.cs68
-rw-r--r--src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionImplementation.cs134
-rw-r--r--src/Text/Impl/EditorOperations/Commands/ExpandContractSelectionOptionDefinitions.cs25
-rw-r--r--src/Text/Impl/EditorOperations/Commands/NavigateToNextIssueCommandHandler.cs156
-rw-r--r--src/Text/Impl/EditorOperations/EditorOperations.cs1042
-rw-r--r--src/Text/Impl/EditorOperations/EditorOperationsFactoryService.cs5
-rw-r--r--src/Text/Impl/EditorOperations/Strings.Designer.cs18
-rw-r--r--src/Text/Impl/EditorOperations/Strings.resx6
-rw-r--r--src/Text/Impl/EditorOperations/TextTransactionMergePolicy.cs12
-rw-r--r--src/Text/Impl/EditorOptions/EditorOptions.cs9
-rw-r--r--src/Text/Impl/EditorOptions/EditorOptionsFactoryService.cs4
-rw-r--r--src/Text/Impl/EditorPrimitives/DefaultBufferPrimitive.cs16
-rw-r--r--src/Text/Impl/EditorPrimitives/DefaultDisplayTextPointPrimitive.cs4
-rw-r--r--src/Text/Impl/EditorPrimitives/DefaultSelectionPrimitive.cs2
-rw-r--r--src/Text/Impl/EditorPrimitives/DefaultTextPointPrimitive.cs22
-rw-r--r--src/Text/Impl/EditorPrimitives/DefaultTextRangePrimitive.cs4
-rw-r--r--src/Text/Impl/EditorPrimitives/DefaultTextViewPrimitive.cs5
-rw-r--r--src/Text/Impl/EditorPrimitives/DefaultViewPrimitivesFactoryService.cs2
-rw-r--r--src/Text/Impl/EditorPrimitives/ViewPrimitives.cs4
-rw-r--r--src/Text/Impl/Navigation/TextStructureNavigatorSelectorService.cs6
-rw-r--r--src/Text/Impl/Outlining/OutliningManager.cs18
-rw-r--r--src/Text/Impl/Outlining/OutliningManagerService.cs2
-rw-r--r--src/Text/Impl/PatternMatching/AllLowerCamelCaseMatcher.cs17
-rw-r--r--src/Text/Impl/PatternMatching/ArraySlice.cs8
-rw-r--r--src/Text/Impl/PatternMatching/CamelCaseResult.cs2
-rw-r--r--src/Text/Impl/PatternMatching/ContainerPatternMatcher.cs2
-rw-r--r--src/Text/Impl/PatternMatching/EditDistance.cs7
-rw-r--r--src/Text/Impl/PatternMatching/PatternMatcher.cs8
-rw-r--r--src/Text/Impl/PatternMatching/WordSimilarityChecker.cs2
-rw-r--r--src/Text/Impl/StandaloneUndo/AutoEnclose.cs2
-rw-r--r--src/Text/Impl/StandaloneUndo/CatchOperationsFromHistoryForDelegatedPrimitive.cs2
-rw-r--r--src/Text/Impl/StandaloneUndo/DelegatedUndoPrimitiveImpl.cs4
-rw-r--r--src/Text/Impl/StandaloneUndo/UndoHistoryImpl.cs39
-rw-r--r--src/Text/Impl/StandaloneUndo/UndoHistoryRegistryImpl.cs20
-rw-r--r--src/Text/Impl/StandaloneUndo/UndoTransactionImpl.cs52
-rw-r--r--src/Text/Impl/TagAggregator/TagAggregator.cs4
-rw-r--r--src/Text/Impl/TagAggregator/TagAggregatorFactoryService.cs4
-rw-r--r--src/Text/Impl/TextBufferUndoManager/Strings.Designer.cs7
-rw-r--r--src/Text/Impl/TextBufferUndoManager/TextBufferChangeUndoPrimitive.cs53
-rw-r--r--src/Text/Impl/TextBufferUndoManager/TextBufferUndoManager.cs245
-rw-r--r--src/Text/Impl/TextBufferUndoManager/TextBufferUndoManagerProvider.cs16
-rw-r--r--src/Text/Impl/TextModel/BaseBuffer.cs38
-rw-r--r--src/Text/Impl/TextModel/BaseSnapshot.cs7
-rw-r--r--src/Text/Impl/TextModel/BufferFactoryService.cs28
-rw-r--r--src/Text/Impl/TextModel/EncodedStreamReader.cs2
-rw-r--r--src/Text/Impl/TextModel/FileNameKey.cs52
-rw-r--r--src/Text/Impl/TextModel/FileUtilities.cs25
-rw-r--r--src/Text/Impl/TextModel/ForwardFidelityCustomTrackingSpan.cs2
-rw-r--r--src/Text/Impl/TextModel/HighFidelityTrackingPoint.cs2
-rw-r--r--src/Text/Impl/TextModel/HighFidelityTrackingSpan.cs2
-rw-r--r--src/Text/Impl/TextModel/MappingPoint.cs14
-rw-r--r--src/Text/Impl/TextModel/MappingSpan.cs15
-rw-r--r--src/Text/Impl/TextModel/NativeMethods.cs28
-rw-r--r--src/Text/Impl/TextModel/NormalizedTextChangeCollection.cs13
-rw-r--r--src/Text/Impl/TextModel/PersistentSpan.cs184
-rw-r--r--src/Text/Impl/TextModel/PersistentSpanFactory.cs256
-rw-r--r--src/Text/Impl/TextModel/PersistentSpanSet.cs125
-rw-r--r--src/Text/Impl/TextModel/Projection/BaseProjectionBuffer.cs7
-rw-r--r--src/Text/Impl/TextModel/Projection/BufferGraph.cs58
-rw-r--r--src/Text/Impl/TextModel/Projection/BufferGraphFactoryService.cs2
-rw-r--r--src/Text/Impl/TextModel/Projection/ElisionBuffer.cs9
-rw-r--r--src/Text/Impl/TextModel/Projection/ElisionSnapshot.cs20
-rw-r--r--src/Text/Impl/TextModel/Projection/ProjectionBuffer.cs15
-rw-r--r--src/Text/Impl/TextModel/Projection/ProjectionSnapshot.cs25
-rw-r--r--src/Text/Impl/TextModel/Projection/ProjectionSpanToChangeConverter.cs10
-rw-r--r--src/Text/Impl/TextModel/Storage/CharStream.cs4
-rw-r--r--src/Text/Impl/TextModel/Storage/ILineBreaks.cs12
-rw-r--r--src/Text/Impl/TextModel/Storage/LineBreakManager.cs199
-rw-r--r--src/Text/Impl/TextModel/Storage/TextImageLoader.cs22
-rw-r--r--src/Text/Impl/TextModel/StringRebuilder/BinaryStringRebuilder.cs18
-rw-r--r--src/Text/Impl/TextModel/StringRebuilder/StringRebuilder.cs16
-rw-r--r--src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForChars.cs2
-rw-r--r--src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForCompressedChars.cs2
-rw-r--r--src/Text/Impl/TextModel/StringRebuilder/StringRebuilderForString.cs2
-rw-r--r--src/Text/Impl/TextModel/StringRebuilder/UnaryStringRebuilder.cs24
-rw-r--r--src/Text/Impl/TextModel/TextChange.cs8
-rw-r--r--src/Text/Impl/TextModel/TextDocument.cs24
-rw-r--r--src/Text/Impl/TextModel/TextDocumentFactoryService.cs12
-rw-r--r--src/Text/Impl/TextModel/TextImageVersion.cs4
-rw-r--r--src/Text/Impl/TextModel/TextVersion.cs11
-rw-r--r--src/Text/Impl/TextModel/TrackingPoint.cs10
-rw-r--r--src/Text/Impl/TextModel/TrackingSpan.cs10
-rw-r--r--src/Text/Impl/TextModel/TrivialNormalizedTextChangeCollection.cs6
-rw-r--r--src/Text/Impl/TextSearch/BackgroundSearch.cs3
-rw-r--r--src/Text/Impl/TextSearch/TextSearchNavigatorFactoryService.cs2
-rw-r--r--src/Text/Impl/TextSearch/TextSearchService.cs54
-rw-r--r--src/Text/Impl/TextSearch/TextSearchTagger.cs6
-rw-r--r--src/Text/Impl/TextSearch/TextSearchTaggerFactoryService.cs2
-rw-r--r--src/Text/Util/TextDataUtil/BufferTracker.cs2
-rw-r--r--src/Text/Util/TextDataUtil/FrugalList.cs21
-rw-r--r--src/Text/Util/TextDataUtil/GuardedOperations.cs118
-rw-r--r--src/Text/Util/TextDataUtil/ListUtilities.cs7
-rw-r--r--src/Text/Util/TextDataUtil/MappingPointSnapshot.cs8
-rw-r--r--src/Text/Util/TextDataUtil/MappingSpanSnapshot.cs7
-rw-r--r--src/Text/Util/TextDataUtil/PooledObjects/ObjectPool`1.cs2
-rw-r--r--src/Text/Util/TextDataUtil/ProjectionSpanDiffer.cs68
-rw-r--r--src/Text/Util/TextDataUtil/ProjectionSpanDifference.cs43
-rw-r--r--src/Text/Util/TextDataUtil/TextModelOptions.cs2
-rw-r--r--src/Text/Util/TextDataUtil/TextUtilities.cs10
-rw-r--r--src/Text/Util/TextUIUtil/VacuousTextViewModel.cs4
352 files changed, 11042 insertions, 6204 deletions
diff --git a/src/Core/Def/BaseUtility/BaseDefinitionAttribute.cs b/src/Core/Def/BaseUtility/BaseDefinitionAttribute.cs
index ba807c3..538f6bb 100644
--- a/src/Core/Def/BaseUtility/BaseDefinitionAttribute.cs
+++ b/src/Core/Def/BaseUtility/BaseDefinitionAttribute.cs
@@ -22,7 +22,7 @@ namespace Microsoft.VisualStudio.Utilities
{
if (string.IsNullOrEmpty(name))
{
- throw new ArgumentNullException("name");
+ throw new ArgumentNullException(nameof(name));
}
baseDefinition = name;
}
diff --git a/src/Core/Def/BaseUtility/DefaultOrderings.cs b/src/Core/Def/BaseUtility/DefaultOrderings.cs
new file mode 100644
index 0000000..ebbbc29
--- /dev/null
+++ b/src/Core/Def/BaseUtility/DefaultOrderings.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Static class defining some default placeholders for the ordering attributes.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Orderable items that do not explicitly indicate they are before <see cref="DefaultOrderings.Lowest"/> have an implicit constraint
+ /// that they are after <see cref="DefaultOrderings.Lowest"/>.
+ /// </para>
+ /// <para>
+ /// Orderable items that do not explicitly indicate they are after <see cref="DefaultOrderings.Highest"/> have an implicit constraint
+ /// that they are before <see cref="DefaultOrderings.Highest"/>.
+ /// </para>
+ /// </remarks>
+ public static class DefaultOrderings
+ {
+ public const string Lowest = "Lowest Priority";
+ public const string Low = "Low Priority";
+ public const string Default = "Default Priority";
+ public const string High = "High Priority";
+ public const string Highest = "Highest Priority";
+ }
+}
diff --git a/src/Core/Def/BaseUtility/DisplayNameAttribute.cs b/src/Core/Def/BaseUtility/DisplayNameAttribute.cs
index 5099fa8..f5a0644 100644
--- a/src/Core/Def/BaseUtility/DisplayNameAttribute.cs
+++ b/src/Core/Def/BaseUtility/DisplayNameAttribute.cs
@@ -12,9 +12,9 @@ namespace Microsoft.VisualStudio.Utilities
/// <remarks>
/// This attribute should be localized wherever it is used.
/// </remarks>
+ [Obsolete("Use " + nameof(LocalizedNameAttribute) + " instead.")]
public sealed class DisplayNameAttribute : SingletonBaseMetadataAttribute
{
- private string displayName;
/// <summary>
/// Initializes a new instance of <see cref="DisplayNameAttribute"/>.
@@ -22,22 +22,12 @@ namespace Microsoft.VisualStudio.Utilities
/// <param name="displayName">The display name of an editor component part.</param>
public DisplayNameAttribute(string displayName)
{
- if (displayName == null)
- {
- throw new ArgumentNullException("displayName");
- }
- this.displayName = displayName;
+ this.DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
}
/// <summary>
/// Gets the display name of an editor component part.
/// </summary>
- public string DisplayName
- {
- get
- {
- return this.displayName;
- }
- }
+ public string DisplayName { get; }
}
-} \ No newline at end of file
+}
diff --git a/src/Core/Def/BaseUtility/IGuardedOperations.cs b/src/Core/Def/BaseUtility/IGuardedOperations.cs
index 839018f..1a94a38 100644
--- a/src/Core/Def/BaseUtility/IGuardedOperations.cs
+++ b/src/Core/Def/BaseUtility/IGuardedOperations.cs
@@ -10,7 +10,7 @@ using Microsoft.VisualStudio.Threading;
namespace Microsoft.VisualStudio.Utilities
{
/// <summary>
- /// Operations that guard calls to extensions code and log errors.
+ /// Operations that guard calls to extensions code, track performance and log errors.
/// </summary>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
@@ -19,6 +19,7 @@ namespace Microsoft.VisualStudio.Utilities
/// <summary>
/// Makes a guarded call to an extension point.
/// </summary>
+ /// <param name="call">Delegate that calls the extension point.</param>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
void CallExtensionPoint(Action call);
@@ -26,6 +27,9 @@ namespace Microsoft.VisualStudio.Utilities
/// <summary>
/// Makes a guarded call to an extension point.
/// </summary>
+ /// <param name="errorSource">Reference to the extension object or event handler that may throw an exception.
+ /// Used for tracking performance and errors.</param>
+ /// <param name="call">Delegate that calls the extension point.</param>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
void CallExtensionPoint(object errorSource, Action call);
@@ -33,6 +37,21 @@ namespace Microsoft.VisualStudio.Utilities
/// <summary>
/// Makes a guarded call to an extension point.
/// </summary>
+ /// <param name="errorSource">Reference to the extension object or event handler that may throw an exception.
+ /// Used for tracking performance and errors.</param>
+ /// <param name="call">Delegate that calls the extension point.</param>
+ /// <param name="exceptionGuardFilter">Determines which exceptions should be guarded against.
+ /// An exception gets handled only if <paramref name="exceptionGuardFilter"/> returns <c>true</c>.</param>
+ /// <remarks>This class supports the Visual Studio
+ /// infrastructure and in general is not intended to be used directly from your code.</remarks>
+ void CallExtensionPoint(object errorSource, Action call, Predicate<Exception> exceptionGuardFilter);
+
+ /// <summary>
+ /// Makes a guarded call to an extension point.
+ /// </summary>
+ /// <param name="call">Delegate that calls the extension point.</param>
+ /// <param name="valueOnThrow">The value returned if the delegate call failed.</param>
+ /// <returns>The result of the <paramref name="call"/> or <paramref name="valueOnThrow"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
T CallExtensionPoint<T>(Func<T> call, T valueOnThrow);
@@ -40,6 +59,11 @@ namespace Microsoft.VisualStudio.Utilities
/// <summary>
/// Makes a guarded call to an extension point.
/// </summary>
+ /// <param name="errorSource">Reference to the extension object or event handler that may throw an exception.
+ /// Used for tracking performance and errors.</param>
+ /// <param name="call">Delegate that calls the extension point.</param>
+ /// <param name="valueOnThrow">The value returned if the delegate call failed.</param>
+ /// <returns>The result of the <paramref name="call"/> or <paramref name="valueOnThrow"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
T CallExtensionPoint<T>(object errorSource, Func<T> call, T valueOnThrow);
@@ -47,7 +71,7 @@ namespace Microsoft.VisualStudio.Utilities
/// <summary>
/// Makes a guarded call to an async extension point.
/// </summary>
- /// <param name="asyncAction">The extension point to be called.</param>
+ /// <param name="asyncCall">Delegate that calls the extension point.</param>
/// <returns>A <see cref="Task"/> that asynchronously executes the <paramref name="asyncAction"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
@@ -56,7 +80,9 @@ namespace Microsoft.VisualStudio.Utilities
/// <summary>
/// Makes a guarded call to an async extension point.
/// </summary>
- /// <param name="asyncAction">The extension point to be called.</param>
+ /// <param name="errorSource">Reference to the extension object or event handler that may throw an exception.
+ /// Used for tracking performance and errors.</param>
+ /// <param name="asyncCall">Delegate that calls the extension point.</param>
/// <returns>A <see cref="Task"/> that asynchronously executes the <paramref name="asyncAction"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
@@ -66,9 +92,9 @@ namespace Microsoft.VisualStudio.Utilities
/// Makes a guarded call to an async extension point.
/// </summary>
/// <typeparam name="T">The type of the value returned from the <paramref name="asyncCall"/>.</typeparam>
- /// <param name="asyncCall">The extension point to be called.</param>
- /// <param name="valueOnThrow">The value returned if call failed.</param>
- /// <returns>A <see cref="Task{T}"/> that asynchronously executes the <paramref name="asyncCall"/>.</returns>
+ /// <param name="asyncCall">Delegate that calls the extension point.</param>
+ /// <param name="valueOnThrow">The value returned if the delegate call failed.</param>
+ /// <returns>A <see cref="Task{T}"/> that asynchronously executes the <paramref name="asyncCall"/> or provides <paramref name="valueOnThrow"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
Task<T> CallExtensionPointAsync<T>(Func<Task<T>> asyncCall, T valueOnThrow);
@@ -77,16 +103,23 @@ namespace Microsoft.VisualStudio.Utilities
/// Makes a guarded call to an async extension point.
/// </summary>
/// <typeparam name="T">The type of the value returned from the <paramref name="asyncCall"/>.</typeparam>
- /// <param name="asyncCall">The extension point to be called.</param>
- /// <param name="valueOnThrow">The value returned if call failed.</param>
- /// <returns>A <see cref="Task{T}"/> that asynchronously executes the <paramref name="asyncCall"/>.</returns>
+ /// <param name="errorSource">Reference to the extension object or event handler that may throw an exception.
+ /// Used for tracking performance and errors.</param>
+ /// <param name="asyncCall">Delegate that calls the extension point.</param>
+ /// <param name="valueOnThrow">The value returned if the delegate call failed.</param>
+ /// <returns>A <see cref="Task{T}"/> that asynchronously executes the <paramref name="asyncCall"/> or provides <paramref name="valueOnThrow"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
Task<T> CallExtensionPointAsync<T>(object errorSource, Func<Task<T>> asyncCall, T valueOnThrow);
/// <summary>
- /// Selects eligible extension factories.
+ /// Selects extension factories whose declared content type metadata
+ /// matches the provided target content type, taking into account that extension factory
+ /// may be disabled by a Replace attribute on another factory.
/// </summary>
+ /// <param name="lazyFactories">Lazy references that will be evaluated.</param>
+ /// <param name="dataContentType">Target content type.</param>
+ /// <param name="contentTypeRegistryService">Instance of <see cref="IContentTypeRegistryService"/> which orders content types.</param>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
IEnumerable<Lazy<TExtensionFactory, TMetadataView>> FindEligibleFactories<TExtensionFactory, TMetadataView>(IEnumerable<Lazy<TExtensionFactory, TMetadataView>> lazyFactories, IContentType dataContentType, IContentTypeRegistryService contentTypeRegistryService)
@@ -96,6 +129,8 @@ namespace Microsoft.VisualStudio.Utilities
/// <summary>
/// Handles an exception occured in a call to an extension point.
/// </summary>
+ /// <param name="errorSource">Reference to the extension object or event handler that threw the exception</param>
+ /// <param name="e">Exception to handle</param>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
void HandleException(object errorSource, Exception e);
@@ -103,6 +138,9 @@ namespace Microsoft.VisualStudio.Utilities
/// <summary>
/// Safely instantiates an extension point.
/// </summary>
+ /// <param name="errorSource">Reference to the object that will be blamed for potential exceptions.</param>
+ /// <param name="provider">Lazy reference that will be initialized.</param>
+ /// <returns>Initialized instance stored in <paramref name="provider"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
TExtension InstantiateExtension<TExtension>(object errorSource, Lazy<TExtension> provider);
@@ -110,27 +148,47 @@ namespace Microsoft.VisualStudio.Utilities
/// <summary>
/// Safely instantiates an extension point.
/// </summary>
+ /// <param name="errorSource">Reference to the object that will be blamed for potential exceptions.</param>
+ /// <param name="provider">Lazy reference that will be initialized.</param>
+ /// <returns>Initialized instance stored in <paramref name="provider"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
TExtension InstantiateExtension<TExtension, TMetadata>(object errorSource, Lazy<TExtension, TMetadata> provider);
/// <summary>
- /// Safely instantiates an extension point.
+ /// Safely invokes a delegate on the extension point.
/// </summary>
+ /// <param name="errorSource">Reference to the object that will be blamed for potential exceptions.</param>
+ /// <param name="provider">Lazy reference that will be initialized.</param>
+ /// <param name="getter">Delegate which constructs an instance of the extension from its <paramref name="provider"/>.</param>
+ /// <returns>The result of <paramref name="getter"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
TExtensionInstance InstantiateExtension<TExtension, TMetadata, TExtensionInstance>(object errorSource, Lazy<TExtension, TMetadata> provider, Func<TExtension, TExtensionInstance> getter);
/// <summary>
- /// Safely invokes best matching extension factory.
+ /// Safely instantiates an extension point whose declared content type metadata
+ /// is the closest match to the provided target content type.
/// </summary>
+ /// <param name="providerHandles">Lazy references that will be evaluated.</param>
+ /// <param name="dataContentType">Target content type.</param>
+ /// <param name="contentTypeRegistryService">Instance of <see cref="IContentTypeRegistryService"/> which orders content types.</param>
+ /// <param name="errorSource">Reference to the object that will be blamed for potential exceptions.</param>
+ /// <returns>The selected element of <paramref name="providerHandles"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
TExtension InvokeBestMatchingFactory<TExtension, TMetadataView>(IList<Lazy<TExtension, TMetadataView>> providerHandles, IContentType dataContentType, IContentTypeRegistryService contentTypeRegistryService, object errorSource) where TMetadataView : IContentTypeMetadata;
/// <summary>
- /// Safely invokes best matching extension factory.
+ /// Safely invokes a delegate on the extension factory whose declared content type metadata
+ /// is the best match to the provided target content type.
/// </summary>
+ /// <param name="providerHandles">Lazy references that will be evaluated.</param>
+ /// <param name="dataContentType">Target content type.</param>
+ /// <param name="getter">Delegate which constructs an instance of the extension from the best matching element of <paramref name="providerHandles"/>.</param>
+ /// <param name="contentTypeRegistryService">Instance of <see cref="IContentTypeRegistryService"/> which orders content types.</param>
+ /// <param name="errorSource">Reference to the object that will be blamed for potential exceptions.</param>
+ /// <returns>The result of <paramref name="getter"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
TExtensionInstance InvokeBestMatchingFactory<TExtensionFactory, TExtensionInstance, TMetadataView>(IList<Lazy<TExtensionFactory, TMetadataView>> providerHandles, IContentType dataContentType, Func<TExtensionFactory, TExtensionInstance> getter, IContentTypeRegistryService contentTypeRegistryService, object errorSource)
@@ -138,8 +196,16 @@ namespace Microsoft.VisualStudio.Utilities
where TMetadataView : IContentTypeMetadata;
/// <summary>
- /// Safely invokes all eligible extension factories.
+ /// Safely invokes a delegate on all extension factories whose declared content type metadata
+ /// matches the provided target content type, taking into account that extension factory
+ /// may be disabled by a Replace attribute on another factory.
/// </summary>
+ /// <param name="lazyFactories">Lazy references that will be evaluated.</param>
+ /// <param name="getter">Delegate which constructs an instance of the extension from each element of <paramref name="lazyFactories"/>.</param>
+ /// <param name="dataContentType">Target content type.</param>
+ /// <param name="contentTypeRegistryService">Instance of <see cref="IContentTypeRegistryService"/> which orders content types.</param>
+ /// <param name="errorSource">Reference to the object that will be blamed for potential exceptions.</param>
+ /// <returns>The list of results of <paramref name="getter"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
List<TExtensionInstance> InvokeEligibleFactories<TExtensionInstance, TExtensionFactory, TMetadataView>(IEnumerable<Lazy<TExtensionFactory, TMetadataView>> lazyFactories, Func<TExtensionFactory, TExtensionInstance> getter, IContentType dataContentType, IContentTypeRegistryService contentTypeRegistryService, object errorSource)
@@ -148,8 +214,14 @@ namespace Microsoft.VisualStudio.Utilities
where TMetadataView : INamedContentTypeMetadata;
/// <summary>
- /// Safely invokes all matching extension factories.
+ /// Safely invokes a delegate on all extension factories whose declared content type metadata
+ /// matches the provided target content type.
/// </summary>
+ /// <param name="lazyFactories">Lazy references that will be evaluated.</param>
+ /// <param name="getter">Delegate which constructs an instance of the extension from each element of <paramref name="lazyFactories"/>.</param>
+ /// <param name="dataContentType">Target content type.</param>
+ /// <param name="errorSource">Reference to the object that will be blamed for potential exceptions.</param>
+ /// <returns>The list of results of <paramref name="getter"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
List<TExtensionInstance> InvokeMatchingFactories<TExtensionInstance, TExtensionFactory, TMetadataView>(IEnumerable<Lazy<TExtensionFactory, TMetadataView>> lazyFactories, Func<TExtensionFactory, TExtensionInstance> getter, IContentType dataContentType, object errorSource)
@@ -157,25 +229,39 @@ namespace Microsoft.VisualStudio.Utilities
where TExtensionFactory : class
where TMetadataView : IContentTypeMetadata;
+#pragma warning disable CA1030 // Use events where appropriate
/// <summary>
- /// Safely raises an event.
+ /// Safely raises an event with empty <see cref="EventArgs"/>.
+ /// Errors are tracked per sender, performance is tracked per handler.
/// </summary>
+ /// <param name="sender">Reference to the sender of the event. Tracks errors.</param>
+ /// <param name="eventHandlers">Event to raise. Each handler tracks performance.</param>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
void RaiseEvent(object sender, EventHandler eventHandlers);
/// <summary>
- /// Safely raises an event.
+ /// Safely raises an event with specified <paramref name="args"/>.
+ /// Errors are tracked per sender, performance is tracked per handler.
/// </summary>
+ /// <param name="sender">Reference to the sender of the event. Tracks errors.</param>
+ /// <param name="eventHandlers">Event to raise. Each handler tracks performance.</param>
+ /// <param name="args">Event data.</param>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
void RaiseEvent<TArgs>(object sender, EventHandler<TArgs> eventHandlers, TArgs args) where TArgs : EventArgs;
/// <summary>
- /// Safely raises an event on a background thread.
+ /// Safely raises an event on a background thread with specified <paramref name="args"/>.
+ /// Errors are tracked per sender, performance is tracked per handler.
/// </summary>
+ /// <param name="sender">Reference to the sender of the event. Tracks errors.</param>
+ /// <param name="eventHandlers">Event to raise. Each handler tracks performance.</param>
+ /// <param name="args">Event data.</param>
+ /// <returns>A <see cref="Task"/> that asynchronously executes the <paramref name="eventHandlers"/>.</returns>
/// <remarks>This class supports the Visual Studio
/// infrastructure and in general is not intended to be used directly from your code.</remarks>
Task RaiseEventOnBackgroundAsync<TArgs>(object sender, AsyncEventHandler<TArgs> eventHandlers, TArgs args) where TArgs : EventArgs;
+#pragma warning restore CA1030 // Use events where appropriate
}
}
diff --git a/src/Core/Def/BaseUtility/ITelemetryIdProvider.cs b/src/Core/Def/BaseUtility/ITelemetryIdProvider.cs
index 73fe1ba..014f3f1 100644
--- a/src/Core/Def/BaseUtility/ITelemetryIdProvider.cs
+++ b/src/Core/Def/BaseUtility/ITelemetryIdProvider.cs
@@ -6,14 +6,14 @@ namespace Microsoft.VisualStudio.Utilities
{
/// <summary>
/// Represents an object that can provide a unique ID for telemetry purposes.
- /// <typeparam name="Tid">Type of the telemetry ID.</typeparam>
+ /// <typeparam name="TId">Type of the telemetry ID.</typeparam>
/// </summary>
- public interface ITelemetryIdProvider<Tid>
+ public interface ITelemetryIdProvider<TId>
{
/// <summary>
/// Tries to get a unique ID for telemetry purposes.
/// </summary>
/// <returns><c>true</c> if a unique telemetry ID was returned, <c>false</c> if this object refuses to participate in telemetry logging.</returns>
- bool TryGetTelemetryId(out Tid telemetryId);
+ bool TryGetTelemetryId(out TId telemetryId);
}
}
diff --git a/src/Core/Def/BaseUtility/LocalizedNameAttribute.cs b/src/Core/Def/BaseUtility/LocalizedNameAttribute.cs
new file mode 100644
index 0000000..f199594
--- /dev/null
+++ b/src/Core/Def/BaseUtility/LocalizedNameAttribute.cs
@@ -0,0 +1,78 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+using System;
+using System.Globalization;
+using System.Resources;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Represents an attribute which can provide a localized name as metadata for a MEF extension.
+ /// </summary>
+ public sealed class LocalizedNameAttribute : SingletonBaseMetadataAttribute
+ {
+ /// <summary>
+ /// Note: the localized name is cached rather than the type to prevent
+ /// MEF from referencing the type in its cache. Types exposed as metadata
+ /// cause MEF to load the assembly containing the type during composition.
+ /// </summary>
+ private readonly string localizedName;
+
+ /// <summary>
+ /// Creates an instance of this attribute, which caches the localized name represented
+ /// by the given type and resource name.
+ /// </summary>
+ /// <param name="type">The type from which to load the localized resource. This should
+ /// be a type created by the resource designer.</param>
+ /// <param name="resourceId">The name of the localized resource string contained the
+ /// resource type.</param>
+ public LocalizedNameAttribute(Type type, string resourceId)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
+ if (resourceId == null)
+ {
+ throw new ArgumentNullException(nameof(resourceId));
+ }
+
+ ResourceManager resourceManager = new ResourceManager(type);
+ this.localizedName = resourceManager.GetString(resourceId, CultureInfo.CurrentUICulture);
+ }
+
+ /// <summary>
+ /// Creates an instance of this attribute, which caches the localized name represented
+ /// by the given type and resource name.
+ /// </summary>
+ /// <param name="type">The type from which to load the localized resource.</param>
+ /// <param name="resourceStreamName">The base name of the resource stream containing the resource.</param>
+ /// <param name="resourceId">The name of the localized resource string contained the
+ /// resource type.</param>
+ public LocalizedNameAttribute(Type type, string resourceStreamName, string resourceId)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
+ if (resourceStreamName == null)
+ {
+ throw new ArgumentNullException(nameof(resourceStreamName));
+ }
+ if (resourceId == null)
+ {
+ throw new ArgumentNullException(nameof(resourceId));
+ }
+
+ ResourceManager resourceManager = new ResourceManager(resourceStreamName, type.Assembly);
+ this.localizedName = resourceManager.GetString(resourceId, CultureInfo.CurrentUICulture);
+ }
+
+ /// <summary>
+ /// Gets the localized name specified by the constructor.
+ /// </summary>
+ public string LocalizedName => this.localizedName;
+ }
+}
diff --git a/src/Core/Def/BaseUtility/NameAttribute.cs b/src/Core/Def/BaseUtility/NameAttribute.cs
index af34702..92698ac 100644
--- a/src/Core/Def/BaseUtility/NameAttribute.cs
+++ b/src/Core/Def/BaseUtility/NameAttribute.cs
@@ -11,7 +11,6 @@ namespace Microsoft.VisualStudio.Utilities
/// </summary>
public sealed class NameAttribute : SingletonBaseMetadataAttribute
{
- private string name;
/// <summary>
/// Constructs a new instance of the attribute.
@@ -23,24 +22,18 @@ namespace Microsoft.VisualStudio.Utilities
{
if (name == null)
{
- throw new ArgumentNullException("name");
+ throw new ArgumentNullException(nameof(name));
}
if (name.Length == 0)
{
- throw new ArgumentException("name must not be empty", "name");
+ throw new ArgumentException("name must not be empty", nameof(name));
}
- this.name = name;
+ this.Name = name;
}
/// <summary>
/// The name of the editor extension part.
/// </summary>
- public string Name
- {
- get
- {
- return name;
- }
- }
+ public string Name { get; }
}
}
diff --git a/src/Core/Def/BaseUtility/OptionUserModifiableAttribute.cs b/src/Core/Def/BaseUtility/OptionUserModifiableAttribute.cs
new file mode 100644
index 0000000..381d447
--- /dev/null
+++ b/src/Core/Def/BaseUtility/OptionUserModifiableAttribute.cs
@@ -0,0 +1,26 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// A MEF attribute determining if an option is user modifiable.
+ /// </summary>
+ public sealed class OptionUserModifiableAttribute : SingletonBaseMetadataAttribute
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OptionUserModifiableAttribute"/>.
+ /// </summary>
+ /// <param name="userModifiable"><c>true</c> if the option is user modifiable; otherwise <c>false</c>.</param>
+ public OptionUserModifiableAttribute(bool userModifiable)
+ {
+ this.OptionUserModifiable = userModifiable;
+ }
+
+ /// <summary>
+ /// Determines whether the option is modifiable to the user.
+ /// </summary>
+ public bool OptionUserModifiable { get; }
+ }
+}
diff --git a/src/Core/Def/BaseUtility/OptionUserVisibleAttribute.cs b/src/Core/Def/BaseUtility/OptionUserVisibleAttribute.cs
new file mode 100644
index 0000000..4b3dbf5
--- /dev/null
+++ b/src/Core/Def/BaseUtility/OptionUserVisibleAttribute.cs
@@ -0,0 +1,26 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// A MEF attribute determining if an option is visible to the user.
+ /// </summary>
+ public sealed class OptionUserVisibleAttribute : SingletonBaseMetadataAttribute
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OptionUserVisibleAttribute"/>.
+ /// </summary>
+ /// <param name="userVisible"><c>true</c> if the option is visible to the user; otherwise <c>false</c>.</param>
+ public OptionUserVisibleAttribute(bool userVisible)
+ {
+ this.OptionUserVisible = userVisible;
+ }
+
+ /// <summary>
+ /// Determines whether the option is visible to the user.
+ /// </summary>
+ public bool OptionUserVisible { get; }
+ }
+}
diff --git a/src/Core/Def/BaseUtility/OrderAttribute.cs b/src/Core/Def/BaseUtility/OrderAttribute.cs
index 3f3d86d..9e2293b 100644
--- a/src/Core/Def/BaseUtility/OrderAttribute.cs
+++ b/src/Core/Def/BaseUtility/OrderAttribute.cs
@@ -24,19 +24,19 @@ namespace Microsoft.VisualStudio.Utilities
{
get
{
- return before;
+ return this.before;
}
set
{
if (value == null)
{
- throw new ArgumentNullException("value");
+ throw new ArgumentNullException(nameof(value));
}
if (value.Length == 0)
{
- throw new ArgumentException("Before value must not be empty", "value");
+ throw new ArgumentException("Before value must not be empty", nameof(value));
}
- before = value;
+ this.before = value;
}
}
@@ -50,19 +50,19 @@ namespace Microsoft.VisualStudio.Utilities
{
get
{
- return after;
+ return this.after;
}
set
{
if (value == null)
{
- throw new ArgumentNullException("value");
+ throw new ArgumentNullException(nameof(value));
}
if (value.Length == 0)
{
- throw new ArgumentException("After value must not be empty", "value");
+ throw new ArgumentException("After value must not be empty", nameof(value));
}
- after = value;
+ this.after = value;
}
}
}
diff --git a/src/Core/Def/BaseUtility/Orderer.cs b/src/Core/Def/BaseUtility/Orderer.cs
index b285e2c..7ab90e4 100644
--- a/src/Core/Def/BaseUtility/Orderer.cs
+++ b/src/Core/Def/BaseUtility/Orderer.cs
@@ -13,6 +13,15 @@ namespace Microsoft.VisualStudio.Utilities
/// </summary>
public static class Orderer
{
+ // This is really sad but, because we actually convert all before/after attributes to upper case, we need upper case versions
+ // of the highest and lowest priorities in order to find them. A better approach would be to use a case insensitive comparison
+ // for the map but that could cause a behavior change.
+ private readonly static string HighestUC = DefaultOrderings.Highest.ToUpperInvariant();
+ private readonly static string HighUC = DefaultOrderings.High.ToUpperInvariant();
+ private readonly static string DefaultUC = DefaultOrderings.Default.ToUpperInvariant();
+ private readonly static string LowUC = DefaultOrderings.Low.ToUpperInvariant();
+ private readonly static string LowestUC = DefaultOrderings.Lowest.ToUpperInvariant();
+
/// <summary>
/// Orders a list of items that are all orderable, that is, items that implement the IOrderable interface.
/// </summary>
@@ -26,13 +35,14 @@ namespace Microsoft.VisualStudio.Utilities
{
if (itemsToOrder == null)
{
- throw new ArgumentNullException("itemsToOrder");
+ throw new ArgumentNullException(nameof(itemsToOrder));
}
#if false && DEBUG
Debug.WriteLine("Before ordering");
DumpGraph(itemsToOrder);
#endif
+
var roots = new Queue<Node<TValue, TMetadata>>();
var unsortedItems = new List<Node<TValue, TMetadata>>();
@@ -58,7 +68,7 @@ namespace Microsoft.VisualStudio.Utilities
{
var node = new Node<TValue, TMetadata>(item);
- if (node.Name != string.Empty)
+ if (node.Name.Length != 0)
{
if (map.ContainsKey(node.Name))
{
@@ -81,14 +91,62 @@ namespace Microsoft.VisualStudio.Utilities
}
}
+ // Only resolve the exported nodes by counting down. Placeholders don't need to be resolved since they have no explicit
+ // after or before (and, when created as a side-effect of resolving an exported node, are added to the end of the list).
for (int i = unsortedItems.Count - 1; (i >= 0); --i)
{
unsortedItems[i].Resolve(map, unsortedItems); //Placeholders are added to the end of unsorted items (and do not need to be resolved).
}
+ // Do the special handling for the highest and lowest placeholders. Specifically, unless something says that it is after Highest
+ // then it gets the implicit constraint that it is before Highest.
+ //
+ // Note that if you have a situation like:
+ // if A declares it is after Highest & B declares that is is after A then, you need to put B in the "after highest" group (otherwise it
+ // will get the before highest constraint, which will cause a cycle).
+ if (map.TryGetValue(HighestUC, out Node<TValue, TMetadata> highest) && (highest.Before.Count != 0))
+ {
+ // There are one or more nodes that explicitly state that they come after highest
+ // collect all of those nodes (and the nodes that come after them) into a single list.
+ var afterHighest = new HashSet<Node<TValue, TMetadata>>();
+ AddToAfterHighest(highest.Before, afterHighest);
+
+ // Go through all of the nodes and, unless they are explicitly in the afterHighest group, add a constraint
+ // that they are explicitly before highest.
+ for (int i = unsortedItems.Count - 1; (i >= 0); --i)
+ {
+ var n = unsortedItems[i];
+ if ((n != highest) && !afterHighest.Contains(n))
+ {
+ n.Before.Add(highest);
+ highest.After.Add(n);
+ }
+ }
+ }
+
+ // Give lowest the same handling as highest, inverting the logic as appropriate.
+ if (map.TryGetValue(LowestUC, out Node<TValue, TMetadata> lowest) && (lowest.After.Count != 0))
+ {
+ var beforeLowest = new HashSet<Node<TValue, TMetadata>>();
+ AddToBeforeLowest(lowest.After, beforeLowest);
+
+ for (int i = unsortedItems.Count - 1; (i >= 0); --i)
+ {
+ var n = unsortedItems[i];
+ if ((n != lowest) && !beforeLowest.Contains(n))
+ {
+ n.After.Add(lowest);
+ lowest.Before.Add(n);
+ }
+ }
+ }
+
+ AddPlaceHolders(map, LowestUC, LowUC, DefaultUC, HighUC, HighestUC);
+
List<Node<TValue, TMetadata>> initialRoots = new List<Node<TValue, TMetadata>>();
- foreach (Node<TValue, TMetadata> node in unsortedItems)
+ for (int i = unsortedItems.Count - 1; (i >= 0); --i)
{
+ var node = unsortedItems[i];
if (node.After.Count == 0)
{
initialRoots.Add(node);
@@ -98,6 +156,60 @@ namespace Microsoft.VisualStudio.Utilities
AddToRoots(roots, initialRoots);
}
+ private static void AddPlaceHolders<TValue, TMetadata>(Dictionary<string, Node<TValue, TMetadata>> map,
+ params string[] names)
+ where TValue : class
+ where TMetadata : IOrderable
+ {
+ // Make sure there's an explicit ordering where the node for name[0] come before the node for name[1], etc.
+ //
+ // If the node for a name doesn't exist, just skip it (no one else was using it so we don't need to order it
+ // with respect to anything).
+ Node<TValue, TMetadata> previousNode = null;
+ for (int i = 0; (i < names.Length); ++i)
+ {
+ Node<TValue, TMetadata> node;
+ if (map.TryGetValue(names[i], out node))
+ {
+ if (previousNode != null)
+ {
+ previousNode.Before.Add(node);
+ node.After.Add(previousNode);
+ }
+
+ previousNode = node;
+ }
+ }
+ }
+
+ // We need to find all the nodes that are after Highest (or after a node that is after Highest, ad infinitum).
+ private static void AddToAfterHighest<TValue, TMetadata>(IEnumerable<Node<TValue, TMetadata>> nodes, HashSet<Node<TValue, TMetadata>> afterHighest)
+ where TValue : class
+ where TMetadata : IOrderable
+ {
+ foreach (var n in nodes)
+ {
+ if (afterHighest.Add(n) && (n.Before.Count != 0))
+ {
+ AddToAfterHighest(n.Before, afterHighest);
+ }
+ }
+ }
+
+ // The Before/Lowest analog of AddToAfterHighest.
+ private static void AddToBeforeLowest<TValue, TMetadata>(IEnumerable<Node<TValue, TMetadata>> nodes, HashSet<Node<TValue, TMetadata>> beforeLowest)
+ where TValue : class
+ where TMetadata : IOrderable
+ {
+ foreach (var n in nodes)
+ {
+ if (beforeLowest.Add(n) && (n.After.Count != 0))
+ {
+ AddToBeforeLowest(n.After, beforeLowest);
+ }
+ }
+ }
+
private static IList<Lazy<TValue, TMetadata>> TopologicalSort<TValue, TMetadata>(Queue<Node<TValue, TMetadata>> roots, List<Node<TValue, TMetadata>> unsortedItems)
where TValue : class
where TMetadata : IOrderable
@@ -115,7 +227,7 @@ namespace Microsoft.VisualStudio.Utilities
sortedItems.Add(node.Item);
}
- unsortedItems.Remove(node);
+ unsortedItems.Remove(node);
node.ClearBefore(roots);
}
@@ -126,10 +238,10 @@ namespace Microsoft.VisualStudio.Utilities
where TValue : class
where TMetadata : IOrderable
{
- newRoots.Sort((l, r) => l.Name.CompareTo(r.Name));
- foreach (Node<TValue, TMetadata> n in newRoots)
+ newRoots.Sort((l, r) => string.CompareOrdinal(l.Name, r.Name));
+ for (int i = 0; (i < newRoots.Count); ++i)
{
- roots.Enqueue(n);
+ roots.Enqueue(newRoots[i]);
}
}
@@ -159,11 +271,13 @@ namespace Microsoft.VisualStudio.Utilities
//Find the cycle with the fewest inbound links from other cycles.
int bestInwardLinkCount = int.MaxValue;
List<Node<TValue, TMetadata>> bestCycle = null;
- foreach (List<Node<TValue, TMetadata>> cycle in cycles)
+ for (int i = 0; (i < cycles.Count); ++i)
{
+ var cycle = cycles[i];
int inwardLinkCount = 0;
- foreach (Node<TValue, TMetadata> node in cycle)
+ for (int j = 0; (j < cycle.Count); ++j)
{
+ var node = cycle[j];
foreach (Node<TValue, TMetadata> child in node.After)
{
if (child.LowIndex != node.LowIndex)
@@ -216,8 +330,9 @@ namespace Microsoft.VisualStudio.Utilities
where TValue : class
where TMetadata : IOrderable
{
- foreach (Node<TValue, TMetadata> n in unsortedItems)
+ for (int i = 0; (i < unsortedItems.Count); ++i)
{
+ var n = unsortedItems[i];
n.Index = -1;
n.LowIndex = -1;
n.ContainedInKnownCycle = false;
@@ -227,8 +342,9 @@ namespace Microsoft.VisualStudio.Utilities
Stack<Node<TValue, TMetadata>> stack = new Stack<Node<TValue, TMetadata>>(unsortedItems.Count);
int index = 0;
- foreach (Node<TValue, TMetadata> node in unsortedItems)
+ for (int i = 0; (i < unsortedItems.Count); ++i)
{
+ var node = unsortedItems[i];
if (node.Index == -1)
{
Orderer.FindCycles(node, stack, ref index, cycles);
@@ -400,7 +516,7 @@ namespace Microsoft.VisualStudio.Utilities
Node<TValue, TMetadata> node;
if (!map.TryGetValue(name, out node))
{
- //We need place-holder to handle the case where A comes before B and C comes after B but B is never defined.
+ //We need place-holder to handle the case where A comes before B and C comes after B but B is never defined.
//We still want C to come after A though so we need to create a "B".
//
//B doesn't show up in the output.
diff --git a/src/Core/Def/BaseUtility/PriorityAttribute.cs b/src/Core/Def/BaseUtility/PriorityAttribute.cs
new file mode 100644
index 0000000..49c8ceb
--- /dev/null
+++ b/src/Core/Def/BaseUtility/PriorityAttribute.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Represents an attribute which assigns an integer priority to a MEF component part.
+ /// </summary>
+ public sealed class PriorityAttribute : SingletonBaseMetadataAttribute
+ {
+ /// <summary>
+ /// Creates a new instance of this attribute, assigning it a priority value.
+ /// </summary>
+ /// <param name="priority">The priority for the MEF component part. Lower integer
+ /// values represent higher precedence.</param>
+ public PriorityAttribute(int priority)
+ {
+ this.Priority = priority;
+ }
+
+ /// <summary>
+ /// Gets the priority for the attributed MEF extension.
+ /// </summary>
+ public int Priority { get; }
+ }
+}
diff --git a/src/Core/Def/BaseUtility/PropertyCollection.cs b/src/Core/Def/BaseUtility/PropertyCollection.cs
index 87b7ab1..b3ceb58 100644
--- a/src/Core/Def/BaseUtility/PropertyCollection.cs
+++ b/src/Core/Def/BaseUtility/PropertyCollection.cs
@@ -70,7 +70,7 @@ namespace Microsoft.VisualStudio.Utilities
public T GetOrCreateSingletonProperty<T>(object key, Func<T> creator) where T : class
{
if (creator == null)
- throw new ArgumentNullException("creator");
+ throw new ArgumentNullException(nameof(creator));
lock (this.syncLock)
{
diff --git a/src/Core/Def/ContentType/ContentTypeAttribute.cs b/src/Core/Def/ContentType/ContentTypeAttribute.cs
index e5939a1..45f5af8 100644
--- a/src/Core/Def/ContentType/ContentTypeAttribute.cs
+++ b/src/Core/Def/ContentType/ContentTypeAttribute.cs
@@ -14,7 +14,6 @@ namespace Microsoft.VisualStudio.Utilities
/// <seealso cref="ContentTypeDefinition"></seealso>
public sealed class ContentTypeAttribute : MultipleBaseMetadataAttribute
{
- private string contentTypes;
/// <summary>
/// Initializes a new instance of <see cref="ContentTypeAttribute"/>.
@@ -26,20 +25,14 @@ namespace Microsoft.VisualStudio.Utilities
{
if (string.IsNullOrEmpty(name))
{
- throw new ArgumentNullException("name");
+ throw new ArgumentNullException(nameof(name));
}
- contentTypes = name;
+ ContentTypes = name;
}
/// <summary>
/// The content type name.
/// </summary>
- public string ContentTypes
- {
- get
- {
- return contentTypes;
- }
- }
+ public string ContentTypes { get; }
}
}
diff --git a/src/Core/Def/ContentType/FileExtensionAttribute.cs b/src/Core/Def/ContentType/FileExtensionAttribute.cs
index 6407a34..6df6ad3 100644
--- a/src/Core/Def/ContentType/FileExtensionAttribute.cs
+++ b/src/Core/Def/ContentType/FileExtensionAttribute.cs
@@ -11,7 +11,6 @@ namespace Microsoft.VisualStudio.Utilities
/// </summary>
public sealed class FileExtensionAttribute : SingletonBaseMetadataAttribute
{
- private string fileExtension;
/// <summary>
/// Constructs a new instance of the attribute.
@@ -22,20 +21,14 @@ namespace Microsoft.VisualStudio.Utilities
{
if (string.IsNullOrWhiteSpace(fileExtension))
{
- throw new ArgumentNullException("fileExtension");
+ throw new ArgumentNullException(nameof(fileExtension));
}
- this.fileExtension = fileExtension;
+ this.FileExtension = fileExtension;
}
/// <summary>
/// Gets the file extension.
/// </summary>
- public string FileExtension
- {
- get
- {
- return this.fileExtension;
- }
- }
+ public string FileExtension { get; }
}
}
diff --git a/src/Core/Def/ContentType/FileExtensionToContentTypeDefinition.cs b/src/Core/Def/ContentType/FileExtensionToContentTypeDefinition.cs
index e88896f..80fd079 100644
--- a/src/Core/Def/ContentType/FileExtensionToContentTypeDefinition.cs
+++ b/src/Core/Def/ContentType/FileExtensionToContentTypeDefinition.cs
@@ -14,10 +14,14 @@ namespace Microsoft.VisualStudio.Utilities
/// internal sealed class Components
/// {
/// [Export]
- /// [FileExtension(".abc")]
+ /// [FileExtension(".abc")] // Any file with the extention "abc" will get the "alphabet" content type.
/// [ContentType("alphabet")]
/// internal FileExtensionToContentTypeDefinition abcFileExtensionDefinition;
///
+ /// [Export]
+ /// [FileName("readme")] // Any file named "readme" will get the "alphabet" content type.
+ /// [ContentType("alphabet")]
+ /// internal FileExtensionToContentTypeDefinition readmeFileNameDefinition;
/// { other components }
/// }
/// </example>
diff --git a/src/Core/Def/ContentType/IFileExtensionRegistryService2.cs b/src/Core/Def/ContentType/IFileExtensionRegistryService2.cs
index 2d63021..e0dfd99 100644
--- a/src/Core/Def/ContentType/IFileExtensionRegistryService2.cs
+++ b/src/Core/Def/ContentType/IFileExtensionRegistryService2.cs
@@ -49,6 +49,6 @@ namespace Microsoft.VisualStudio.Utilities
/// </summary>
/// <remarks>If the specified name does not exist, then the method does nothing.</remarks>
/// <param name="name">The file name (the period is optional).</param>
- void RemoveFileName(string name);
+ void RemoveFileName(string name);
}
}
diff --git a/src/Core/Def/ContentType/IFilePathToContentTypeProvider.cs b/src/Core/Def/ContentType/IFilePathToContentTypeProvider.cs
new file mode 100644
index 0000000..605e64a
--- /dev/null
+++ b/src/Core/Def/ContentType/IFilePathToContentTypeProvider.cs
@@ -0,0 +1,32 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// MEF export to map full file names to a content type.
+ /// </summary>
+ /// <remarks>
+ /// <para>Instances of this class should define the following MEF attributes.
+ /// <code>
+ /// [Export(typeof(IFilePathToContentTypeProvider)] -- Required
+ /// [Name("BamBam")] -- Required
+ /// [Order(After = "Fred", Before="Barney")] -- Optional, can have more than one.
+ /// [FileExtension(".abc")] -- Optional, but must have either a FileExtension or a FileName attribute
+ /// [FileName("George")] -- Optional, but must have either a FileExtension or a FileName attribute
+ /// </code>
+ /// You can use "*" as the FileExtension attribute to match any file extension.</para>
+ ///
+ /// <para>
+ /// The <see cref="IFilePathToContentTypeProvider"/> will be called in order (based on the <see cref="OrderAttribute"/>) if their
+ /// <see cref="FileExtensionAttribute"/> matches the extension of the file in question (or is a "*") or the <see cref="FileNameAttribute"/>
+ /// matches the name of the file in question.
+ /// </para>
+ /// </remarks>
+ public interface IFilePathToContentTypeProvider
+ {
+ bool TryGetContentTypeForFilePath(string filePath, out IContentType contentType);
+ }
+}
diff --git a/src/Core/Def/ContentType/IFileToContentTypeService.cs b/src/Core/Def/ContentType/IFileToContentTypeService.cs
new file mode 100644
index 0000000..7c8bde0
--- /dev/null
+++ b/src/Core/Def/ContentType/IFileToContentTypeService.cs
@@ -0,0 +1,107 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Service for mapping files to the appropriate <see cref="IContentType"/> for that file.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Note that this interface duplicates the methods from <see cref="IFileExtensionRegistryService"/> and
+ /// <see cref="IFileExtensionRegistryService2"/>. The eventual goal is to deprecate the other interfaces
+ /// and only use <see cref="IFileToContentTypeService"/>.
+ /// </para></remarks>
+ public interface IFileToContentTypeService
+ {
+ /// <summary>
+ /// Get the default <see cref="IContentType"/> for a file located at <paramref name="filePath"/>.
+ /// </summary>
+ /// <param name="filePath">Name of the file in question.</param>
+ /// <returns>Excpected content type or
+ /// <see cref="IContentTypeRegistryService.UnknownContentType"/> if no content type is found.</returns>
+ /// <remarks>If no <see cref="IContentType"/> is found using declared <see cref="IFilePathToContentTypeProvider"/>
+ /// assets, then the <see cref="GetContentTypeForFileNameOrExtension(string)"/> is used.</remarks>
+ IContentType GetContentTypeForFilePath(string filePath);
+
+ /// <summary>
+ /// Get the default <see cref="IContentType"/> for a file located at <paramref name="filePath"/>.
+ /// </summary>
+ /// <param name="filePath">Name of the file in question.</param>
+ /// <returns>Excpected content type or
+ /// <see cref="IContentTypeRegistryService.UnknownContentType"/> if no content type is found.</returns>
+ /// <remarks>If no <see cref="IContentType"/> is found using declared <see cref="IFilePathToContentTypeProvider"/>
+ /// assets, then <see cref="IContentTypeRegistryService.UnknownContentType"/> is returned.</remarks>
+ IContentType GetContentTypeForFilePathOnly(string filePath);
+
+ /// <summary>
+ /// Gets the content type associated with the given file name.
+ /// </summary>
+ /// <param name="name">The file name. It cannot be null.</param>
+ /// <returns>The <see cref="IContentType"></see> associated with this name. If no association exists, it returns the "unknown" content type. It never returns null.</returns>
+ IContentType GetContentTypeForFileName(string name);
+
+ /// <summary>
+ /// Gets the content type associated with the given file name or its extension.
+ /// </summary>
+ /// <param name="name">The file name. It cannot be null.</param>
+ /// <returns>The <see cref="IContentType"></see> associated with this name. If no association exists, it returns the "unknown" content type. It never returns null.</returns>
+ IContentType GetContentTypeForFileNameOrExtension(string name);
+
+ /// <summary>
+ /// Gets the list of file names associated with the specified content type.
+ /// </summary>
+ /// <param name="contentType">The content type. It cannot be null.</param>
+ /// <returns>The list of file names associated with the content type.</returns>
+ IEnumerable<string> GetFileNamesForContentType(IContentType contentType);
+
+ /// <summary>
+ /// Adds a new file name to the registry.
+ /// </summary>
+ /// <param name="name">The file name (the period is optional).</param>
+ /// <param name="contentType">The content type for the file name.</param>
+ /// <exception cref="InvalidOperationException"><see paramref="name"/> is already present in the registry.</exception>
+ void AddFileName(string name, IContentType contentType);
+
+ /// <summary>
+ /// Removes the specified file name from the registry.
+ /// </summary>
+ /// <remarks>If the specified name does not exist, then the method does nothing.</remarks>
+ /// <param name="name">The file name (the period is optional).</param>
+ void RemoveFileName(string name);
+
+ /// <summary>
+ /// Gets the content type associated with the given file extension.
+ /// </summary>
+ /// <param name="extension">The file extension. It cannot be null, and it should not contain a period.</param>
+ /// <returns>The <see cref="IContentType"></see> associated with this extension. If no association exists, it returns the "unknown" content type. It never returns null.</returns>
+ IContentType GetContentTypeForExtension(string extension);
+
+ /// <summary>
+ /// Gets the list of file extensions associated with the specified content type.
+ /// </summary>
+ /// <param name="contentType">The content type. It cannot be null.</param>
+ /// <returns>The list of file extensions associated with the content type.</returns>
+ IEnumerable<string> GetExtensionsForContentType(IContentType contentType);
+
+ /// <summary>
+ /// Adds a new file extension to the registry.
+ /// </summary>
+ /// <param name="extension">The file extension (the period is optional).</param>
+ /// <param name="contentType">The content type for the file extension.</param>
+ /// <exception cref="InvalidOperationException"><see paramref="extension"/> is already present in the registry.</exception>
+ void AddFileExtension(string extension, IContentType contentType);
+
+ /// <summary>
+ /// Removes the specified file extension from the registry.
+ /// </summary>
+ /// <remarks>If the specified extension does not exist, then the method does nothing.</remarks>
+ /// <param name="extension">The file extension (the period is optional).</param>
+ void RemoveFileExtension(string extension);
+ }
+}
diff --git a/src/Core/Def/Features/FeatureDefinition.cs b/src/Core/Def/Features/FeatureDefinition.cs
new file mode 100644
index 0000000..4960167
--- /dev/null
+++ b/src/Core/Def/Features/FeatureDefinition.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Defines a feature which may be disabled using <see cref="IFeatureService"/> and grouped using <see cref="BaseDefinitionAttribute"/>
+ /// </summary>
+ /// <remarks>
+ /// Because you cannot subclass this type, you can use the [Export] attribute with no type.
+ /// </remarks>
+ /// <example>
+ /// [Export]
+ /// [Name(nameof(MyFeature))] // required
+ /// [BaseDefinition(PredefinedEditorFeatureNames.Popup)] // zero or more BaseDefinitions are allowed
+ /// public FeatureDefinition MyFeature;
+ /// </example>
+ public sealed class FeatureDefinition
+ {
+ }
+}
diff --git a/src/Core/Def/Features/FeatureEventArgs.cs b/src/Core/Def/Features/FeatureEventArgs.cs
new file mode 100644
index 0000000..fa791b1
--- /dev/null
+++ b/src/Core/Def/Features/FeatureEventArgs.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Notifies that a specific feature was updated and might have changed its state,
+ /// without computing the state value.
+ /// </summary>
+ public class FeatureUpdatedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Name of feature that was updated.
+ /// </summary>
+ public string FeatureName { get; }
+
+ /// <summary>
+ /// Creates an instance of <see cref="FeatureUpdatedEventArgs"/>.
+ /// </summary>
+ /// <param name="featureName">Name of feature that was updated</param>
+ public FeatureUpdatedEventArgs(string featureName)
+ {
+ FeatureName = featureName;
+ }
+ }
+
+ /// <summary>
+ /// Notifies that a specific feature changed state, and provides the new state value.
+ /// </summary>
+ public class FeatureChangedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Name of feature that was changed.
+ /// </summary>
+ public string FeatureName { get; }
+
+ /// <summary>
+ /// New value of the feature state.
+ /// </summary>
+ public bool IsEnabled { get; }
+
+ /// <summary>
+ /// Creates an instance of <see cref="FeatureChangedEventArgs"/>.
+ /// </summary>
+ /// <param name="featureName">Name of feature that was changed</param>
+ /// <param name="isEnabled">New value of the feature state</param>
+ public FeatureChangedEventArgs(string featureName, bool isEnabled)
+ {
+ FeatureName = featureName;
+ IsEnabled = isEnabled;
+ }
+ }
+}
diff --git a/src/Core/Def/Features/IFeatureController.cs b/src/Core/Def/Features/IFeatureController.cs
new file mode 100644
index 0000000..45f91e4
--- /dev/null
+++ b/src/Core/Def/Features/IFeatureController.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Keeps track of requests to disable a feature using <see cref="IFeatureService"/>.
+ /// Each <see cref="IFeatureController"/> may re-enable a feature it disabled,
+ /// but may not re-enable a feature disabled by another <see cref="IFeatureController"/>.
+ /// </summary>
+ public interface IFeatureController
+ {
+ }
+}
diff --git a/src/Core/Def/Features/IFeatureCookie.cs b/src/Core/Def/Features/IFeatureCookie.cs
new file mode 100644
index 0000000..9b890bf
--- /dev/null
+++ b/src/Core/Def/Features/IFeatureCookie.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Provides O(1) read only view on the state of the feature
+ /// in the <see cref="IFeatureService" /> that created this <see cref="IFeatureCookie" />.
+ /// Also exposes an event that provides notification when the state of the feature changes.
+ /// </summary>
+ public interface IFeatureCookie
+ {
+ /// <summary>
+ /// Provides notification when <see cref="IsEnabled"/> value changes.
+ /// </summary>
+ event EventHandler<FeatureChangedEventArgs> StateChanged;
+
+ /// <summary>
+ /// Up to date state of the feature.
+ /// </summary>
+ bool IsEnabled { get; }
+
+ /// <summary>
+ /// Name of the tracked feature.
+ /// </summary>
+ string FeatureName { get; }
+ }
+}
diff --git a/src/Core/Def/Features/IFeatureDisableToken.cs b/src/Core/Def/Features/IFeatureDisableToken.cs
new file mode 100644
index 0000000..69a9169
--- /dev/null
+++ b/src/Core/Def/Features/IFeatureDisableToken.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Keeps track of the request to disable the feature.
+ /// To restore the feature,
+ /// </summary>
+ public interface IFeatureDisableToken : IDisposable
+ {
+ }
+}
diff --git a/src/Core/Def/Features/IFeatureService.cs b/src/Core/Def/Features/IFeatureService.cs
new file mode 100644
index 0000000..88a8c9f
--- /dev/null
+++ b/src/Core/Def/Features/IFeatureService.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Service that keeps track of <see cref="IFeatureController"/>'s requests to disable a feature in given scope.
+ /// When multiple <see cref="IFeatureController"/>s disable a feature and one <see cref="IFeatureController"/>
+ /// enables it back, it will not interfere with other disable requests, and feature will ultimately remain disabled.
+ ///
+ /// While this service does have a thread affinity, its implementation does not guarantee thread safety.
+ /// It is advised to change feature state from UI thread, otherwise simultaneous changes may result in race conditions.
+ /// </summary>
+ /// <example>
+ /// // In an exported MEF part:
+ /// [Import]
+ /// IFeatureServiceFactory FeatureServiceFactory;
+ ///
+ /// IFeatureService globalService = FeatureServiceFactory.GlobalFeatureService;
+ /// IFeatureService localService = FeatureServiceFactory.GetOrCreate(scope); // scope is an IPropertyOwner
+ ///
+ /// // Also have a reference to <see cref="IFeatureController"/>:
+ /// IFeatureController MyFeatureController;
+ /// // Interact with the <see cref="IFeatureService"/>:
+ /// globalService.Disable(PredefinedEditorFeatureNames.Popup, MyFeatureController);
+ /// localService.IsEnabled(PredefinedEditorFeatureNames.Completion); // returns false, because Popup is a base definition of Completion and because global scope is a superset of local scope.
+ /// </example>
+ public interface IFeatureService
+ {
+ /// <summary>
+ /// Checks if feature is enabled. By default, every feature is enabled.
+ /// </summary>
+ /// <param name="featureName">Name of the feature</param>
+ /// <returns>False if there are any disable requests. True otherwise.</returns>
+ bool IsEnabled(string featureName);
+
+ /// <summary>
+ /// Disables a feature.
+ /// </summary>
+ /// <param name="featureName">Name of the feature to disable</param>
+ /// <param name="controller">Object that uniquely identifies the caller.</param>
+ IFeatureDisableToken Disable(string featureName, IFeatureController controller);
+
+ /// <summary>
+ /// Provides a notification when this feature or its base feature was updated.
+ /// We use FeatureUpdatedEventArgs and not FeatureChangedEventArgs
+ /// because there are base features and disable requests from parent scopes that affect the factual state of given feature.
+ /// We use this event to let the interested parties (<see cref="IFeatureCookie"/>) recalculate the actual state of the feature.
+ /// </summary>
+ event EventHandler<FeatureUpdatedEventArgs> StateUpdated;
+
+ /// <summary>
+ /// Creates a new <see cref="IFeatureCookie"/> that provides O(1) access to the feature's value, in this service's scope.
+ /// The <see cref="IFeatureCookie" /> is updated when the feature or its base is updated in this scope or in the global scope.
+ /// </summary>
+ /// <param name="featureName">Name of the feature</param>
+ /// <returns>New instance of <see cref="IFeatureCookie"/></returns>
+ IFeatureCookie GetCookie(string featureName);
+ }
+}
diff --git a/src/Core/Def/Features/IFeatureServiceFactory.cs b/src/Core/Def/Features/IFeatureServiceFactory.cs
new file mode 100644
index 0000000..245fb5c
--- /dev/null
+++ b/src/Core/Def/Features/IFeatureServiceFactory.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Service that provides <see cref="IFeatureService" />s used to track feature availability and to request feature to be disabled.
+ /// Feature may be tracked by scope, using <see cref="IFeatureServiceFactory.GetOrCreate" /> and passing <see cref="IPropertyOwner" /> e.g. a text view.
+ /// or throughout the application using <see cref="IFeatureServiceFactory.GlobalFeatureService" />.
+ ///
+ /// Features are implemented by exporting <see cref="FeatureDefinition"/> and grouped using <see cref="BaseDefinitionAttribute"/>.
+ /// Grouping allows alike features to be disabling at once.
+ /// Grouping also relieves <see cref="IFeatureController"/> from updating its code when new feature of appropriate category is introduced.
+ /// Standard editor feature names are available in <see cref="PredefinedEditorFeatureNames"/>.
+ /// </summary>
+ /// <example>
+ /// // In an exported MEF part:
+ /// [Import]
+ /// IFeatureServiceFactory FeatureServiceFactory;
+ ///
+ /// IFeatureService globalService = FeatureServiceFactory.GlobalFeatureService;
+ /// IFeatureService localService = FeatureServiceFactory.GetOrCreate(scope); // scope is an IPropertyOwner
+ ///
+ /// // Also have a reference to <see cref="IFeatureController"/>:
+ /// IFeatureController MyFeatureController;
+ /// // Interact with the <see cref="IFeatureService"/>:
+ /// globalService.Disable(PredefinedEditorFeatureNames.Popup, MyFeatureController);
+ /// localService.IsEnabled(PredefinedEditorFeatureNames.Completion); // returns false, because Popup is a base definition of Completion and because global scope is a superset of local scope.
+ /// </example>
+ public interface IFeatureServiceFactory
+ {
+ /// <summary>
+ /// Gets the global <see cref="IFeatureService"/>
+ /// </summary>
+ IFeatureService GlobalFeatureService { get; }
+
+ /// <summary>
+ /// Gets the <see cref="IFeatureService"/> for the specified scope.
+ /// </summary>
+ /// <param name="scope"></param>
+ /// <returns></returns>
+ IFeatureService GetOrCreate(IPropertyOwner scope);
+ }
+}
diff --git a/src/Core/Def/Features/PredefinedEditorFeatureNames.cs b/src/Core/Def/Features/PredefinedEditorFeatureNames.cs
new file mode 100644
index 0000000..08d7568
--- /dev/null
+++ b/src/Core/Def/Features/PredefinedEditorFeatureNames.cs
@@ -0,0 +1,30 @@
+namespace Microsoft.VisualStudio.Utilities
+{
+ /// <summary>
+ /// Contains definitions for known <see cref="FeatureDefinition"/>s and their groupings.
+ /// </summary>
+ public static class PredefinedEditorFeatureNames
+ {
+ /// <summary>
+ /// Definition of group of features that make up the core editor.
+ /// </summary>
+ public const string Editor = nameof(Editor);
+
+ /// <summary>
+ /// Definition of group of features that appear in a popup.
+ /// </summary>
+ public const string Popup = nameof(Popup);
+
+ /// <summary>
+ /// Definition of group of features that appear in an interactive popup.
+ /// Descends from <see cref="Popup"/>
+ /// </summary>
+ public const string InteractivePopup = nameof(InteractivePopup);
+
+ /// <summary>
+ /// Definition of IntelliSense Completion.
+ /// Descends from <see cref="InteractivePopup"/> and <see cref="Editor"/>
+ /// </summary>
+ public const string Completion = nameof(Completion);
+ }
+}
diff --git a/src/Core/Def/ImageId.cs b/src/Core/Def/ImageId.cs
index d540c16..0f7486f 100644
--- a/src/Core/Def/ImageId.cs
+++ b/src/Core/Def/ImageId.cs
@@ -2,6 +2,8 @@
{
using System;
+#pragma warning disable CA1720 // Identifier contains type name
+#pragma warning disable CA1051 // Do not declare visible instance fields
/// <summary>
/// Unique identifier for Visual Studio image asset.
/// </summary>
@@ -9,7 +11,7 @@
/// On Windows systems, <see cref="ImageId"/> can be converted to and from
/// various other image representations via the ImageIdExtensions extension methods.
/// </remarks>
- public struct ImageId
+ public struct ImageId : IEquatable<ImageId>
{
/// <summary>
/// The <see cref="Guid"/> identifying the group to which this image belongs.
@@ -31,5 +33,26 @@
this.Guid = guid;
this.Id = id;
}
+ public override string ToString()
+ {
+ return this.ToString(System.Globalization.CultureInfo.InvariantCulture);
+ }
+
+ public string ToString(IFormatProvider provider)
+ {
+ return string.Format(provider, @"{0} : {1}", this.Guid.ToString("D", provider), Id.ToString(provider));
+ }
+
+ bool IEquatable<ImageId>.Equals(ImageId other) => Id.Equals(other.Id) && Guid.Equals(other.Guid);
+
+ public override bool Equals(object other) => other is ImageId otherImage && ((IEquatable<ImageId>)this).Equals(otherImage);
+
+ public static bool operator ==(ImageId left, ImageId right) => left.Equals(right);
+
+ public static bool operator !=(ImageId left, ImageId right) => !(left == right);
+
+ public override int GetHashCode() => Guid.GetHashCode() ^ Id.GetHashCode();
}
+#pragma warning restore CA1720 // Identifier contains type name
+#pragma warning restore CA1051 // Do not declare visible instance fields
}
diff --git a/src/Core/Impl/ContentType/ContentTypeImpl.cs b/src/Core/Impl/ContentType/ContentTypeImpl.cs
index 1a6e3b9..29f8bea 100644
--- a/src/Core/Impl/ContentType/ContentTypeImpl.cs
+++ b/src/Core/Impl/ContentType/ContentTypeImpl.cs
@@ -13,32 +13,28 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
{
internal partial class ContentTypeImpl : IContentType
{
- private readonly string name;
private readonly static ContentTypeImpl[] emptyBaseTypes = Array.Empty<ContentTypeImpl>();
private ContentTypeImpl[] baseTypeList = emptyBaseTypes;
internal ContentTypeImpl(string name, string mimeType = null, IEnumerable<string> baseTypes = null)
{
- this.name = name;
+ this.TypeName = name;
this.MimeType = mimeType;
this.UnprocessedBaseTypes = baseTypes;
}
- public string TypeName
- {
- get { return this.name; }
- }
+ public string TypeName { get; private set; }
public string DisplayName
{
- get { return this.name; }
+ get { return this.TypeName; }
}
public string MimeType { get; }
public bool IsOfType(string type)
{
- if (string.Compare(type, this.name, StringComparison.OrdinalIgnoreCase) == 0)
+ if (string.Equals(type, this.TypeName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
@@ -62,7 +58,7 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
public override string ToString()
{
- return this.name;
+ return this.TypeName;
}
internal void ProcessBaseTypes(IDictionary<string, ContentTypeImpl> nameToContentTypeBuilder,
@@ -70,7 +66,7 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
{
if (this.UnprocessedBaseTypes != null)
{
- List<ContentTypeImpl> newBaseTypes = new List<ContentTypeImpl>();
+ var newBaseTypes = new List<ContentTypeImpl>();
foreach (var baseTypeName in this.UnprocessedBaseTypes)
{
// The expectation is that the base type will already exists but (if it doesn't) add a stub for it (& the ctor for a stub base type leaves it in a state
@@ -78,8 +74,8 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
var baseType = ContentTypeRegistryImpl.AddContentTypeFromMetadata(baseTypeName, /* mime type */null, /* base types */null, nameToContentTypeBuilder, mimeTypeToContentTypeBuilder);
if (baseType == ContentTypeRegistryImpl.UnknownContentTypeImpl)
{
- throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentUICulture,
- Strings.ContentTypeRegistry_ContentTypesCannotDeriveFromUnknown, this.TypeName));
+ throw new InvalidOperationException(string.Format(provider: System.Globalization.CultureInfo.CurrentCulture,
+ format: Strings.ContentTypeRegistry_ContentTypesCannotDeriveFromUnknown, arg0: this.TypeName));
}
if (!newBaseTypes.Contains(baseType))
diff --git a/src/Core/Impl/ContentType/ContentTypeRegistryServiceImpl.cs b/src/Core/Impl/ContentType/ContentTypeRegistryServiceImpl.cs
index 943c6c3..2f32dba 100644
--- a/src/Core/Impl/ContentType/ContentTypeRegistryServiceImpl.cs
+++ b/src/Core/Impl/ContentType/ContentTypeRegistryServiceImpl.cs
@@ -30,10 +30,10 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
[Export(typeof(IFileExtensionRegistryService))]
[Export(typeof(IFileExtensionRegistryService2))]
- [Export(typeof(IFilePathRegistryService))]
+ [Export(typeof(IFileToContentTypeService))]
[Export(typeof(IContentTypeRegistryService))]
[Export(typeof(IContentTypeRegistryService2))]
- internal sealed partial class ContentTypeRegistryImpl : IContentTypeRegistryService2, IFileExtensionRegistryService, IFileExtensionRegistryService2, IFilePathRegistryService
+ internal sealed partial class ContentTypeRegistryImpl : IContentTypeRegistryService2, IFileExtensionRegistryService, IFileExtensionRegistryService2, IFileToContentTypeService
{
[ImportMany]
internal List<Lazy<ContentTypeDefinition, IContentTypeDefinitionMetadata>> ContentTypeDefinitions { get; set; }
@@ -60,9 +60,10 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
}
else
{
- _orderedFilePathToContentTypeProductions = new List<Lazy<IFilePathToContentTypeProvider, IFilePathToContentTypeMetadata>>();
+ _orderedFilePathToContentTypeProductions = new List<Lazy<IFilePathToContentTypeProvider, IFilePathToContentTypeMetadata>>(0);
}
}
+
return _orderedFilePathToContentTypeProductions;
}
}
@@ -256,10 +257,7 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
#region IContentTypeRegistryService Members
public IContentType GetContentType(string typeName)
{
- if (string.IsNullOrWhiteSpace(typeName))
- {
- throw new ArgumentException(nameof(typeName));
- }
+ Requires.NotNullOrWhiteSpace(typeName, nameof(typeName));
this.BuildContentTypes();
@@ -287,16 +285,16 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
public IContentType AddContentType(string typeName, IEnumerable<string> baseTypeNames)
{
- if (string.IsNullOrWhiteSpace(typeName))
- {
- throw new ArgumentException(nameof(typeName));
- }
+ Requires.NotNullOrWhiteSpace(typeName, nameof(typeName));
// This has the side effect of building the content types.
if (this.GetContentType(typeName) != null)
{
// Cannot dynamically add a new content type if a content type with the same name already exists
- throw new ArgumentException(String.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.ContentTypeRegistry_CannotAddExistentType, typeName));
+ throw new ArgumentException(
+ string.Format(
+ System.Globalization.CultureInfo.CurrentCulture,
+ Strings.ContentTypeRegistry_CannotAddExistentType, typeName));
}
var oldMaps = Volatile.Read(ref this.maps);
@@ -312,7 +310,10 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
if (type.CheckForCycle(breakCycle: false))
{
- throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.ContentTypeRegistry_CausesCycles, type.TypeName));
+ throw new InvalidOperationException(
+ string.Format(
+ System.Globalization.CultureInfo.CurrentCulture,
+ Strings.ContentTypeRegistry_CausesCycles, type.TypeName));
}
var newMaps = new MapCollection(nameToContentTypeMap.Source, mimeTypeToContentTypeMap.Source, oldMaps.FileExtensionToContentTypeMap, oldMaps.FileNameToContentTypeMap);
@@ -329,10 +330,7 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
public void RemoveContentType(string typeName)
{
- if (string.IsNullOrWhiteSpace(typeName))
- {
- throw new ArgumentException(nameof(typeName));
- }
+ Requires.NotNullOrWhiteSpace(typeName, nameof(typeName));
this.BuildContentTypes();
@@ -356,21 +354,34 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
if (IsBaseType(type, out derivedType))
{
// Check if the type is base type for another registered type
- throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.ContentTypeRegistry_CannotRemoveBaseType, type.TypeName, derivedType.TypeName));
+ throw new InvalidOperationException(
+ string.Format(
+ System.Globalization.CultureInfo.CurrentCulture,
+ Strings.ContentTypeRegistry_CannotRemoveBaseType,
+ type.TypeName,
+ derivedType.TypeName));
}
// If there are file extensions using this content type we won't allow removing it
if (this.maps.FileExtensionToContentTypeMap.Values.Any(c => c == type))
{
// If there are file extensions using this content type we won't allow removing it
- throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.ContentTypeRegistry_CannotRemoveTypeUsedByFileExtensions, type.TypeName));
+ throw new InvalidOperationException(
+ string.Format(
+ System.Globalization.CultureInfo.CurrentCulture,
+ Strings.ContentTypeRegistry_CannotRemoveTypeUsedByFileExtensions,
+ type.TypeName));
}
// If there are file extensions using this content type we won't allow removing it
if (this.maps.FileNameToContentTypeMap.Values.Any(c => c == type))
{
// If there are file extensions using this content type we won't allow removing it
- throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.ContentTypeRegistry_CannotRemoveTypeUsedByFileExtensions, type.TypeName));
+ throw new InvalidOperationException(
+ string.Format(
+ System.Globalization.CultureInfo.CurrentCulture,
+ Strings.ContentTypeRegistry_CannotRemoveTypeUsedByFileExtensions,
+ type.TypeName));
}
var newMaps = new MapCollection(oldMaps.NameToContentTypeMap.Remove(typeName),
@@ -394,7 +405,7 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
var typeImpl = type as ContentTypeImpl;
if (typeImpl == null)
{
- throw new ArgumentException(nameof(type));
+ throw new ArgumentNullException(nameof(type));
}
else if (typeImpl == UnknownContentTypeImpl)
{
@@ -406,19 +417,16 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
public IContentType GetContentTypeForMimeType(string mimeType)
{
- if (string.IsNullOrWhiteSpace(mimeType))
- {
- throw new ArgumentException(nameof(mimeType));
- }
+ Requires.NotNullOrWhiteSpace(mimeType, nameof(mimeType));
this.BuildContentTypes();
ContentTypeImpl contentType = null;
if (!this.maps.MimeTypeToContentTypeMap.TryGetValue(mimeType, out contentType))
{
- if (mimeType.StartsWith(BaseMimePrefix))
+ if (mimeType.StartsWith(BaseMimePrefix, StringComparison.Ordinal))
{
- if (!(mimeType.StartsWith(MimePrefix) && this.maps.NameToContentTypeMap.TryGetValue(mimeType.Substring(MimePrefix.Length), out contentType)))
+ if (!(mimeType.StartsWith(MimePrefix, StringComparison.Ordinal) && this.maps.NameToContentTypeMap.TryGetValue(mimeType.Substring(MimePrefix.Length), out contentType)))
{
this.maps.NameToContentTypeMap.TryGetValue(mimeType.Substring(BaseMimePrefix.Length), out contentType);
}
@@ -432,17 +440,21 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
#region IFileExtensionRegistryService Members
public IContentType GetContentTypeForExtension(string extension)
{
- if (extension == null)
+ ContentTypeImpl contentType = null;
+ if (string.IsNullOrEmpty(extension))
{
- throw new ArgumentNullException(nameof(extension));
+ if (extension == null)
+ {
+ throw new ArgumentNullException(nameof(extension));
+ }
}
+ else
+ {
- this.BuildContentTypes();
-
- ContentTypeImpl contentType = null;
- this.maps.FileExtensionToContentTypeMap.TryGetValue(RemoveExtensionDot(extension), out contentType);
+ this.BuildContentTypes();
+ this.maps.FileExtensionToContentTypeMap.TryGetValue(RemoveExtensionDot(extension), out contentType);
+ }
- // TODO: should we return null if contentType is null?
return contentType ?? ContentTypeRegistryImpl.UnknownContentTypeImpl;
}
@@ -467,15 +479,12 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
public void AddFileExtension(string extension, IContentType contentType)
{
- if (string.IsNullOrWhiteSpace(extension))
- {
- throw new ArgumentException(nameof(extension));
- }
+ Requires.NotNullOrWhiteSpace(extension, nameof(extension));
var contentTypeImpl = contentType as ContentTypeImpl;
if ((contentTypeImpl == null) || (contentTypeImpl == UnknownContentTypeImpl))
{
- throw new ArgumentException(nameof(contentType));
+ throw new ArgumentException(nameof(contentType) + " cannot be null or unknown");
}
this.BuildContentTypes();
@@ -489,9 +498,9 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
{
if (type != contentTypeImpl)
{
- throw new InvalidOperationException
- (String.Format(System.Globalization.CultureInfo.CurrentUICulture,
- Strings.FileExtensionRegistry_NoMultipleContentTypes, extension));
+ throw new InvalidOperationException(
+ string.Format(System.Globalization.CultureInfo.CurrentCulture,
+ Strings.FileExtensionRegistry_NoMultipleContentTypes, extension));
}
return;
@@ -550,17 +559,21 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
#region IFileExtensionRegistryService2 Members
public IContentType GetContentTypeForFileName(string fileName)
{
- if (fileName == null)
+ ContentTypeImpl contentType = null;
+
+ if (string.IsNullOrWhiteSpace(fileName))
{
- throw new ArgumentNullException(nameof(fileName));
+ if (fileName == null)
+ {
+ throw new ArgumentNullException(nameof(fileName));
+ }
+ }
+ else
+ {
+ this.BuildContentTypes();
+ this.maps.FileNameToContentTypeMap.TryGetValue(fileName, out contentType);
}
- this.BuildContentTypes();
-
- ContentTypeImpl contentType = null;
- this.maps.FileNameToContentTypeMap.TryGetValue(fileName, out contentType);
-
- // TODO: should we return null if contentType is null?
return contentType ?? ContentTypeRegistryImpl.UnknownContentTypeImpl;
}
@@ -572,21 +585,16 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
}
// No need to lock, we are calling locking public method.
- var fileNameContentType = this.GetContentTypeForFileName(name);
+ var contentType = this.GetContentTypeForFileName(name);
// Attempt to use extension as fallback ContentType if file name isn't recognized.
- if (fileNameContentType == ContentTypeRegistryImpl.UnknownContentTypeImpl)
+ if (contentType == ContentTypeRegistryImpl.UnknownContentTypeImpl)
{
var extension = Path.GetExtension(name);
-
- if (!string.IsNullOrEmpty(extension))
- {
- // No need to lock, we are calling locking public method.
- return this.GetContentTypeForExtension(extension);
- }
+ contentType = this.GetContentTypeForExtension(extension);
}
- return fileNameContentType;
+ return contentType;
}
public IEnumerable<string> GetFileNamesForContentType(IContentType contentType)
@@ -612,13 +620,13 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
{
if (string.IsNullOrWhiteSpace(fileName))
{
- throw new ArgumentException(nameof(fileName));
+ throw new ArgumentException(nameof(fileName) + " cannot be null or whitespace");
}
var contentTypeImpl = contentType as ContentTypeImpl;
if ((contentTypeImpl == null) || (contentTypeImpl == UnknownContentTypeImpl))
{
- throw new ArgumentException(nameof(contentType));
+ throw new ArgumentException(nameof(contentType) + " cannot be null or unknown");
}
this.BuildContentTypes();
@@ -631,9 +639,9 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
{
if (type != contentTypeImpl)
{
- throw new InvalidOperationException
- (String.Format(System.Globalization.CultureInfo.CurrentUICulture,
- Strings.FileExtensionRegistry_NoMultipleContentTypes, fileName));
+ throw new InvalidOperationException(
+ string.Format(System.Globalization.CultureInfo.CurrentCulture,
+ Strings.FileExtensionRegistry_NoMultipleContentTypes, fileName));
}
return;
@@ -688,7 +696,18 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
#endregion
#region IFilePathRegistryService Members
- public IContentType GetContentTypeForPath(string filePath)
+ public IContentType GetContentTypeForFilePath(string filePath)
+ {
+ return this.InternalGetContentTypeForPath(filePath) ?? this.GetContentTypeForFileNameOrExtension(Path.GetFileName(filePath));
+ }
+
+ public IContentType GetContentTypeForFilePathOnly(string filePath)
+ {
+ return this.InternalGetContentTypeForPath(filePath) ?? ContentTypeRegistryImpl.UnknownContentTypeImpl;
+ }
+ #endregion
+
+ private IContentType InternalGetContentTypeForPath(string filePath)
{
if (filePath == null)
{
@@ -696,28 +715,32 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
}
string fileName = Path.GetFileName(filePath);
- string extension = Path.GetExtension(filePath);
- var providers = OrderedFilePathToContentTypeProductions.Where(md =>
- (md.Metadata.FileExtension == null || md.Metadata.FileExtension.Equals(extension, StringComparison.OrdinalIgnoreCase)) &&
- (md.Metadata.FileName == null || md.Metadata.FileName.Equals(fileName, StringComparison.OrdinalIgnoreCase)));
+ string extension = Path.GetExtension(fileName);
- IContentType contentType = null;
- foreach(var curProvider in providers)
+ for (int i = 0; (i < this.OrderedFilePathToContentTypeProductions.Count); ++i)
{
- if (curProvider.Value.TryGetContentTypeForFilePath(filePath, out IContentType curContentType))
+ var provider = this.OrderedFilePathToContentTypeProductions[i];
+ if (WildcardMatch(provider.Metadata.FileExtension, extension) ||
+ ((provider.Metadata.FileName != null) && provider.Metadata.FileName.Equals(fileName, StringComparison.OrdinalIgnoreCase)))
{
- contentType = curContentType;
- break;
+ if (provider.Value.TryGetContentTypeForFilePath(filePath, out IContentType contentType))
+ {
+ return contentType;
+ }
}
}
- return contentType ?? ContentTypeRegistryImpl.UnknownContentTypeImpl;
+ return null;
+ }
+
+ private static bool WildcardMatch(string s1, string s2)
+ {
+ return (s1 != null) && ((s1.Equals("*", StringComparison.Ordinal)) || s1.Equals(s2, StringComparison.OrdinalIgnoreCase));
}
- #endregion
private static string RemoveExtensionDot(string extension)
{
- if (extension.StartsWith("."))
+ if (extension.StartsWith(".", StringComparison.Ordinal))
{
return extension.TrimStart('.');
}
@@ -838,4 +861,13 @@ namespace Microsoft.VisualStudio.Utilities.Implementation
#endregion
}
}
+
+ public interface IFilePathToContentTypeMetadata : IOrderable
+ {
+ [System.ComponentModel.DefaultValue(null)]
+ string FileExtension { get; }
+
+ [System.ComponentModel.DefaultValue(null)]
+ string FileName { get; }
+ }
}
diff --git a/src/Core/Impl/ContentType/IFileExtensionToContentTypeMetadata.cs b/src/Core/Impl/ContentType/IFileExtensionToContentTypeMetadata.cs
deleted file mode 100644
index 4e3bbd9..0000000
--- a/src/Core/Impl/ContentType/IFileExtensionToContentTypeMetadata.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// 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.Utilities.Implementation
-{
- using System;
- using System.Collections.Generic;
-
- public interface IFileExtensionToContentTypeMetadata
- {
- string FileExtension { get; }
- IEnumerable<string> ContentTypes { get; }
- }
-}
-
diff --git a/src/Core/Impl/ContentType/IFileNameToContentTypeMetadata.cs b/src/Core/Impl/ContentType/IFileNameToContentTypeMetadata.cs
deleted file mode 100644
index 48ec87a..0000000
--- a/src/Core/Impl/ContentType/IFileNameToContentTypeMetadata.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// 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.Utilities.Implementation
-{
- using System;
- using System.Collections.Generic;
-
- public interface IFileNameToContentTypeMetadata
- {
- string FileName { get; }
- IEnumerable<string> ContentTypes { get; }
- }
-}
-
diff --git a/src/Core/Impl/ContentType/IFilePathRegistryService.cs b/src/Core/Impl/ContentType/IFilePathRegistryService.cs
deleted file mode 100644
index 451d95c..0000000
--- a/src/Core/Impl/ContentType/IFilePathRegistryService.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-//
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-//
-
-namespace Microsoft.VisualStudio.Utilities
-{
- public interface IFilePathRegistryService
- {
- IContentType GetContentTypeForPath(string filePath);
- }
-}
diff --git a/src/Core/Impl/ContentType/IFilePathToContentTypeMetadata.cs b/src/Core/Impl/ContentType/IFilePathToContentTypeMetadata.cs
deleted file mode 100644
index eb15212..0000000
--- a/src/Core/Impl/ContentType/IFilePathToContentTypeMetadata.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// 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.Utilities.Implementation
-{
- public interface IFilePathToContentTypeMetadata : IOrderable
- {
- [System.ComponentModel.DefaultValue(null)]
- string FileExtension { get; }
-
- [System.ComponentModel.DefaultValue(null)]
- string FileName { get; }
- }
-}
diff --git a/src/Core/Impl/ContentType/IFilePathToContentTypeProvider.cs b/src/Core/Impl/ContentType/IFilePathToContentTypeProvider.cs
deleted file mode 100644
index a4c624b..0000000
--- a/src/Core/Impl/ContentType/IFilePathToContentTypeProvider.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-//
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-//
-
-namespace Microsoft.VisualStudio.Utilities
-{
- public interface IFilePathToContentTypeProvider
- {
- bool TryGetContentTypeForFilePath(string filePath, out IContentType contentType);
- }
-}
diff --git a/src/Core/Impl/ContentType/StringToContentTypesMap.cs b/src/Core/Impl/ContentType/StringToContentTypesMap.cs
deleted file mode 100644
index 52fae34..0000000
--- a/src/Core/Impl/ContentType/StringToContentTypesMap.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-//
-// 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.Utilities.Implementation
-{
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.Linq;
-
- internal sealed class StringToContentTypesMap
- {
- // Map of extensions to their corresponding content types
- private Dictionary<string, IContentType> stringMap;
-
- public StringToContentTypesMap(IEnumerable<Tuple<string, IContentType>> mappings)
- {
-
- if (mappings == null)
- {
- throw new ArgumentNullException(nameof(mappings));
- }
-
- this.stringMap = new Dictionary<string, IContentType>(StringComparer.OrdinalIgnoreCase);
-
- foreach (var mapping in mappings)
- {
- // Any failures should ideally be logged somehow.
- TryAddString(mapping.Item1, mapping.Item2);
- }
- }
-
- public IContentType GetContentTypeForString(string str)
- {
- if (str == null)
- {
- throw new ArgumentNullException(nameof(str));
- }
-
- IContentType contentType;
-
- if (!this.stringMap.TryGetValue(str, out contentType))
- {
- return null;
- }
-
- return contentType;
- }
-
- public IEnumerable<string> GetStringsForContentType(IContentType contentType)
- {
- if (contentType == null)
- {
- throw new ArgumentNullException(nameof(contentType));
- }
-
- // Asymptotically slow, however, after searching the VS code base, we found that
- // looking up extensions for ContentType is only used by tests, and is probably
- // rarely used. This method used be backed by a second map for quick lookup,
- // but for simplicity, we're going to move to a single map, barring any regressions.
- return this.stringMap
- .Where(pair => pair.Value == contentType)
- .Select(pair => pair.Key);
- }
-
- public void AddMapping(string str, IContentType contentType)
- {
- if (str == null)
- {
- throw new ArgumentNullException(nameof(str));
- }
-
- if (contentType == null)
- {
- throw new ArgumentNullException(nameof(contentType));
- }
-
- if (!TryAddString(str, contentType))
- {
- throw new InvalidOperationException
- (String.Format(System.Globalization.CultureInfo.CurrentUICulture,
- Strings.FileExtensionRegistry_NoMultipleContentTypes, str));
- }
- }
-
- public void RemoveMapping(string str)
- {
- if (str == null)
- {
- throw new ArgumentNullException(nameof(str));
- }
-
- this.stringMap.Remove(str);
- }
-
- private bool TryAddString(string str, IContentType contentType)
- {
- if (str == null)
- {
- throw new ArgumentNullException(nameof(str));
- }
-
- if (contentType == null)
- {
- throw new ArgumentNullException(nameof(contentType));
- }
-
- // Check if the string is already registered
- IContentType existingContentType;
- if (this.stringMap.TryGetValue(str, out existingContentType))
- {
- // Return false if there is a conflict.
- return contentType == existingContentType;
- }
- else
- {
- // A new string - lets add it to the map
- this.stringMap.Add(str, contentType);
- }
-
- return true;
- }
- }
-}
-
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/AsyncCompletionSessionDataSnapshot.cs b/src/Language/Def/Language/AsyncCompletion/Data/AsyncCompletionSessionDataSnapshot.cs
new file mode 100644
index 0000000..d947d61
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/AsyncCompletionSessionDataSnapshot.cs
@@ -0,0 +1,76 @@
+using System.Collections.Immutable;
+using Microsoft.VisualStudio.Text;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Contains data of <see cref="IAsyncCompletionSession"/> valid at a specific, instantenous moment pertinent to current computation.
+ /// This data is passed to <see cref="IAsyncCompletionItemManager"/> to filter the list and select appropriate item.
+ /// </summary>
+ public class AsyncCompletionSessionDataSnapshot
+ {
+ /// <summary>
+ /// Set of <see cref="CompletionItem"/>s to filter and sort, originally returned from <see cref="IAsyncCompletionItemManager.SortCompletionListAsync"/>.
+ /// </summary>
+ public ImmutableArray<CompletionItem> InitialSortedList { get; }
+
+ /// <summary>
+ /// The <see cref="ITextSnapshot"/> applicable for this computation. The snapshot comes from the view's data buffer.
+ /// </summary>
+ public ITextSnapshot Snapshot { get; }
+
+ /// <summary>
+ /// The <see cref="InitialTrigger"/> that started this completion session.
+ /// </summary>
+ public InitialTrigger InitialTrigger { get; }
+
+ /// <summary>
+ /// The <see cref="UpdateTrigger"/> for this update.
+ /// </summary>
+ public UpdateTrigger UpdateTrigger { get; }
+
+ /// <summary>
+ /// Filters, their availability and selection state.
+ /// </summary>
+ public ImmutableArray<CompletionFilterWithState> SelectedFilters { get; }
+
+ /// <summary>
+ /// Inidicates whether the session is using soft selection
+ /// </summary>
+ public bool IsSoftSelected { get; }
+
+ /// <summary>
+ /// Inidicates whether the session displays a suggestion item.
+ /// </summary>
+ public bool DisplaySuggestionItem { get; }
+
+ /// <summary>
+ /// Constructs <see cref="AsyncCompletionSessionDataSnapshot"/>
+ /// </summary>
+ /// <param name="initialSortedList">Set of <see cref="CompletionItem"/>s to filter and sort, originally returned from <see cref="IAsyncCompletionItemManager.SortCompletionListAsync"/></param>
+ /// <param name="snapshot">The <see cref="ITextSnapshot"/> applicable for this computation. The snapshot comes from the view's data buffer</param>
+ /// <param name="initialTrigger">The <see cref="InitialTrigger"/> that started this completion session</param>
+ /// <param name="updateTrigger">The <see cref="UpdateTrigger"/> for this update</param>
+ /// <param name="selectedFilters">Filters, their availability and selection state</param>
+ /// <param name="isSoftSelected">Inidicates whether the session is using soft selection</param>
+ /// <param name="displaySuggestionItem">Inidicates whether the session has a suggestion item</param>
+ public AsyncCompletionSessionDataSnapshot(
+ ImmutableArray<CompletionItem> initialSortedList,
+ ITextSnapshot snapshot,
+ InitialTrigger initialTrigger,
+ UpdateTrigger updateTrigger,
+ ImmutableArray<CompletionFilterWithState> selectedFilters,
+ bool isSoftSelected,
+ bool displaySuggestionItem
+ )
+ {
+ InitialSortedList = initialSortedList;
+ Snapshot = snapshot;
+ InitialTrigger = initialTrigger;
+ UpdateTrigger = updateTrigger;
+ SelectedFilters = selectedFilters;
+ IsSoftSelected = isSoftSelected;
+ DisplaySuggestionItem = displaySuggestionItem;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/AsyncCompletionSessionInitialDataSnapshot.cs b/src/Language/Def/Language/AsyncCompletion/Data/AsyncCompletionSessionInitialDataSnapshot.cs
new file mode 100644
index 0000000..8b3f3dd
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/AsyncCompletionSessionInitialDataSnapshot.cs
@@ -0,0 +1,44 @@
+using System.Collections.Immutable;
+using Microsoft.VisualStudio.Text;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Contains data of <see cref="IAsyncCompletionSession"/> valid at a specific, instantenous moment pertinent to current computation.
+ /// This data is passed to <see cref="IAsyncCompletionItemManager"/> to initially sort the list prior to filtering and selecting.
+ /// </summary>
+ public class AsyncCompletionSessionInitialDataSnapshot
+ {
+ /// <summary>
+ /// Set of <see cref="CompletionItem"/>s to sort.
+ /// </summary>
+ public ImmutableArray<CompletionItem> InitialList { get; }
+
+ /// <summary>
+ /// The <see cref="ITextSnapshot"/> applicable for this computation. The snapshot comes from the view's data buffer.
+ /// </summary>
+ public ITextSnapshot Snapshot { get; }
+
+ /// <summary>
+ /// The <see cref="InitialTrigger"/> that started this completion session.
+ /// </summary>
+ public InitialTrigger InitialTrigger { get; }
+
+ /// <summary>
+ /// Constructs <see cref="AsyncCompletionSessionInitialDataSnapshot"/>
+ /// </summary>
+ /// <param name="initialList">Set of <see cref="CompletionItem"/>s to sort</param>
+ /// <param name="snapshot">The <see cref="ITextSnapshot"/> applicable for this computation. The snapshot comes from the view's data buffer</param>
+ /// <param name="initialTrigger">The <see cref="InitialTrigger"/> that started this completion session</param>
+ public AsyncCompletionSessionInitialDataSnapshot(
+ ImmutableArray<CompletionItem> initialList,
+ ITextSnapshot snapshot,
+ InitialTrigger initialTrigger
+ )
+ {
+ InitialList = initialList;
+ Snapshot = snapshot;
+ InitialTrigger = initialTrigger;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CommitBehavior.cs b/src/Language/Def/Language/AsyncCompletion/Data/CommitBehavior.cs
new file mode 100644
index 0000000..659fa2a
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CommitBehavior.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Instructs the editor how to behave after committing a <see cref="CompletionItem"/>.
+ /// </summary>
+ [Flags]
+#pragma warning disable CA1714 // Flags enums should have plural names
+ public enum CommitBehavior
+#pragma warning restore CA1714 // Flags enums should have plural names
+ {
+ /// <summary>
+ /// Use the default behavior,
+ /// that is, to propagate TypeChar command, but surpress ReturnKey and TabKey commands.
+ /// </summary>
+ None = 0b0000,
+
+ /// <summary>
+ /// Surpresses further invocation of the TypeChar command handlers.
+ /// By default, editor invokes these command handlers to enable features such as brace completion.
+ /// </summary>
+ SuppressFurtherTypeCharCommandHandlers = 0b0001,
+
+ /// <summary>
+ /// Raises further invocation of the ReturnKey and Tab command handlers.
+ /// By default, editor doesn't invoke ReturnKey and Tab command handlers after committing completion session.
+ /// </summary>
+ RaiseFurtherReturnKeyAndTabKeyCommandHandlers = 0b0010,
+
+ /// <summary>
+ /// Cancels the commit operation, does not call any other <see cref="IAsyncCompletionCommitManager.TryCommit(Text.Editor.ITextView, Text.ITextBuffer, CompletionItem, Text.ITrackingSpan, char, System.Threading.CancellationToken)"/>.
+ /// Functionally, acts as if the typed character was not a commit character,
+ /// allowing the user to continue working with the <see cref="IAsyncCompletionSession"/>
+ /// </summary>
+ CancelCommit = 0b0100,
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CommitResult.cs b/src/Language/Def/Language/AsyncCompletion/Data/CommitResult.cs
new file mode 100644
index 0000000..5b7af68
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CommitResult.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Tracks whether the commit occured, and provides instructions for behavior after committing.
+ /// </summary>
+ public struct CommitResult : IEquatable<CommitResult>
+ {
+ /// <summary>
+ /// Marks this commit as handled. No other <see cref="IAsyncCompletionCommitManager"/> will be asked to commit.
+ /// </summary>
+ public readonly static CommitResult Handled = new CommitResult(isHandled: true, behavior: CommitBehavior.None);
+
+ /// <summary>
+ /// Marks this commit as unhandled. Another <see cref="IAsyncCompletionCommitManager"/> will be asked to commit.
+ /// </summary>
+ public readonly static CommitResult Unhandled = new CommitResult(isHandled: false, behavior: CommitBehavior.None);
+
+ /// <summary>
+ /// Whether the commit occured.
+ /// If true, no other <see cref="IAsyncCompletionCommitManager"/> will be asked to commit.
+ /// If false, another <see cref="IAsyncCompletionCommitManager"/> will be asked to commit.
+ /// </summary>
+ public bool IsHandled { get; }
+
+ /// <summary>
+ /// Desired behavior after committing, respected even when <see cref="IsHandled"/> is unset.
+ /// </summary>
+ public CommitBehavior Behavior { get; }
+
+ /// <summary>
+ /// Creates a <see cref="CommitResult"/> with specified properties.
+ /// </summary>
+ /// <param name="isHandled">Whether the commit occured</param>
+ /// <param name="behavior">Desired behavior after committing</param>
+ public CommitResult(bool isHandled, CommitBehavior behavior)
+ {
+ IsHandled = isHandled;
+ Behavior = behavior;
+ }
+
+ bool IEquatable<CommitResult>.Equals(CommitResult other) => IsHandled.Equals(other.IsHandled) && Behavior.Equals(other.Behavior);
+
+ public override bool Equals(object other) => (other is CommitResult otherCR) ? ((IEquatable<CommitResult>)this).Equals(otherCR) : false;
+
+ public static bool operator ==(CommitResult left, CommitResult right) => left.Equals(right);
+
+ public static bool operator !=(CommitResult left, CommitResult right) => !(left == right);
+
+ public override int GetHashCode() => (Behavior.GetHashCode() << 1) | (IsHandled ? 1 : 0);
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionClosedEventArgs.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionClosedEventArgs.cs
new file mode 100644
index 0000000..17cccd6
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionClosedEventArgs.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This class is used to notify completion's logic when the UI closes
+ /// </summary>
+ public sealed class CompletionClosedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// <see cref="ITextView"/> that hosted completion UI
+ /// </summary>
+ public ITextView TextView { get; }
+
+ /// <summary>
+ /// Constructs instance of <see cref="CompletionClosedEventArgs"/>.
+ /// </summary>
+ /// <param name="textView"><see cref="ITextView"/> that hosted this completion UI</param>
+ public CompletionClosedEventArgs(ITextView textView)
+ {
+ TextView = textView;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionContext.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionContext.cs
new file mode 100644
index 0000000..d098d3a
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionContext.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using Microsoft.VisualStudio.Text;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This type is used to transfer data from <see cref="IAsyncCompletionSource"/>
+ /// to <see cref="IAsyncCompletionBroker"/> and further to <see cref="IAsyncCompletionItemManager"/>
+ /// </summary>
+ [DebuggerDisplay("{Items.Length} items")]
+ public sealed class CompletionContext
+ {
+ /// <summary>
+ /// Empty completion context, when <see cref="IAsyncCompletionSource"/> offers no items pertinent to given location.
+ /// </summary>
+ public static CompletionContext Empty { get; } = new CompletionContext(ImmutableArray<CompletionItem>.Empty);
+
+ /// <summary>
+ /// Set of completion items available at a location
+ /// </summary>
+ public ImmutableArray<CompletionItem> Items { get; }
+
+ /// <summary>
+ /// Recommends the initial selection method for the completion list.
+ /// When <see cref="SuggestionItemOptions"/> is defined, "soft selection" will be used without a need to set this property.
+ /// </summary>
+ public InitialSelectionHint SelectionHint { get; }
+
+ /// <summary>
+ /// When defined, uses suggestion mode with options specified in this object.
+ /// When null, this context does not activate the suggestion mode.
+ /// Suggestion mode puts selection in "soft selection" mode without need to set <see cref="SelectionHint"/>
+ /// </summary>
+ public SuggestionItemOptions SuggestionItemOptions { get; }
+
+ /// <summary>
+ /// Constructs <see cref="CompletionContext"/> with specified <see cref="CompletionItem"/>s,
+ /// with recommendation to not use suggestion mode and to use use regular selection.
+ /// </summary>
+ /// <param name="items">Available completion items. If none are available, use <code>CompletionContext.Default</code></param>
+ public CompletionContext(ImmutableArray<CompletionItem> items)
+ : this(items, suggestionItemOptions: null, selectionHint: InitialSelectionHint.RegularSelection)
+ {
+ }
+
+ /// <summary>
+ /// Constructs <see cref="CompletionContext"/> with specified <see cref="CompletionItem"/>s,
+ /// with recommendation to use suggestion mode and to use regular selection.
+ /// </summary>
+ /// <param name="items">Available completion items</param>
+ /// <param name="suggestionItemOptions">Suggestion item options, or null to not use suggestion mode. Default is <code>null</code></param>
+ public CompletionContext(
+ ImmutableArray<CompletionItem> items,
+ SuggestionItemOptions suggestionItemOptions)
+ : this(items, suggestionItemOptions, InitialSelectionHint.RegularSelection)
+ {
+ }
+
+ /// <summary>
+ /// Constructs <see cref="CompletionContext"/> with specified <see cref="CompletionItem"/>s,
+ /// with recommendation to use suggestion mode item and to use a specific selection mode.
+ /// </summary>
+ /// <param name="items">Available completion items</param>
+ /// <param name="suggestionItemOptions">Suggestion mode options, or null to not use suggestion mode. Default is <code>null</code></param>
+ /// <param name="selectionHint">Recommended selection mode. Suggestion mode automatically sets soft selection Default is <code>InitialSelectionHint.RegularSelection</code></param>
+ public CompletionContext(
+ ImmutableArray<CompletionItem> items,
+ SuggestionItemOptions suggestionItemOptions,
+ InitialSelectionHint selectionHint)
+ {
+ if (items.IsDefault)
+ throw new ArgumentException("Array must be initialized", nameof(items));
+ Items = items;
+ SelectionHint = selectionHint;
+ SuggestionItemOptions = suggestionItemOptions;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionFilter.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionFilter.cs
new file mode 100644
index 0000000..b9e2e1c
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionFilter.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Diagnostics;
+using Microsoft.VisualStudio.Text.Adornments;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Identifies a filter that toggles exclusive display of <see cref="CompletionItem"/>s that reference it.
+ /// </summary>
+ /// <remarks>
+ /// These instances should be singletons. All <see cref="CompletionItem"/>s that should be filtered
+ /// using the same filter button must use the same reference to the instance of <see cref="CompletionFilter"/>.
+ /// </remarks>
+ /// <example>
+ /// static CompletionFilter MyFilter = new CompletionFilter("My items", "m", MyItemsImageElement);
+ /// </example>
+ [DebuggerDisplay("{DisplayText}")]
+ public sealed class CompletionFilter
+ {
+ /// <summary>
+ /// Localized name of this filter.
+ /// </summary>
+ public string DisplayText { get; }
+
+ /// <summary>
+ /// Key used in a keyboard shortcut that toggles this filter.
+ /// </summary>
+ public string AccessKey { get; }
+
+ /// <summary>
+ /// <see cref="ImageElement"/> that represents this filter.
+ /// </summary>
+ public ImageElement Image { get; }
+
+ /// <summary>
+ /// Constructs an instance of CompletionFilter.
+ /// </summary>
+ /// <param name="displayText">Name of this filter</param>
+ /// <param name="accessKey">Key used in a keyboard shortcut that toggles this filter.</param>
+ /// <param name="image">Image that represents this filter</param>
+ public CompletionFilter(string displayText, string accessKey, ImageElement image)
+ {
+ if (string.IsNullOrWhiteSpace(displayText))
+ {
+ throw new ArgumentException("Display text must be non-empty", nameof(displayText));
+ }
+ if (string.IsNullOrWhiteSpace(accessKey))
+ {
+ throw new ArgumentException("Access key must be non-empty", nameof(accessKey));
+ }
+
+ DisplayText = displayText;
+ AccessKey = accessKey;
+ Image = image;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionFilterEventArgs.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionFilterEventArgs.cs
new file mode 100644
index 0000000..e8bea33
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionFilterEventArgs.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This class is used to notify completion's logic of selection change in the filter UI
+ /// </summary>
+ public sealed class CompletionFilterChangedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Current state of the filters
+ /// </summary>
+ public ImmutableArray<CompletionFilterWithState> Filters { get; }
+
+ /// <summary>
+ /// Constructs instance of <see cref="CompletionFilterChangedEventArgs"/>.
+ /// </summary>
+ /// <param name="filters">Current state of the filters</param>
+ public CompletionFilterChangedEventArgs(
+ ImmutableArray<CompletionFilterWithState> filters)
+ {
+ if (filters.IsDefault)
+ throw new ArgumentException("Array must be initialized", nameof(filters));
+ this.Filters = filters;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionFilterWithState.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionFilterWithState.cs
new file mode 100644
index 0000000..7712e93
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionFilterWithState.cs
@@ -0,0 +1,82 @@
+using System;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Immutable data transfer object used to communicate between the completion session and completion UI
+ /// </summary>
+ public sealed class CompletionFilterWithState
+ {
+ /// <summary>
+ /// Reference to the completion filter
+ /// </summary>
+ public CompletionFilter Filter { get; }
+
+ /// <summary>
+ /// Whether the filter is available.
+ /// Filter should be available when there are visible <see cref="CompletionItem"/>s that define this <see cref="Filter"/> in their <see cref="CompletionItem.Filters"/>
+ /// </summary>
+ public bool IsAvailable { get; }
+
+ /// <summary>
+ /// Whether the filter is selected by the user.
+ /// </summary>
+ public bool IsSelected { get; }
+
+ /// <summary>
+ /// Constructs a new instance of <see cref="CompletionFilterWithState"/>.
+ /// </summary>
+ /// <param name="filter">Reference to <see cref="CompletionFilter"/></param>
+ /// <param name="isAvailable">Whether this <see cref="CompletionFilter"/> is available</param>
+ public CompletionFilterWithState(CompletionFilter filter, bool isAvailable)
+ : this(filter, isAvailable, isSelected: false)
+ { }
+
+ /// <summary>
+ /// Constructs a new instance of <see cref="CompletionFilterWithState"/> when selected state is known.
+ /// </summary>
+ /// <param name="filter">Reference to <see cref="CompletionFilter"/></param>
+ /// <param name="isAvailable">Whether this <see cref="CompletionFilter"/> is available</param>
+ /// <param name="isSelected">Whether this <see cref="CompletionFilter"/> is selected</param>
+ public CompletionFilterWithState(CompletionFilter filter, bool isAvailable, bool isSelected)
+ {
+ Filter = filter ?? throw new ArgumentNullException(nameof(filter));
+ IsAvailable = isAvailable;
+ IsSelected = isSelected;
+ }
+
+ /// <summary>
+ /// Returns instance of <see cref="CompletionFilterWithState"/> with specified <see cref="IsAvailable"/>
+ /// </summary>
+ /// <param name="isAvailable">Value to use for <see cref="IsAvailable"/></param>
+ /// <returns>Updated instance of <see cref="CompletionFilterWithState"/></returns>
+ public CompletionFilterWithState WithAvailability(bool isAvailable)
+ {
+ return this.IsAvailable == isAvailable
+ ? this
+ : new CompletionFilterWithState(Filter, isAvailable, IsSelected);
+ }
+
+ /// <summary>
+ /// Returns instance of <see cref="CompletionFilterWithState"/> with specified <see cref="IsSelected"/>
+ /// </summary>
+ /// <param name="availability">Value to use for <see cref="IsSelected"/></param>
+ /// <returns>Updated instance of <see cref="CompletionFilterWithState"/></returns>
+ public CompletionFilterWithState WithSelected(bool isSelected)
+ {
+ return this.IsSelected == isSelected
+ ? this
+ : new CompletionFilterWithState(Filter, IsAvailable, isSelected);
+ }
+
+ /// <summary>
+ /// Override for nice debugger display
+ /// </summary>
+ public override string ToString()
+ {
+ var availableStatus = IsAvailable ? "available" : "unavailable";
+ var selectedStatus = IsSelected ? "selected" : "not selected";
+ return $"{Filter.DisplayText} - {availableStatus}, {selectedStatus}";
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionItem.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItem.cs
new file mode 100644
index 0000000..6168457
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItem.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.VisualStudio.Core.Imaging;
+using Microsoft.VisualStudio.Text.Adornments;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This class, returned from <see cref="IAsyncCompletionSource"/>, represents a single entry
+ /// to be displayed in the completion UI. This class implements <see cref="IPropertyOwner"/>
+ /// </summary>
+ [DebuggerDisplay("{DisplayText}")]
+ public sealed class CompletionItem : IPropertyOwner
+ {
+ /// <summary>
+ /// Text used in the UI
+ /// </summary>
+ public string DisplayText { get; }
+
+ /// <summary>
+ /// Text inserted when completing this item
+ /// </summary>
+ public string InsertText { get; }
+
+ /// <summary>
+ /// Text used by <see cref="IAsyncCompletionItemManager"/> when sorting against other items
+ /// </summary>
+ public string SortText { get; }
+
+ /// <summary>
+ /// Text used by <see cref="IAsyncCompletionItemManager"/> when matching against the applicable span
+ /// </summary>
+ public string FilterText { get; }
+
+ /// <summary>
+ /// Reference to the instance that will provide tooltip and custom commit method.
+ /// This should be the same instance as the one that created this <see cref="CompletionItem"/>
+ /// </summary>
+ public IAsyncCompletionSource Source { get; }
+
+ /// <summary>
+ /// <see cref="ImmutableArray"/> of references to <see cref="CompletionFilter"/>s applicable to this item
+ /// </summary>
+ public ImmutableArray<CompletionFilter> Filters { get; }
+
+ /// <summary>
+ /// Image displayed in the UI
+ /// </summary>
+ public ImageElement Icon { get; }
+
+ /// <summary>
+ /// Additional text to display in the UI, after <see cref="DisplayText"/>.
+ /// This text has less emphasis than <see cref="DisplayText"/> and is usually right-aligned.
+ /// </summary>
+ public string Suffix { get; }
+
+ /// <summary>
+ /// Additional images to display in the UI.
+ /// Usually, these images are displayed on the far right side of the UI.
+ /// </summary>
+ public ImmutableArray<ImageElement> AttributeIcons { get; }
+
+ /// <summary>
+ /// The collection of properties controlled by the property owner. See <see cref="IPropertyOwner.Properties"/>
+ /// </summary>
+ public PropertyCollection Properties { get; }
+
+ /// <summary>
+ /// Creates a completion item whose <see cref="DisplayText"/>, <see cref="InsertText"/>, <see cref="SortText"/> and <see cref="FilterText"/> are all the same,
+ /// and there are no icon, filter, suffix nor attribute icons associated with this item.
+ /// </summary>
+ /// <param name="displayText">Text to use in the UI, when sorting, filtering and completing</param>
+ /// <param name="source">Reference to <see cref="IAsyncCompletionSource"/> that created this item</param>
+ public CompletionItem(string displayText, IAsyncCompletionSource source)
+ : this(displayText, insertText: displayText, sortText: displayText, filterText: displayText,
+ source: source, filters: ImmutableArray<CompletionFilter>.Empty, icon: default(ImageElement),
+ suffix: string.Empty, attributeIcons: ImmutableArray<ImageElement>.Empty)
+ {
+ }
+
+ /// <summary>
+ /// Creates a completion item whose <see cref="DisplayText"/>, <see cref="InsertText"/>, <see cref="SortText"/> and <see cref="FilterText"/> are all the same,
+ /// there is an image, and there are no filter, suffix nor attribute images associated with this item.
+ /// </summary>
+ /// <param name="displayText">Text to use in the UI, when sorting, filtering and completing</param>
+ /// <param name="source">Reference to <see cref="IAsyncCompletionSource"/> that created this item</param>
+ /// <param name="icon">Image displayed in the UI. Default is <code>default(ImageElement)</code></param>
+ public CompletionItem(string displayText, IAsyncCompletionSource source, ImageElement icon)
+ : this(displayText, insertText: displayText, sortText: displayText, filterText: displayText,
+ source: source, filters: ImmutableArray<CompletionFilter>.Empty, icon: icon,
+ suffix: string.Empty, attributeIcons: ImmutableArray<ImageElement>.Empty)
+ {
+ }
+
+ /// <summary>
+ /// Creates a completion item whose <see cref="DisplayText"/>, <see cref="InsertText"/>, <see cref="SortText"/> and <see cref="FilterText"/> are all the same,
+ /// there is an image, filters, and there are no suffix nor attribute images associated with this item.
+ /// </summary>
+ /// <param name="displayText">Text to use in the UI, when sorting, filtering and completing</param>
+ /// <param name="source">Reference to <see cref="IAsyncCompletionSource"/> that created this item</param>
+ /// <param name="icon">Image displayed in the UI</param>
+ /// <param name="filters"><see cref="ImmutableArray"/> of references to <see cref="CompletionFilter"/>s applicable to this item. Default is <code>ImmutableArray<CompletionFilter>.Empty</code></param>
+ public CompletionItem(string displayText, IAsyncCompletionSource source, ImageElement icon, ImmutableArray<CompletionFilter> filters)
+ : this(displayText, insertText: displayText, sortText: displayText, filterText: displayText,
+ source: source, filters: filters, icon: icon,
+ suffix: string.Empty, attributeIcons: ImmutableArray<ImageElement>.Empty)
+ {
+ }
+
+ /// <summary>
+ /// Creates a completion item whose <see cref="DisplayText"/>, <see cref="InsertText"/>, <see cref="SortText"/> and <see cref="FilterText"/> are all the same,
+ /// there is an image, filters, suffix, and there are no attribute images associated with this item.
+ /// </summary>
+ /// <param name="displayText">Text to use in the UI, when sorting, filtering and completing</param>
+ /// <param name="source">Reference to <see cref="IAsyncCompletionSource"/> that created this item</param>
+ /// <param name="icon">Image displayed in the UI</param>
+ /// <param name="filters"><see cref="ImmutableArray"/> of references to <see cref="CompletionFilter"/>s applicable to this item</param>
+ /// <param name="suffix">Additional text to display in the UI. Default is <code>string.Empty</code></param>
+ public CompletionItem(string displayText, IAsyncCompletionSource source, ImageElement icon, ImmutableArray<CompletionFilter> filters, string suffix)
+ : this(displayText, insertText: displayText, sortText: displayText, filterText: displayText,
+ source: source, filters: filters, icon: icon,
+ suffix: suffix, attributeIcons: ImmutableArray<ImageElement>.Empty)
+ {
+ }
+
+ /// <summary>
+ /// Creates a completion item, allowing customization of all of its properties.
+ /// </summary>
+ /// <param name="displayText">Text used in the UI</param>
+ /// <param name="source">Reference to <see cref="IAsyncCompletionSource"/> that created this item</param>
+ /// <param name="icon">Image displayed in the UI. Default is <code>default(ImageElement)</code></param>
+ /// <param name="filters"><see cref="ImmutableArray"/> of references to <see cref="CompletionFilter"/>s applicable to this item. Default is <code>ImmutableArray<CompletionFilter>.Empty</code></param>
+ /// <param name="suffix">Additional text to display in the UI. Default is <code>string.Empty</code></param>
+ /// <param name="insertText">Text inserted when completing this item. Default is <see cref="displayText"/></param>
+ /// <param name="sortText">Text used by <see cref="IAsyncCompletionItemManager"/> when sorting against other items. Default is <see cref="displayText"/></param>
+ /// <param name="filterText">Text used by <see cref="IAsyncCompletionItemManager"/> when matching against the applicable span. Default is <see cref="displayText"/></param>
+ /// <param name="attributeIcons">Additional images to display in the UI. Default is <code>ImmutableArray<ImageElement>.Empty</code></param>
+ public CompletionItem(string displayText, IAsyncCompletionSource source, ImageElement icon, ImmutableArray<CompletionFilter> filters,
+ string suffix, string insertText, string sortText, string filterText, ImmutableArray<ImageElement> attributeIcons)
+ {
+ if (displayText == null)
+ displayText = string.Empty;
+ if (insertText == null)
+ insertText = string.Empty;
+ if (sortText == null)
+ sortText = string.Empty;
+ if (filterText == null)
+ filterText = string.Empty;
+ if (filters.IsDefault)
+ throw new ArgumentException("Array must be initialized", nameof(filters));
+
+ DisplayText = displayText;
+ InsertText = insertText;
+ SortText = sortText;
+ FilterText = filterText;
+ Source = source ?? throw new ArgumentNullException(nameof(source));
+ Icon = icon;
+ Filters = filters;
+ Suffix = suffix;
+ AttributeIcons = attributeIcons;
+ Properties = new PropertyCollection();
+ }
+
+ public override string ToString() => DisplayText;
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemEventArgs.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemEventArgs.cs
new file mode 100644
index 0000000..1fda80f
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemEventArgs.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Diagnostics;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This class is used to notify of an operation that affects a single <see cref="CompletionItem"/>.
+ /// </summary>
+ [DebuggerDisplay("EventArgs: {Item}")]
+ public sealed class CompletionItemEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Relevant item
+ /// </summary>
+ public CompletionItem Item { get; }
+
+ /// <summary>
+ /// Constructs instance of <see cref="CompletionItemEventArgs"/>.
+ /// </summary>
+ public CompletionItemEventArgs(CompletionItem item)
+ {
+ this.Item = item ?? throw new ArgumentNullException(nameof(item));
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemSelectedEventArgs.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemSelectedEventArgs.cs
new file mode 100644
index 0000000..3844800
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemSelectedEventArgs.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This class is used to notify completion's logic of selecting through the UI
+ /// </summary>
+ [DebuggerDisplay("EventArgs: {SelectedItem}, is suggestion: {SuggestionItemSelected}")]
+ public sealed class CompletionItemSelectedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Selected item. Might be null if there is no selection
+ /// </summary>
+ public CompletionItem SelectedItem { get; }
+
+ /// <summary>
+ /// Whether selected item is a suggestion mode item
+ /// </summary>
+ public bool SuggestionItemSelected { get; }
+
+ /// <summary>
+ /// Constructs instance of <see cref="CompletionItemSelectedEventArgs"/>.
+ /// </summary>
+ /// <param name="selectedItem">User-selected item</param>
+ /// <param name="suggestionItemSelected">Whether the selected item is a suggestion item</param>
+ public CompletionItemSelectedEventArgs(CompletionItem selectedItem, bool suggestionItemSelected)
+ {
+ this.SelectedItem = selectedItem;
+ this.SuggestionItemSelected = suggestionItemSelected;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemWithHighlight.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemWithHighlight.cs
new file mode 100644
index 0000000..0591dd3
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemWithHighlight.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using Microsoft.VisualStudio.Text;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Wraps <see cref="CompletionItem"/> with information about highlighted parts of its <see cref="CompletionItem.DisplayText"/>.
+ /// </summary>
+ [DebuggerDisplay("{CompletionItem}")]
+ public struct CompletionItemWithHighlight : IEquatable<CompletionItemWithHighlight>
+ {
+ /// <summary>
+ /// The completion item
+ /// </summary>
+ public CompletionItem CompletionItem { get; }
+
+ /// <summary>
+ /// Which parts of <see cref="CompletionItem.DisplayText"/> to highlight
+ /// </summary>
+ public ImmutableArray<Span> HighlightedSpans { get; }
+
+ /// <summary>
+ /// Constructs <see cref="CompletionItemWithHighlight"/> without any highlighting.
+ /// Used when the <see cref="CompletionItem"/> appears in the completion list without being a text match.
+ /// </summary>
+ /// <param name="completionItem">Instance of the <see cref="CompletionItem"/></param>
+ public CompletionItemWithHighlight(CompletionItem completionItem)
+ : this (completionItem, ImmutableArray<Span>.Empty)
+ {
+ }
+
+ /// <summary>
+ /// Constructs <see cref="CompletionItemWithHighlight"/> with given highlighting.
+ /// Used when text used to filter the completion list can be found in the <see cref="CompletionItem.DisplayText"/>.
+ /// </summary>
+ /// <param name="completionItem">Instance of the <see cref="CompletionItem"/></param>
+ /// <param name="highlightedSpans"><see cref="Span"/>s of <see cref="CompletionItem.DisplayText"/> to highlight</param>
+ public CompletionItemWithHighlight(CompletionItem completionItem, ImmutableArray<Span> highlightedSpans)
+ {
+ CompletionItem = completionItem ?? throw new ArgumentNullException(nameof(completionItem));
+ if (highlightedSpans.IsDefault)
+ throw new ArgumentException("Array must be initialized", nameof(highlightedSpans));
+ HighlightedSpans = highlightedSpans;
+ }
+
+ bool IEquatable<CompletionItemWithHighlight>.Equals(CompletionItemWithHighlight other)
+ => CompletionItem != null && CompletionItem.Equals(other.CompletionItem) && HighlightedSpans.Equals(other.HighlightedSpans);
+
+ public override bool Equals(object other) => (other is CompletionItemWithHighlight otherItem) ? ((IEquatable<CompletionItemWithHighlight>)this).Equals(otherItem) : false;
+
+ public static bool operator ==(CompletionItemWithHighlight left, CompletionItemWithHighlight right) => left.Equals(right);
+
+ public static bool operator !=(CompletionItemWithHighlight left, CompletionItemWithHighlight right) => !(left == right);
+
+ public override int GetHashCode() => CompletionItem.GetHashCode() ^ HighlightedSpans.GetHashCode();
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemsWithHighlightEventArgs.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemsWithHighlightEventArgs.cs
new file mode 100644
index 0000000..6bc01cc
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionItemsWithHighlightEventArgs.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Immutable;
+using System.Diagnostics;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This class is used to notify of an operation that affects multiple <see cref="CompletionItemWithHighlight"/>s.
+ /// </summary>
+ [DebuggerDisplay("EventArgs: {Items.Length} items")]
+ public sealed class ComputedCompletionItemsEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Relevant items
+ /// </summary>
+ public ComputedCompletionItems Items { get; }
+
+ /// <summary>
+ /// Constructs instance of <see cref="CompletionItemEventArgs"/>.
+ /// </summary>
+ public ComputedCompletionItemsEventArgs(ComputedCompletionItems items)
+ {
+ Items = items ?? throw new ArgumentNullException(nameof(items));
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionPresentationViewModel.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionPresentationViewModel.cs
new file mode 100644
index 0000000..96baa4f
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionPresentationViewModel.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.VisualStudio.Text;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This class contains completion items, filters and other pieces of information
+ /// used by <see cref="ICompletionPresenter"/> to render the completion UI.
+ /// </summary>
+ public sealed class CompletionPresentationViewModel
+ {
+ /// <summary>
+ /// Completion items to display with their highlighted spans.
+ /// </summary>
+ public ImmutableArray<CompletionItemWithHighlight> Items { get; }
+
+ /// <summary>
+ /// Completion filters with their available and selected state.
+ /// </summary>
+ public ImmutableArray<CompletionFilterWithState> Filters { get; }
+
+ /// <summary>
+ /// Span pertinent to the completion session.
+ /// </summary>
+ public ITrackingSpan ApplicableToSpan { get; }
+
+ /// <summary>
+ /// Controls whether selected item should be soft selected.
+ /// </summary>
+ public bool UseSoftSelection { get; }
+
+ /// <summary>
+ /// Controls whether suggestion item is visible.
+ /// </summary>
+ public bool DisplaySuggestionItem { get; }
+
+ /// <summary>
+ /// Controls whether suggestion item is selected.
+ /// </summary>
+ public bool SelectSuggestionItem { get; }
+
+ /// <summary>
+ /// Controls which item is selected. Use -1 in suggestion mode.
+ /// </summary>
+ public int SelectedItemIndex { get; }
+
+ /// <summary>
+ /// Suggestion item to display when <see cref="DisplaySuggestionItem"/> is set.
+ /// </summary>
+ public CompletionItem SuggestionItem { get; }
+
+ /// <summary>
+ /// How to display the <see cref="SuggestionItem"/>.
+ /// </summary>
+ public SuggestionItemOptions SuggestionItemOptions { get; }
+
+ /// <summary>
+ /// Constructs <see cref="CompletionPresentationViewModel"/>
+ /// </summary>
+ /// <param name="items">Completion items to display with their highlighted spans</param>
+ /// <param name="filters">Completion filters with their available and selected state</param>
+ /// <param name="selectedItemIndex">Controls which item is selected. Use -1 in suggestion mode</param>
+ /// <param name="applicableToSpan">Span pertinent to the completion session</param>
+ /// <param name="useSoftSelection">Controls whether selected item should be soft selected. Default is <code>false</code></param>
+ /// <param name="displaySuggestionItem">Controls whether suggestion mode item is visible. Default is <code>false</code></param>
+ /// <param name="selectSuggestionItem">Controls whether suggestion mode item is selected. Default is <code>false</code></param>
+ /// <param name="suggestionItem">Suggestion mode item to display. Default is <code>null</code></param>
+ /// <param name="suggestionItemOptions">How to present the suggestion mode item. This is required because completion may be in suggestion mode even if there is no explicit suggestion mode item</param>
+ public CompletionPresentationViewModel(
+ ImmutableArray<CompletionItemWithHighlight> items,
+ ImmutableArray<CompletionFilterWithState> filters,
+ int selectedItemIndex,
+ ITrackingSpan applicableToSpan,
+ bool useSoftSelection,
+ bool displaySuggestionItem,
+ bool selectSuggestionItem,
+ CompletionItem suggestionItem,
+ SuggestionItemOptions suggestionItemOptions)
+ {
+ if (selectedItemIndex < -1)
+ throw new ArgumentOutOfRangeException(nameof(selectedItemIndex), "Selected index value must be greater than or equal to 0, or -1 to indicate no selection");
+ if (items.IsDefault)
+ throw new ArgumentException("Array must be initialized", nameof(items));
+ if (filters.IsDefault)
+ throw new ArgumentException("Array must be initialized", nameof(filters));
+
+ Items = items;
+ Filters = filters;
+ ApplicableToSpan = applicableToSpan ?? throw new NullReferenceException(nameof(applicableToSpan));
+ UseSoftSelection = useSoftSelection;
+ DisplaySuggestionItem = displaySuggestionItem;
+ SelectSuggestionItem = selectSuggestionItem;
+ SelectedItemIndex = selectedItemIndex;
+ SuggestionItem = suggestionItem;
+ SuggestionItemOptions = suggestionItemOptions ?? throw new NullReferenceException(nameof(suggestionItemOptions));
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionPresenterOptions.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionPresenterOptions.cs
new file mode 100644
index 0000000..0ca2c44
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionPresenterOptions.cs
@@ -0,0 +1,25 @@
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Contains additional properties of thie <see cref="ICompletionPresenter"/> that may be accessed
+ /// prior to initializing an instance of <see cref="ICompletionPresenter"/>
+ /// </summary>
+ public sealed class CompletionPresenterOptions
+ {
+ /// <summary>
+ /// Declares the length of the jump when user presses PageUp and PageDown keys.
+ /// </summary>
+ /// <remarks>This value needs to be known before the UI is created, hence it is defined in this class instead of <see cref="ICompletionPresenter"/>.
+ /// Note that <see cref="IAsyncCompletionSession"/> handles keyboard scrolling, including using PageUp and PageDown keys.</remarks>
+ public int ResultsPerPage { get; }
+
+ /// <summary>
+ /// Constructs instance of <see cref="CompletionPresenterOptions"/>
+ /// </summary>
+ /// <param name="resultsPerPage">Declares the length of the jump when user presses PageUp and PageDown keys</param>
+ public CompletionPresenterOptions(int resultsPerPage)
+ {
+ ResultsPerPage = resultsPerPage;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/CompletionTriggeredEventArgs.cs b/src/Language/Def/Language/AsyncCompletion/Data/CompletionTriggeredEventArgs.cs
new file mode 100644
index 0000000..9f67b01
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/CompletionTriggeredEventArgs.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This class is used to notify about new <see cref="IAsyncCompletionSession"/> being triggered
+ /// </summary>
+ public sealed class CompletionTriggeredEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Newly created <see cref="IAsyncCompletionSession"/>.
+ /// </summary>
+ public IAsyncCompletionSession CompletionSession { get; }
+
+ /// <summary>
+ /// <see cref="ITextView"/> where completion was triggered.
+ /// </summary>
+ public ITextView TextView { get; }
+
+ /// <summary>
+ /// Constructs instance of <see cref="CompletionItemSelectedEventArgs"/>.
+ /// </summary>
+ /// <param name="completionSession">Newly created <see cref="IAsyncCompletionSession"/></param>
+ /// <param name="textView"><see cref="ITextView"/> where completion was triggered</param>
+ public CompletionTriggeredEventArgs(IAsyncCompletionSession completionSession, ITextView textView)
+ {
+ this.CompletionSession = completionSession;
+ this.TextView = textView;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/ComputedCompletionItems.cs b/src/Language/Def/Language/AsyncCompletion/Data/ComputedCompletionItems.cs
new file mode 100644
index 0000000..463b5f7
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/ComputedCompletionItems.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Stores information on computed <see cref="CompletionItem"/>s and their selection information.
+ /// </summary>
+ public sealed class ComputedCompletionItems
+ {
+ /// <summary>
+ /// Constructs instance of <see cref="ComputedCompletionItems"/> with recently computed
+ /// <see cref="CompletionItem"/>s and their selection infomration.
+ /// </summary>
+ /// <param name="items"><see cref="CompletionItem"/>s displayed in the completion UI</param>
+ /// <param name="suggestionItem">Suggestion <see cref="CompletionItem"/> displayed in the UI, or null if no suggestion is displayed</param>
+ /// <param name="selectedItem">Currently selected <see cref="CompletionItem"/></param>
+ /// <param name="suggestionItemSelected">Whether <see cref="SelectedItem"/> is a suggestion item</param>
+ /// <param name="usesSoftSelection">Whether <see cref="SelectedItem"/> is soft selected.</param>
+ public ComputedCompletionItems(
+ ImmutableArray<CompletionItem> items,
+ CompletionItem suggestionItem,
+ CompletionItem selectedItem,
+ bool suggestionItemSelected,
+ bool usesSoftSelection)
+ {
+ _items = items;
+ SuggestionItem = suggestionItem;
+ SelectedItem = selectedItem;
+ SuggestionItemSelected = suggestionItemSelected;
+ UsesSoftSelection = usesSoftSelection;
+ }
+
+ /// <summary>
+ /// Constructs instance of <see cref="ComputedCompletionItems"/> with recently computed
+ /// <see cref="CompletionItemWithHighlight"/>s and their selection infomration.
+ /// </summary>
+ /// <param name="itemsWithHighlight"><see cref="CompletionItemWithHighlight"/>s displayed in the completion UI</param>
+ /// <param name="suggestionItem">Suggestion <see cref="CompletionItem"/> displayed in the UI, or null if no suggestion is displayed</param>
+ /// <param name="selectedItem">Currently selected <see cref="CompletionItem"/></param>
+ /// <param name="suggestionItemSelected">Whether <see cref="SelectedItem"/> is a suggestion item</param>
+ /// <param name="usesSoftSelection">Whether <see cref="SelectedItem"/> is soft selected.</param>
+ public ComputedCompletionItems(
+ ImmutableArray<CompletionItemWithHighlight> itemsWithHighlight,
+ CompletionItem suggestionItem,
+ CompletionItem selectedItem,
+ bool suggestionItemSelected,
+ bool usesSoftSelection)
+ {
+ _itemsWithHighlight = itemsWithHighlight;
+ SuggestionItem = suggestionItem;
+ SelectedItem = selectedItem;
+ SuggestionItemSelected = suggestionItemSelected;
+ UsesSoftSelection = usesSoftSelection;
+ }
+
+ /// <summary>
+ /// Empty data structure, used when no computation was performed
+ /// </summary>
+ public static ComputedCompletionItems Empty { get; } = new ComputedCompletionItems(ImmutableArray<CompletionItem>.Empty, null, null, false, false);
+
+ private IEnumerable<CompletionItem> _items = null;
+ private IEnumerable<CompletionItemWithHighlight> _itemsWithHighlight = null;
+
+ /// <summary>
+ /// <see cref="CompletionItem"/>s displayed in the completion UI
+ /// </summary>
+ public IEnumerable<CompletionItem> Items => _items ?? _itemsWithHighlight.Select(n => n.CompletionItem);
+
+ /// <summary>
+ /// Suggestion <see cref="CompletionItem"/> displayed in the UI, or null if no suggestion is displayed
+ /// </summary>
+ public CompletionItem SuggestionItem { get; }
+
+ /// <summary>
+ /// Currently selected <see cref="CompletionItem"/>
+ /// </summary>
+ public CompletionItem SelectedItem { get; }
+
+ /// <summary>
+ /// Whether <see cref="SelectedItem"/> is a suggestion item
+ /// </summary>
+ public bool SuggestionItemSelected { get; }
+
+ /// <summary>
+ /// Whether <see cref="SelectedItem"/> is soft selected.
+ /// </summary>
+ public bool UsesSoftSelection { get; }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/FilteredCompletionModel.cs b/src/Language/Def/Language/AsyncCompletion/Data/FilteredCompletionModel.cs
new file mode 100644
index 0000000..07f76d1
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/FilteredCompletionModel.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Diagnostics;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// This class, returned from <see cref="IAsyncCompletionItemManager"/>,
+ /// contains completion items to display in the UI, recommended item to display, selection mode and available filters.
+ /// </summary>
+ public sealed class FilteredCompletionModel
+ {
+ /// <summary>
+ /// Items to display in the completion UI.
+ /// </summary>
+ public ImmutableArray<CompletionItemWithHighlight> Items { get; }
+
+ /// <summary>
+ /// Recommended item index to select. -1 selects suggestion item.
+ /// </summary>
+ public int SelectedItemIndex { get; }
+
+ /// <summary>
+ /// Completion filters with their availability and selection state.
+ /// </summary>
+ public ImmutableArray<CompletionFilterWithState> Filters { get; }
+
+ /// <summary>
+ /// Controls the selection mode of the selected item.
+ /// </summary>
+ public UpdateSelectionHint SelectionHint { get; }
+
+ /// <summary>
+ /// Whether selected item should be displayed in the center of the list. Usually, this is true
+ /// </summary>
+ public bool CenterSelection { get; }
+
+ /// <summary>
+ /// Optionally, provides an item that should be committed using the "commit if unique" command.
+ /// </summary>
+ public CompletionItem UniqueItem { get; }
+
+ /// <summary>
+ /// Constructs <see cref="FilteredCompletionModel"/> without completion filters.
+ /// </summary>
+ /// <param name="items">Items to display in the completion UI.</param>
+ /// <param name="selectedItemIndex">Recommended item index to select. -1 selects suggestion item.</param>
+ public FilteredCompletionModel(ImmutableArray<CompletionItemWithHighlight> items, int selectedItemIndex)
+ : this(items, selectedItemIndex, ImmutableArray<CompletionFilterWithState>.Empty, selectionHint: UpdateSelectionHint.NoChange, centerSelection: true, uniqueItem: null)
+ {
+ }
+
+ /// <summary>
+ /// Constructs <see cref="FilteredCompletionModel"/> with completion filters.
+ /// </summary>
+ /// <param name="items">Items to display in the completion UI.</param>
+ /// <param name="selectedItemIndex">Recommended item index to select. -1 selects suggestion item.</param>
+ /// <param name="filters">Completion filters with their availability and selection state. Default is empty array.</param>
+ public FilteredCompletionModel(ImmutableArray<CompletionItemWithHighlight> items, int selectedItemIndex, ImmutableArray<CompletionFilterWithState> filters)
+ : this(items, selectedItemIndex, filters, selectionHint: UpdateSelectionHint.NoChange, centerSelection: true, uniqueItem: null)
+ {
+ }
+
+ /// <summary>
+ /// Constructs <see cref="FilteredCompletionModel"/> with completion filters, indication regarding selection mode and the unique item
+ /// </summary>
+ /// <param name="items">Items to display in the completion UI.</param>
+ /// <param name="selectedItemIndex">Recommended item index to select. -1 selects suggestion item.</param>
+ /// <param name="filters">Completion filters with their availability and selection state. Default is empty array.</param>
+ /// <param name="selectionHint">Allows <see cref="IAsyncCompletionItemManager"/> to influence the selection mode. Default is <see cref="UpdateSelectionHint.NoChange" /></param>
+ /// <param name="uniqueItem">Provides <see cref="CompletionItem"/> to commit using "commit if unique" command despite displaying more than one item. Default is <code>null</code></param>
+ public FilteredCompletionModel(
+ ImmutableArray<CompletionItemWithHighlight> items,
+ int selectedItemIndex,
+ ImmutableArray<CompletionFilterWithState> filters,
+ UpdateSelectionHint selectionHint,
+ bool centerSelection,
+ CompletionItem uniqueItem)
+ {
+ if (selectedItemIndex < -1)
+ throw new ArgumentOutOfRangeException(nameof(selectedItemIndex), "Selected index value must be greater than or equal to 0, or -1 to indicate selection of the suggestion item");
+ if (items.IsDefault)
+ throw new ArgumentException("Array must be initialized", nameof(items));
+ if (filters.IsDefault)
+ throw new ArgumentException("Array must be initialized", nameof(filters));
+
+ Items = items;
+ SelectedItemIndex = selectedItemIndex;
+ Filters = filters;
+ SelectionHint = selectionHint;
+ CenterSelection = centerSelection;
+ UniqueItem = uniqueItem;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/InitialSelectionHint.cs b/src/Language/Def/Language/AsyncCompletion/Data/InitialSelectionHint.cs
new file mode 100644
index 0000000..ae225c7
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/InitialSelectionHint.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Used by <see cref="IAsyncCompletionSource"/> to recommend the selection mode.
+ /// </summary>
+ public enum InitialSelectionHint
+ {
+ /// <summary>
+ /// Item is selected.
+ /// It will be committed by pressing a commit character, e.g. a token delimeter,
+ /// Tab, Enter and mouse click.
+ /// When multiple <see cref="IAsyncCompletionSource"/> give different results, this value has the lowest priority.
+ /// </summary>
+ RegularSelection,
+
+ /// <summary>
+ /// Item is soft selected.
+ /// It will be committed only by pressing Tab or clicking the item.
+ /// Typing a commit character will dismiss the <see cref="IAsyncCompletionSession"/>.
+ /// Selecting another item automatically disables soft selection and enables regular selection.
+ /// When multiple <see cref="IAsyncCompletionSource"/> give different results, this value has higher priority than <see cref="RegularSelection"/>.
+ /// </summary>
+ SoftSelection,
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/InitialTrigger.cs b/src/Language/Def/Language/AsyncCompletion/Data/InitialTrigger.cs
new file mode 100644
index 0000000..9319b3e
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/InitialTrigger.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Diagnostics;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// What triggered the completion, but not where it happened.
+ /// The reason we don't expose location is that for each extension,
+ /// we map the point to a buffer with matching content type.
+ /// </summary>
+ [DebuggerDisplay("{Reason} {Character}")]
+ public struct InitialTrigger : IEquatable<InitialTrigger>
+ {
+ /// <summary>
+ /// The reason that completion was started.
+ /// </summary>
+ public InitialTriggerReason Reason { get; }
+
+ /// <summary>
+ /// The text edit associated with the triggering action.
+ /// </summary>
+ public char Character { get; }
+
+ /// <summary>
+ /// Creates a <see cref="InitialTrigger"/> associated with a text edit
+ /// </summary>
+ /// <param name="reason">The kind of action that triggered completion to start</param>
+ /// <param name="character">Character that triggered completion</param>
+ public InitialTrigger(InitialTriggerReason reason, char character)
+ {
+ this.Reason = reason;
+ this.Character = character;
+ }
+
+ /// <summary>
+ /// Creates a <see cref="InitialTrigger"/> not associated with a text edit
+ /// </summary>
+ /// <param name="reason">The kind of action that triggered completion to start</param>
+ public InitialTrigger(InitialTriggerReason reason) : this(reason, default)
+ { }
+
+ bool IEquatable<InitialTrigger>.Equals(InitialTrigger other) => Reason.Equals(other.Reason) && Character.Equals(other.Character);
+
+ public override bool Equals(object other) => (other is InitialTrigger otherImage) ? ((IEquatable<InitialTrigger>)this).Equals(otherImage) : false;
+
+ public static bool operator ==(InitialTrigger left, InitialTrigger right) => left.Equals(right);
+
+ public static bool operator !=(InitialTrigger left, InitialTrigger right) => !(left == right);
+
+ public override int GetHashCode() => Reason.GetHashCode() ^ Character.GetHashCode();
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/InitialTriggerReason.cs b/src/Language/Def/Language/AsyncCompletion/Data/InitialTriggerReason.cs
new file mode 100644
index 0000000..ec1942e
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/InitialTriggerReason.cs
@@ -0,0 +1,35 @@
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Describes the kind of action that initially triggered completion to open.
+ /// </summary>
+ public enum InitialTriggerReason
+ {
+ /// <summary>
+ /// Completion was triggered by a direct invocation of the completion feature
+ /// using the Edit.ListMember command.
+ /// </summary>
+ Invoke,
+
+ /// <summary>
+ /// Completion was triggered with a request to commit if a single item would be selected
+ /// using the Edit.CompleteWord command.
+ /// </summary>
+ InvokeAndCommitIfUnique,
+
+ /// <summary>
+ /// Completion was triggered via an action inserting a character into the document.
+ /// </summary>
+ Insertion,
+
+ /// <summary>
+ /// Completion was triggered via an action deleting a character from the document.
+ /// </summary>
+ Deletion,
+
+ /// <summary>
+ /// Completion was triggered for snippets only.
+ /// </summary>
+ Snippets,
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/SuggestionItemOptions.cs b/src/Language/Def/Language/AsyncCompletion/Data/SuggestionItemOptions.cs
new file mode 100644
index 0000000..a9550e5
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/SuggestionItemOptions.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Instructs the editor if and how to display the suggestion item.
+ /// When in suggestion mode, the UI displays a single <see cref="CompletionItem"/> whose <see cref="CompletionItem.DisplayText"/>
+ /// and <see cref="CompletionItem.InsertText"/> is equal to text typed by the user so far.
+ /// This class specifies the tooltip to use for this item, and <see cref="CompletionItem.DisplayText"/> when user has not typed anything.
+ /// </summary>
+ public sealed class SuggestionItemOptions
+ {
+ /// <summary>
+ /// Text to use as suggestion item's <see cref="CompletionItem.DisplayText"/> when user has not typed anything.
+ /// Usually prompts user to begin typing and describes what does the suggestion item represent.
+ /// </summary>
+ public string DisplayTextWhenEmpty { get; }
+
+ /// <summary>
+ /// Localized tooltip text for the suggestion item.
+ /// Usually describes why suggestion mode is active, and what does the suggestion item represent.
+ /// </summary>
+ public string ToolTipText { get; }
+
+ /// <summary>
+ /// Creates instance of SuggestionItemOptions with specified tooltip text and text to display in absence of user input.
+ /// Provide this instance to <see cref="CompletionContext"/> to activate suggestion mode.
+ /// </summary>
+ /// <param name="displayTextWhenEmpty"><see cref="CompletionItem.DisplayText"/> to use when user has not typed anything</param>
+ /// <param name="toolTipText">Localized tooltip text for the suggestion item</param>
+ public SuggestionItemOptions(string displayTextWhenEmpty, string toolTipText)
+ {
+ if (string.IsNullOrWhiteSpace(toolTipText))
+ throw new ArgumentNullException(nameof(toolTipText));
+
+ DisplayTextWhenEmpty = displayTextWhenEmpty ?? throw new ArgumentNullException(nameof(displayTextWhenEmpty));
+ ToolTipText = toolTipText;
+ }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/UpdateSelectionHint.cs b/src/Language/Def/Language/AsyncCompletion/Data/UpdateSelectionHint.cs
new file mode 100644
index 0000000..bb9d45d
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/UpdateSelectionHint.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Used by <see cref="IAsyncCompletionItemManager" /> to recommend the selection mode.
+ /// </summary>
+ public enum UpdateSelectionHint
+ {
+ /// <summary>
+ /// Don't change the current selection mode. This is the recommended value.
+ /// </summary>
+ NoChange,
+
+ /// <summary>
+ /// Set selection mode to soft selection: item is committed only using Tab or mouse.
+ /// </summary>
+ SoftSelected,
+
+ /// <summary>
+ /// Set selection mode to regular selection: item is committed using Tab, mouse, enter and commit characters.
+ /// </summary>
+ Selected
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/UpdateTrigger.cs b/src/Language/Def/Language/AsyncCompletion/Data/UpdateTrigger.cs
new file mode 100644
index 0000000..6151d88
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/UpdateTrigger.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Diagnostics;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// What triggered updating of completion.
+ /// </summary>
+ [DebuggerDisplay("{Reason} {Character}")]
+ public struct UpdateTrigger : IEquatable<UpdateTrigger>
+ {
+ /// <summary>
+ /// The reason that completion was updated.
+ /// </summary>
+ public UpdateTriggerReason Reason { get; }
+
+ /// <summary>
+ /// The text edit associated with the triggering action.
+ /// </summary>
+ public char Character { get; }
+
+ /// <summary>
+ /// Creates a <see cref="UpdateTrigger"/> associated with a text edit
+ /// </summary>
+ /// <param name="reason">The kind of action that triggered completion to update</param>
+ /// <param name="character">Character that triggered the update</param>
+ public UpdateTrigger(UpdateTriggerReason reason, char character)
+ {
+ this.Reason = reason;
+ this.Character = character;
+ }
+
+ /// <summary>
+ /// Creates a <see cref="InitialTrigger"/> not associated with a text edit
+ /// </summary>
+ /// <param name="reason">The kind of action that triggered completion to update</param>
+ public UpdateTrigger(UpdateTriggerReason reason) : this(reason, default(char))
+ { }
+
+ bool IEquatable<UpdateTrigger>.Equals(UpdateTrigger other) => Reason.Equals(other.Reason) && Character.Equals(other.Character);
+
+ public override bool Equals(object other) => (other is InitialTrigger otherImage) ? ((IEquatable<UpdateTrigger>)this).Equals(otherImage) : false;
+
+ public static bool operator ==(UpdateTrigger left, UpdateTrigger right) => left.Equals(right);
+
+ public static bool operator !=(UpdateTrigger left, UpdateTrigger right) => !(left == right);
+
+ public override int GetHashCode() => Reason.GetHashCode() ^ Character.GetHashCode();
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/Data/UpdateTriggerReason.cs b/src/Language/Def/Language/AsyncCompletion/Data/UpdateTriggerReason.cs
new file mode 100644
index 0000000..83ce4d1
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/Data/UpdateTriggerReason.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data
+{
+ /// <summary>
+ /// Describes the kind of action that triggered completion to filter.
+ /// </summary>
+ public enum UpdateTriggerReason
+ {
+ /// <summary>
+ /// Completion was triggered by a direct invocation of the completion feature
+ /// using the Edit.ListMember command.
+ /// </summary>
+ Initial,
+
+ /// <summary>
+ /// Completion was triggered via an action inserting a character into the document.
+ /// </summary>
+ Insertion,
+
+ /// <summary>
+ /// Completion was triggered via an action deleting a character from the document.
+ /// </summary>
+ Deletion,
+
+ /// <summary>
+ /// Update was triggered by changing filters
+ /// </summary>
+ FilterChange,
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionBroker.cs b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionBroker.cs
new file mode 100644
index 0000000..f882166
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionBroker.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Threading;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Represents a class that manages the completion feature.
+ /// The editor uses this class to trigger completion and obtain instance of <see cref="IAsyncCompletionSession"/>
+ /// which contains methods and events relevant to the active completion session.
+ /// </summary>
+ /// <remarks>
+ /// This is a MEF component and may be imported by another MEF component:
+ /// </remarks>
+ /// <example>
+ /// [Import]
+ /// IAsyncCompletionBroker CompletionBroker;
+ /// </example>
+ public interface IAsyncCompletionBroker
+ {
+ /// <summary>
+ /// Returns whether <see cref="IAsyncCompletionSession"/> is active in given <see cref="ITextView"/>.
+ /// </summary>
+ /// <remarks>
+ /// The data may be stale if <see cref="IAsyncCompletionSession"/> was simultaneously dismissed on another thread.
+ /// </remarks>
+ /// <param name="textView">View that hosts completion and relevant buffers</param>
+ bool IsCompletionActive(ITextView textView);
+
+ /// <summary>
+ /// Returns whether there are any completion item sources available for given <see cref="IContentType"/>.
+ /// This method should be called prior to calling <see cref="TriggerCompletion(ITextView, SnapshotPoint, char, CancellationToken)"/> to avoid traversal of the buffer graph.
+ /// </summary>
+ /// <param name="textView"><see cref="ITextView"/> to check for available completion source exports</param>
+ bool IsCompletionSupported(IContentType contentType);
+
+ /// <summary>
+ /// Returns <see cref="IAsyncCompletionSession"/> if there is one active in a given <see cref="ITextView"/>, or null if not.
+ /// </summary>
+ /// <remarks>
+ /// The data may be stale if <see cref="IAsyncCompletionSession"/> was simultaneously dismissed on another thread.
+ /// Use <see cref="IAsyncCompletionSession.IsDismissed"/> to check state of returned session.
+ /// </remarks>
+ /// <param name="textView">View that hosts completion and relevant buffers</param>
+ IAsyncCompletionSession GetSession(ITextView textView);
+
+ /// <summary>
+ /// Activates completion and returns <see cref="IAsyncCompletionSession"/>.
+ /// If completion was already active, returns the existing session without changing it.
+ /// Must be invoked on UI thread.
+ /// This does not cause the completion popup to appear.
+ /// To compute available icons and display the UI, call <see cref="IAsyncCompletionSession.OpenOrUpdate(InitialTrigger, SnapshotPoint, CancellationToken)"/>.
+ /// Invoke <see cref="IsCompletionSupported(IContentType)"/> prior to invoking this method to more efficiently verify whether feature is disabled or if there are no completion providers.
+ /// </summary>
+ /// <param name="textView">View that hosts completion and relevant buffers</param>
+ /// <param name="triggerLocation">Location of completion on the view's data buffer: <see cref="ITextView.TextBuffer"/>. Used to pick relevant <see cref="IAsyncCompletionSource"/>s and <see cref="IAsyncCompletionItemManager"/></param>
+ /// <param name="typeChar">Character that triggered completion, '\t', '\n' or default ('\0') </param>
+ /// <param name="token">Cancellation token that may interrupt this operation, despire running on the UI thread</param>
+ /// <returns>
+ /// Returns existing <see cref="IAsyncCompletionSession"/> if one already exists
+ /// Returns null if the completion feature is disabled or if there are no applicable completion providers. Invoke <see cref="IsCompletionSupported(IContentType)"/> prior to invoking this method to perform this check more efficiently.
+ /// Returns null if applicable <see cref="IAsyncCompletionSource"/>s determine that completion is not applicable at the given <paramref name="triggerLocation"/>.
+ /// Returns a new <see cref="IAsyncCompletionSession"/>. Invoke <see cref="IAsyncCompletionSession.OpenOrUpdate(InitialTrigger, SnapshotPoint, CancellationToken)"/> to compute and display the available completions.
+ /// </returns>
+ IAsyncCompletionSession TriggerCompletion(ITextView textView, SnapshotPoint triggerLocation, char typedChar, CancellationToken token);
+
+ /// <summary>
+ /// Raised on UI thread when new <see cref="IAsyncCompletionSession"/> is triggered.
+ /// </summary>
+ event EventHandler<CompletionTriggeredEventArgs> CompletionTriggered;
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionCommitManager.cs b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionCommitManager.cs
new file mode 100644
index 0000000..c365393
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionCommitManager.cs
@@ -0,0 +1,55 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Threading;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Represents a class that provides means to adjust the commit behavior,
+ /// including which typed characters commit the <see cref="IAsyncCompletionSession"/>
+ /// and how to commit <see cref="CompletionItem"/>s.
+ /// </summary>
+ /// <remarks>
+ /// Instances of this class should be created by <see cref="IAsyncCompletionCommitManagerProvider"/>, which is a MEF part.
+ /// </remarks>
+ public interface IAsyncCompletionCommitManager
+ {
+ /// <summary>
+ /// Returns characters that may commit completion.
+ /// When completion is active and a text edit matches one of these characters,
+ /// <see cref="ShouldCommitCompletion(char, SnapshotPoint, CancellationToken)"/> is called to verify that the character
+ /// is indeed a commit character at a given location.
+ /// Called on UI thread.
+ /// </summary>
+ IEnumerable<char> PotentialCommitCharacters { get; }
+
+ /// <summary>
+ /// Returns whether this character is a commit character in a given location.
+ /// If every character returned by <see cref="PotentialCommitCharacters"/> should always commit the active completion session, return true.
+ /// Called on UI thread.
+ /// </summary>
+ /// <param name="typedChar">Character typed by the user</param>
+ /// <param name="location">Location in the snapshot of the view's topmost buffer. The character is not inserted into this snapshot.</param>
+ /// <param name="token">Token used to cancel this operation</param>
+ /// <returns>True if this character should commit the active session.</returns>
+ bool ShouldCommitCompletion(char typedChar, SnapshotPoint location, CancellationToken token);
+
+ /// <summary>
+ /// Allows the instance of <see cref="IAsyncCompletionCommitManager"/> to commit of specified <see cref="CompletionItem"/>.
+ /// Implementer does not need to commit the item. Return <see cref="CommitResult.Unhandled"/> to allow another
+ /// <see cref="IAsyncCompletionCommitManager"/> to attempt the commit, or to invoke default commit behavior.
+ /// Called on UI thread.
+ /// </summary>
+ /// <param name="view">View that hosts completion and relevant buffers</param>
+ /// <param name="buffer">Reference to the buffer with matching content type to perform text edits etc.</param>
+ /// <param name="item">Which completion item is to be applied</param>
+ /// <param name="applicableToSpan">Span augmented by completion, on the view's data buffer: <see cref="ITextView.TextBuffer"/></param>
+ /// <param name="typedChar">Text change associated with this commit</param>
+ /// <param name="token">Token used to cancel this operation</param>
+ /// <returns>Instruction for the editor how to proceed after invoking this method. Default is <see cref="CommitResult.Unhandled"/></returns>
+ CommitResult TryCommit(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableToSpan, char typedChar, CancellationToken token);
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionCommitManagerProvider.cs b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionCommitManagerProvider.cs
new file mode 100644
index 0000000..b235e38
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionCommitManagerProvider.cs
@@ -0,0 +1,36 @@
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Provides instances of <see cref="IAsyncCompletionCommitManager"/> which provides means to adjust the commit behavior,
+ /// including which typed characters commit the <see cref="IAsyncCompletionSession"/>
+ /// and how to commit <see cref="CompletionItem"/>s.
+ /// </summary>
+ /// <remarks>
+ /// This is a MEF component and should be exported with [ContentType] and [Name] attributes
+ /// and optional [Order] and [TextViewRoles] attributes.
+ /// An instance of <see cref="IAsyncCompletionItemManager"/> is selected
+ /// first by matching ContentType with content type of the <see cref="ITextView.TextBuffer"/>, and then by Order.
+ /// Only one <see cref="IAsyncCompletionItemManager"/> is used in a given view.
+ /// </remarks>
+ /// <example>
+ /// [Export(typeof(IAsyncCompletionCommitManagerProvider))]
+ /// [Name(nameof(MyCompletionCommitManagerProvider))]
+ /// [ContentType("text")]
+ /// [TextViewRoles(PredefinedTextViewRoles.Editable)]
+ /// [Order(Before = "OtherCompletionCommitManager")]
+ /// public class MyCompletionCommitManagerProvider : IAsyncCompletionCommitManagerProvider
+ /// </example>
+ public interface IAsyncCompletionCommitManagerProvider
+ {
+ /// <summary>
+ /// Creates an instance of <see cref="IAsyncCompletionCommitManager"/> for the specified <see cref="ITextView"/>.
+ /// Called on the UI thread.
+ /// </summary>
+ /// <param name="textView">Text view that will host the completion. Completion acts on buffers of this view.</param>
+ /// <returns>Instance of <see cref="IAsyncCompletionItemManager"/></returns>
+ IAsyncCompletionCommitManager GetOrCreate(ITextView textView);
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionItemManager.cs b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionItemManager.cs
new file mode 100644
index 0000000..7967e15
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionItemManager.cs
@@ -0,0 +1,52 @@
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Represents a class that filters and sorts available <see cref="CompletionItem"/>s given the current state of the editor.
+ /// It also declares which completion filters are available for the returned subset of <see cref="CompletionItem"/>s.
+ /// All methods are called on background thread.
+ /// </summary>
+ /// <remarks>
+ /// Instances of this class should be created by <see cref="IAsyncCompletionItemManagerProvider"/>, which is a MEF part.
+ /// </remarks>
+ public interface IAsyncCompletionItemManager
+ {
+ /// <summary>
+ /// This method is first called before completion is about to appear,
+ /// and then on subsequent typing events and when user toggles completion filters.
+ /// <paramref name="session"/> tracks user user's input tracked with <see cref="IAsyncCompletionSession.ApplicableToSpan"/>.
+ /// <paramref name="data"/> provides applicable <see cref="AsyncCompletionSessionDataSnapshot.Snapshot"/> and
+ /// and <see cref="AsyncCompletionSessionDataSnapshot.SelectedFilters"/>s that indicate user's filter selection.
+ /// </summary>
+ /// <param name="session">The active <see cref="IAsyncCompletionSession"/>. See <see cref="IAsyncCompletionSession.ApplicableToSpan"/> and <see cref="IAsyncCompletionSession.TextView"/></param>
+ /// <param name="data">Contains properties applicable at the time this method is invoked.</param>
+ /// <param name="token">Cancellation token that may interrupt this operation</param>
+ /// <returns>Instance of <see cref="FilteredCompletionModel"/> that contains completion items to render, filters to display and recommended item to select</returns>
+ Task<FilteredCompletionModel> UpdateCompletionListAsync(
+ IAsyncCompletionSession session,
+ AsyncCompletionSessionDataSnapshot data,
+ CancellationToken token);
+
+ /// <summary>
+ /// This method is first called before completion is about to appear,
+ /// and then on subsequent typing events and when user toggles completion filters.
+ /// The result of this method will be used in subsequent invocations of <see cref="UpdateCompletionListAsync"/>
+ /// <paramref name="session"/> tracks user user's input tracked with <see cref="IAsyncCompletionSession.ApplicableToSpan"/>.
+ /// <paramref name="data"/> provides applicable <see cref="AsyncCompletionSessionDataSnapshot.Snapshot"/> and
+ /// </summary>
+ /// <param name="session">The active <see cref="IAsyncCompletionSession"/>. See <see cref="IAsyncCompletionSession.ApplicableToSpan"/> and <see cref="IAsyncCompletionSession.TextView"/></param>
+ /// <param name="data">Contains properties applicable at the time this method is invoked.</param>
+ /// <param name="token">Cancellation token that may interrupt this operation</param>
+ /// <returns>Instance of <see cref="FilteredCompletionModel"/> that contains completion items to render, filters to display and recommended item to select</returns>
+ Task<ImmutableArray<CompletionItem>> SortCompletionListAsync(
+ IAsyncCompletionSession session,
+ AsyncCompletionSessionInitialDataSnapshot data,
+ CancellationToken token);
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionItemManagerProvider.cs b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionItemManagerProvider.cs
new file mode 100644
index 0000000..8becc26
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionItemManagerProvider.cs
@@ -0,0 +1,34 @@
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Provides instances of <see cref="IAsyncCompletionItemManager"/> which filters and sorts available <see cref="CompletionItem"/>s given the current state of the editor.
+ /// </summary>
+ /// <remarks>
+ /// This is a MEF component and should be exported with [ContentType] and [Name] attributes
+ /// and optional [Order] and [TextViewRoles] attributes.
+ /// An instance of <see cref="IAsyncCompletionItemManager"/> is selected
+ /// first by matching ContentType with content type of the <see cref="ITextView.TextBuffer"/>, and then by Order.
+ /// Only one <see cref="IAsyncCompletionItemManager"/> is used in a given view.
+ /// </remarks>
+ /// <example>
+ /// [Export(typeof(IAsyncCompletionItemManagerProvider))]
+ /// [Name(nameof(MyCompletionItemManagerProvider))]
+ /// [ContentType("text")]
+ /// [TextViewRoles(PredefinedTextViewRoles.Editable)]
+ /// [Order(Before = "OtherCompletionItemManager")]
+ /// public class MyCompletionItemManagerProvider : IAsyncCompletionItemManagerProvider
+ /// </example>
+ public interface IAsyncCompletionItemManagerProvider
+ {
+ /// <summary>
+ /// Creates an instance of <see cref="IAsyncCompletionItemManager"/> for the specified <see cref="ITextView"/>.
+ /// Called on the UI thread.
+ /// </summary>
+ /// <param name="textView">Text view that will host the completion</param>
+ /// <returns>Instance of <see cref="IAsyncCompletionItemManager"/> that will sort and filter <see cref="CompletionItem"/>s</returns>
+ IAsyncCompletionItemManager GetOrCreate(ITextView textView);
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSession.cs b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSession.cs
new file mode 100644
index 0000000..f6549f1
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSession.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Threading;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Represents a class that tracks completion within a single <see cref="ITextView"/>.
+ /// Constructed and managed by an instance of <see cref="IAsyncCompletionBroker"/>
+ /// </summary>
+ public interface IAsyncCompletionSession : IPropertyOwner
+ {
+ /// <summary>
+ /// Request completion to be opened or updated in a given location,
+ /// the completion items to be filtered and sorted, and the UI updated.
+ /// Must be called on UI thread. Enqueues work on a worker thread.
+ /// </summary>
+ /// <param name="trigger">What caused completion</param>
+ /// <param name="triggerLocation">Location of the trigger on the subject buffer</param>
+ /// <param name="token">Token used to cancel this and other queued operation.</param>
+ void OpenOrUpdate(InitialTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token);
+
+ /// <summary>
+ /// Stops the session and hides associated UI.
+ /// May be called from any thread.
+ /// </summary>
+ void Dismiss();
+
+ /// <summary>
+ /// Returns whether given text edit should result in committing this session.
+ /// Since this method is on a typing hot path, it returns quickly if the <paramref name="typedChar"/>
+ /// is not found among characters collected from <see cref="IAsyncCompletionCommitManager.PotentialCommitCharacters"/>
+ /// Else, we map the top-buffer <paramref name="triggerLocation"/> to subject buffers and query
+ /// <see cref="IAsyncCompletionCommitManager.ShouldCommitCompletion(char, SnapshotPoint, CancellationToken)"/>
+ /// to see whether any <see cref="IAsyncCompletionCommitManager"/> would like to commit completion.
+ /// Must be called on UI thread.
+ /// </summary>
+ /// <remarks>This method must run on UI thread because of mapping the point across buffers.</remarks>
+ /// <param name="typedChar">The text edit which caused this action. May be null.</param>
+ /// <param name="triggerLocation">Location on the view's data buffer: <see cref="ITextView.TextBuffer"/></param>
+ /// <param name="token">Token used to cancel this operation</param>
+ /// <returns>Whether any <see cref="IAsyncCompletionCommitManager.ShouldCommitCompletion(char, SnapshotPoint, CancellationToken)"/> returned true</returns>
+ bool ShouldCommit(char typedChar, SnapshotPoint triggerLocation, CancellationToken token);
+
+ /// <summary>
+ /// Commits the currently selected <see cref="CompletionItem"/>.
+ /// Must be called on UI thread.
+ /// </summary>
+ /// <param name="typedChar">The text edit which caused this action.
+ /// May be default(char) when commit was requested by an explcit command (e.g. hitting Tab, Enter or clicking)</param>
+ /// <param name="token">Token used to cancel this operation</param>
+ /// <returns>Instruction for the editor how to proceed after invoking this method</returns>
+ CommitBehavior Commit(char typedChar, CancellationToken token);
+
+ /// <summary>
+ /// Commits the single <see cref="CompletionItem"/> or opens the completion UI.
+ /// Must be called on UI thread.
+ /// </summary>
+ /// <param name="token">Token used to cancel this operation</param>
+ /// <returns>Whether the unique item was committed.</returns>
+ bool CommitIfUnique(CancellationToken token);
+
+ /// <summary>
+ /// Returns the <see cref="ITextView"/> this session is active on.
+ /// </summary>
+ ITextView TextView { get; }
+
+ /// <summary>
+ /// Gets span applicable to this completion session.
+ /// The span is defined on the session's <see cref="ITextView.TextBuffer"/>.
+ /// </summary>
+ ITrackingSpan ApplicableToSpan { get; }
+
+ /// <summary>
+ /// Returns whether session is dismissed.
+ /// When session is dismissed, all work is canceled.
+ /// </summary>
+ bool IsDismissed { get; }
+
+ /// <summary>
+ /// Raised on UI thread when completion item is committed
+ /// </summary>
+ event EventHandler<CompletionItemEventArgs> ItemCommitted;
+
+ /// <summary>
+ /// Raised on UI thread when completion session is dismissed.
+ /// </summary>
+ event EventHandler Dismissed;
+
+ /// <summary>
+ /// Provides elements that are visible in the UI
+ /// Raised on worker thread when filtering and sorting of items has finished.
+ /// There may be more updates happening immediately after this update.
+ /// </summary>
+ event EventHandler<ComputedCompletionItemsEventArgs> ItemsUpdated;
+
+ /// <summary>
+ /// Gets items visible in the UI and information about selection.
+ /// This is a blocking call. As a side effect, prevents the UI from displaying.
+ /// </summary>
+ ComputedCompletionItems GetComputedItems(CancellationToken token);
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSource.cs b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSource.cs
new file mode 100644
index 0000000..fc17d0b
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSource.cs
@@ -0,0 +1,60 @@
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Adornments;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Represents a class that provides <see cref="CompletionItem"/>s and other information
+ /// relevant to the completion feature at a specific <see cref="SnapshotPoint"/>.
+ /// </summary>
+ /// <remarks>
+ /// Instances of this class should be created by <see cref="IAsyncCompletionSourceProvider"/>, which is a MEF part.
+ /// </remarks>
+ public interface IAsyncCompletionSource
+ {
+ /// <summary>
+ /// Called once per completion session to fetch the set of all completion items available at a given location.
+ /// Called on a background thread.
+ /// </summary>
+ /// <param name="trigger">What caused the completion</param>
+ /// <param name="triggerLocation">Location where completion was triggered, on the subject buffer that matches this <see cref="IAsyncCompletionSource"/>'s content type</param>
+ /// <param name="applicableToSpan">Location where completion will take place, on the view's data buffer: <see cref="ITextView.TextBuffer"/></param>
+ /// <param name="token">Cancellation token that may interrupt this operation</param>
+ /// <returns>A struct that holds completion items and applicable span</returns>
+ Task<CompletionContext> GetCompletionContextAsync(InitialTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableToSpan, CancellationToken token);
+
+ /// <summary>
+ /// Returns tooltip associated with provided <see cref="CompletionItem"/>.
+ /// The returned object will be rendered by <see cref="IViewElementFactoryService"/>. See its documentation for default supported types.
+ /// You may export a <see cref="IViewElementFactory"/> to provide a renderer for a custom type.
+ /// Since this method is called on a background thread and on multiple platforms, an instance of UIElement may not be returned.
+ /// </summary>
+ /// <param name="item"><see cref="CompletionItem"/> which is a subject of the tooltip</param>
+ /// <param name="token">Cancellation token that may interrupt this operation</param>
+ /// <returns>An object that will be passed to <see cref="IViewElementFactoryService"/>. See its documentation for supported types.</returns>
+ Task<object> GetDescriptionAsync(CompletionItem item, CancellationToken token);
+
+ /// <summary>
+ /// Provides the span applicable to the prospective session.
+ /// Called on UI thread and expected to return very quickly, based on textual information.
+ /// This method is called sequentially on available <see cref="IAsyncCompletionSource"/>s until one of them returns true.
+ /// Returning <code>false</code> does not exclude this source from participating in completion session.
+ /// If no <see cref="IAsyncCompletionSource"/>s return <code>true</code>, there will be no completion session.
+ /// </summary>
+ /// <remarks>
+ /// A language service should provide the span and return <code>true</code> even if it does not wish to provide completion.
+ /// This will enable extensions to provide completion in syntactically appropriate location.
+ /// </remarks>
+ /// <param name="typedChar">Character typed by the user</param>
+ /// <param name="triggerLocation">Location on the subject buffer that matches this <see cref="IAsyncCompletionSource"/>'s content type</param>
+ /// <param name="applicableToSpan">Applicable span for the prospective completion session. You may set it to <code>default</code> if returning false</param>
+ /// <param name="token">Cancellation token that may interrupt this operation</param>
+ /// <returns>Whether completion should use the supplied applicable span.</returns>
+ bool TryGetApplicableToSpan(char typedChar, SnapshotPoint triggerLocation, out SnapshotSpan applicableToSpan, CancellationToken token);
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSourceProvider.cs b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSourceProvider.cs
new file mode 100644
index 0000000..0e850e1
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/IAsyncCompletionSourceProvider.cs
@@ -0,0 +1,37 @@
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Provides instances of <see cref="IAsyncCompletionSource"/> which filters and sorts available <see cref="CompletionItem"/>s given the current state of the editor.
+ /// </summary>
+ /// <summary>
+ /// Provides instances of <see cref="IAsyncCompletionSource"/> which provides <see cref="CompletionItem"/>s
+ /// and other information relevant to the completion feature at a specific <see cref="SnapshotPoint"/>
+ /// </summary>
+ /// <remarks>
+ /// This is a MEF component and should be exported with [ContentType] and [Name] attributes
+ /// and optional [TextViewRoles] attribute.
+ /// Completion feature will request data from all exported <see cref="IAsyncCompletionSource"/>s whose ContentType
+ /// matches content type of any buffer in the completion's trigger location.
+ /// </remarks>
+ /// <example>
+ /// [Export(typeof(IAsyncCompletionSourceProvider))]
+ /// [Name(nameof(MyCompletionSource))]
+ /// [ContentType("text")]
+ /// [TextViewRoles(PredefinedTextViewRoles.Editable)]
+ /// public class MyCompletionSource : IAsyncCompletionSource
+ /// </example>
+ public interface IAsyncCompletionSourceProvider
+ {
+ /// <summary>
+ /// Creates an instance of <see cref="IAsyncCompletionSource"/> for the specified <see cref="ITextView"/>.
+ /// Called on the UI thread.
+ /// </summary>
+ /// <param name="textView">Text view that will host the completion. Completion acts on buffers of this view.</param>
+ /// <returns>Instance of <see cref="IAsyncCompletionSource"/></returns>
+ IAsyncCompletionSource GetOrCreate(ITextView textView);
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/ICompletionPresenter.cs b/src/Language/Def/Language/AsyncCompletion/ICompletionPresenter.cs
new file mode 100644
index 0000000..1187224
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/ICompletionPresenter.cs
@@ -0,0 +1,52 @@
+using System;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Represents a class that manages user interface for the completion feature.
+ /// All methods are called on UI thread.
+ /// </summary>
+ /// <remarks>
+ /// Instances of this class should be created by <see cref="ICompletionPresenterProvider"/>, which is a MEF part.
+ /// </remarks>
+ public interface ICompletionPresenter : IDisposable
+ {
+ /// <summary>
+ /// Opens the UI and displays provided data
+ /// </summary>
+ /// <param name="presentation">Data to display in the UI</param>
+ void Open(CompletionPresentationViewModel presentation);
+
+ /// <summary>
+ /// Updates the UI with provided data
+ /// </summary>
+ /// <param name="presentation">Data to display in the UI</param>
+ void Update(CompletionPresentationViewModel presentation);
+
+ /// <summary>
+ /// Hides the completion UI
+ /// </summary>
+ void Close();
+
+ /// <summary>
+ /// Notifies of user changing the selection state of filters
+ /// </summary>
+ event EventHandler<CompletionFilterChangedEventArgs> FiltersChanged;
+
+ /// <summary>
+ /// Notifies of user selecting an item
+ /// </summary>
+ event EventHandler<CompletionItemSelectedEventArgs> CompletionItemSelected;
+
+ /// <summary>
+ /// Notifies of user committing an item for completion
+ /// </summary>
+ event EventHandler<CompletionItemEventArgs> CommitRequested;
+
+ /// <summary>
+ /// Notifies of UI closing
+ /// </summary>
+ event EventHandler<CompletionClosedEventArgs> CompletionClosed;
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/ICompletionPresenterProvider.cs b/src/Language/Def/Language/AsyncCompletion/ICompletionPresenterProvider.cs
new file mode 100644
index 0000000..719251a
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/ICompletionPresenterProvider.cs
@@ -0,0 +1,41 @@
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Represents a class that produces instances of <see cref="ICompletionPresenter"/>
+ /// </summary>
+ /// <remarks>
+ /// This is a MEF component and should be exported with [ContentType] and [Name] attributes
+ /// and optional [Order] attribute.
+ /// An instance of <see cref="ICompletionPresenterProvider"/> is selected
+ /// first by matching ContentType with content type of the <see cref="ITextView.TextBuffer"/>, and then by Order.
+ /// Only one <see cref="ICompletionPresenterProvider"/> is used in a given view.
+ /// </remarks>
+ /// <example>
+ /// [Export(typeof(ICompletionPresenterProvider))]
+ /// [Name(nameof(MyCompletionPresenterProvider))]
+ /// [ContentType("any")]
+ /// [TextViewRoles(PredefinedTextViewRoles.Editable)]
+ /// [Order(Before = KnownCompletionNames.DefaultCompletionPresenter)]
+ /// public class MyCompletionPresenterProvider : ICompletionPresenterProvider
+ /// </example>
+ public interface ICompletionPresenterProvider
+ {
+ /// <summary>
+ /// Returns instance of <see cref="ICompletionPresenter"/> that will host completion for given <see cref="ITextView"/>.
+ /// Called on the UI thread.
+ /// </summary>
+ /// <remarks>It is encouraged to reuse the UI over creating new UI each time this method is called.</remarks>
+ /// <param name="textView">Text view that will host the completion. Completion acts on buffers of this view.</param>
+ /// <returns>Instance of <see cref="ICompletionPresenter"/></returns>
+ ICompletionPresenter GetOrCreate(ITextView textView);
+
+ /// <summary>
+ /// Contains additional properties of thie <see cref="ICompletionPresenter"/> that may be accessed
+ /// prior to initializing an instance of <see cref="ICompletionPresenter"/>
+ /// </summary>
+ CompletionPresenterOptions Options { get; }
+ }
+}
diff --git a/src/Language/Def/Language/AsyncCompletion/PredefinedCompletionNames.cs b/src/Language/Def/Language/AsyncCompletion/PredefinedCompletionNames.cs
new file mode 100644
index 0000000..80debb8
--- /dev/null
+++ b/src/Language/Def/Language/AsyncCompletion/PredefinedCompletionNames.cs
@@ -0,0 +1,35 @@
+using Microsoft.VisualStudio.Commanding;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
+{
+ /// <summary>
+ /// Provides names used by the Async Completion feature.
+ /// </summary>
+ public static class PredefinedCompletionNames
+ {
+ /// <summary>
+ /// Name of the default <see cref="IAsyncCompletionItemManagerProvider"/>. Use to order your MEF part.
+ /// </summary>
+ public const string DefaultCompletionItemManager = "DefaultCompletionItemManager";
+
+ /// <summary>
+ /// Name of the default <see cref="ICompletionPresenterProvider"/>. Use to order your MEF part.
+ /// </summary>
+ public const string DefaultCompletionPresenter = "DefaultCompletionPresenter";
+
+ /// <summary>
+ /// Name of the completion's <see cref="ICommandHandler"/>. Use to order your MEF part.
+ /// </summary>
+ public const string CompletionCommandHandler = "CompletionCommandHandler";
+
+ /// <summary>
+ /// Name of the editor option that stores user's preference for the completion mode.
+ /// </summary>
+ public const string SuggestionModeInCompletionOptionName = "SuggestionModeInCompletion";
+
+ /// <summary>
+ /// Name of the editor option that stores user's preference for the completion mode during debugging.
+ /// </summary>
+ public const string SuggestionModeInDebuggerCompletionOptionName = "SuggestionModeInCompletionDuringDebugging";
+ }
+}
diff --git a/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoBroker.cs b/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoBroker.cs
new file mode 100644
index 0000000..9375a22
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoBroker.cs
@@ -0,0 +1,90 @@
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Editor;
+
+ /// <summary>
+ /// Controls invocation and dismissal of Quick Info tooltips for <see cref="ITextView"/> instances.
+ /// </summary>
+ /// <remarks>
+ /// This type can be called from any thread and will marshal its work to the UI thread as required.
+ /// </remarks>
+ public interface IAsyncQuickInfoBroker
+ {
+ /// <summary>
+ /// Determines whether there is at least one active Quick Info session in the specified <see cref="ITextView" />.
+ /// </summary>
+ /// <remarks>
+ /// Quick info is considered to be active if there is a visible, calculating, or recalculating quick info session.
+ /// </remarks>
+ /// <param name="textView">The <see cref="ITextView" /> for which Quick Info session status is to be determined.</</param>
+ /// <returns>
+ /// <c>true</c> if there is at least one active or calculating Quick Info session over the specified <see cref="ITextView" />, <c>false</c>
+ /// otherwise.
+ /// </returns>
+ bool IsQuickInfoActive(ITextView textView);
+
+ /// <summary>
+ /// Triggers Quick Info tooltip in the specified <see cref="ITextView"/> at the caret or optional <paramref name="triggerPoint"/>.
+ /// </summary>
+ /// <exception cref="OperationCanceledException">
+ /// <paramref name="cancellationToken"/> was canceled by the caller or the operation was interrupted by another call to
+ /// <see cref="TriggerQuickInfoAsync(ITextView, ITrackingPoint, QuickInfoSessionOptions, CancellationToken)"/>
+ /// </exception>
+ /// <param name="cancellationToken">If canceled before the method returns, cancels any computations in progress.</param>
+ /// <param name="textView">
+ /// The <see cref="ITextView" /> for which Quick Info is to be triggered.
+ /// </param>
+ /// <param name="triggerPoint">
+ /// The <see cref="ITrackingPoint" /> in the view's text buffer at which Quick Info should be triggered.
+ /// </param>
+ /// <param name="options">Options for customizing Quick Info behavior.</param>
+ /// <returns>
+ /// An <see cref="IAsyncQuickInfoSession"/> tracking the state of the session or null if there are no items.
+ /// </returns>
+ Task<IAsyncQuickInfoSession> TriggerQuickInfoAsync(
+ ITextView textView,
+ ITrackingPoint triggerPoint = null,
+ QuickInfoSessionOptions options = QuickInfoSessionOptions.None,
+ CancellationToken cancellationToken = default);
+
+ /// <summary>
+ /// Gets Quick Info items for the <see cref="ITextView"/> at the <paramref name="triggerPoint"/>.
+ /// </summary>
+ /// <exception cref="OperationCanceledException">
+ /// <paramref name="cancellationToken"/> was canceled by the caller.
+ /// </exception>
+ /// <exception cref="AggregateException">
+ /// One or more errors occured during query of quick info items sources.
+ /// </exception>
+ /// <param name="cancellationToken">If canceled before the method returns, cancels any computations in progress.</param>
+ /// <param name="textView">
+ /// The <see cref="ITextView" /> for which Quick Info is to be triggered.
+ /// </param>
+ /// <param name="triggerPoint">
+ /// The <see cref="ITrackingPoint" /> in the view's text buffer at which Quick Info should be triggered.
+ /// </param>
+ /// <param name="options">Options for customizing Quick Info behavior.</param>
+ /// <returns>
+ /// A series of Quick Info items and a span for which they are applicable.
+ /// </returns>
+ Task<QuickInfoItemsCollection> GetQuickInfoItemsAsync(
+ ITextView textView,
+ ITrackingPoint triggerPoint,
+ CancellationToken cancellationToken);
+
+ /// <summary>
+ /// Gets the current <see cref="IAsyncQuickInfoSession"/> for the <see cref="ITextView"/>.
+ /// </summary>
+ /// <remarks>
+ /// Quick info is considered to be active if there is a visible, calculating, or recalculating quick info session.
+ /// </remarks>
+ /// <param name="textView">The <see cref="ITextView" /> for which to lookup the session.</param>
+ /// <returns>The session, or <c>null</c> if there is no active session.</returns>
+ IAsyncQuickInfoSession GetSession(ITextView textView);
+ }
+}
diff --git a/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSession.cs b/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSession.cs
new file mode 100644
index 0000000..46e1f17
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSession.cs
@@ -0,0 +1,81 @@
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Threading.Tasks;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Editor;
+ using Microsoft.VisualStudio.Utilities;
+
+ /// <summary>
+ /// Tracks state of a visible or calculating Quick Info session.
+ /// </summary>
+ public interface IAsyncQuickInfoSession : IPropertyOwner
+ {
+ /// <summary>
+ /// Dispatched on the UI thread whenever the Quick Info Session changes state.
+ /// </summary>
+ event EventHandler<QuickInfoSessionStateChangedEventArgs> StateChanged;
+
+ /// <summary>
+ /// The span of text to which this Quick Info session applies.
+ /// </summary>
+ ITrackingSpan ApplicableToSpan { get; }
+
+ /// <summary>
+ /// The ordered, merged collection of content to be displayed in the Quick Info.
+ /// </summary>
+ /// <remarks>
+ /// This field is originally null and is updated with the content once the session has
+ /// finished querying the providers.
+ /// </remarks>
+ IEnumerable<object> Content { get; }
+
+ /// <summary>
+ /// Indicates that this Quick Info has interactive content that can request to stay open.
+ /// </summary>
+ bool HasInteractiveContent { get; }
+
+ /// <summary>
+ /// Specifies attributes of the Quick Info session and Quick Info session presentation.
+ /// </summary>
+ QuickInfoSessionOptions Options { get; }
+
+ /// <summary>
+ /// The current state of the Quick Info session.
+ /// </summary>
+ QuickInfoSessionState State { get; }
+
+ /// <summary>
+ /// The <see cref="ITextView"/> for which this Quick Info session was created.
+ /// </summary>
+ ITextView TextView { get; }
+
+ /// <summary>
+ /// Gets the point at which the Quick Info tip was triggered in the <see cref="ITextView"/>.
+ /// </summary>
+ /// <remarks>
+ /// Returned <see cref="ITrackingPoint"/> is on the buffer requested by the caller.
+ /// </remarks>
+ /// <param name="textBuffer">The <see cref="ITextBuffer"/> relative to which to obtain the point.</param>
+ /// <returns>A <see cref="ITrackingPoint"/> indicating the point over which Quick Info was invoked.</returns>
+ ITrackingPoint GetTriggerPoint(ITextBuffer textBuffer);
+
+ /// <summary>
+ /// Gets the point at which the Quick Info tip was triggered in the <see cref="ITextView"/>.
+ /// </summary>
+ /// <remarks>
+ /// Returned point is on the buffer requested by the caller.
+ /// </remarks>
+ /// <param name="snapshot">The <see cref="ITextSnapshot"/> relative to which to obtain the point.</param>
+ /// <returns>The point over which Quick Info was invoked or <c>null</c> if it does not exist in <paramref name="snapshot"/>.</returns>
+ SnapshotPoint? GetTriggerPoint(ITextSnapshot snapshot);
+
+ /// <summary>
+ /// Dismisses the Quick Info session, if applicable. If the session is already dismissed,
+ /// this method no-ops.
+ /// </summary>
+ /// <returns>A task tracking the completion of the operation.</returns>
+ Task DismissAsync();
+ }
+}
diff --git a/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSource.cs b/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSource.cs
new file mode 100644
index 0000000..9641be1
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSource.cs
@@ -0,0 +1,34 @@
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ using System;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Microsoft.VisualStudio.Text.Adornments;
+ using Microsoft.VisualStudio.Threading;
+
+ /// <summary>
+ /// Source of Quick Info tooltip content item, proffered to the IDE by a <see cref="IAsyncQuickInfoSourceProvider"/>.
+ /// </summary>
+ /// <remarks>
+ /// This class is always constructed and disposed on the UI thread and called on
+ /// a non-UI thread. Callers that require the UI thread must explicitly marshal there with
+ /// <see cref="JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken)"/>.
+ /// Content objects are resolved into UI constructs via the <see cref="IViewElementFactoryService"/>.
+ /// </remarks>
+ public interface IAsyncQuickInfoSource : IDisposable
+ {
+ /// <summary>
+ /// Gets Quick Info item and tracking span via a <see cref="QuickInfoItem"/>.
+ /// </summary>
+ /// <remarks>
+ /// This method is always called on a background thread. Multiple elements can be
+ /// be returned by a single source by wrapping them in a <see cref="ContainerElement"/>.
+ /// </remarks>
+ /// <param name="session">An object tracking the current state of the Quick Info.</param>
+ /// <param name="cancellationToken">Cancels an in-progress computation.</param>
+ /// <returns>item and a tracking span for which these item are applicable.</returns>
+ Task<QuickInfoItem> GetQuickInfoItemAsync(
+ IAsyncQuickInfoSession session,
+ CancellationToken cancellationToken);
+ }
+}
diff --git a/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSourceProvider.cs b/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSourceProvider.cs
new file mode 100644
index 0000000..ffba691
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/IAsyncQuickInfoSourceProvider.cs
@@ -0,0 +1,29 @@
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ using Microsoft.VisualStudio.Text;
+
+ /// <summary>
+ /// A MEF component part that is proffered to the IDE to construct an <see cref="IAsyncQuickInfoSource"/>.
+ /// </summary>
+ /// <remarks>
+ /// This class is always constructed and called on the UI thread.
+ /// </remarks>
+ /// <example>
+ /// [Export(typeof(IAsyncQuickInfoSourceProvider))]
+ /// [Name("Foo QuickInfo Provider")]
+ /// [Order(After = "default")]
+ /// [ContentType("text")]
+ /// </example>
+ public interface IAsyncQuickInfoSourceProvider
+ {
+ /// <summary>
+ /// Creates an <see cref="IAsyncQuickInfoSource"/> for the specified <see cref="ITextBuffer"/>.
+ /// </summary>
+ /// <param name="textBuffer">The <see cref="ITextBuffer"/> for which this source produces items.</param>
+ /// <returns>
+ /// An instance of <see cref="IAsyncQuickInfoSource"/> for <paramref name="textBuffer"/>
+ /// or null if no source could be created.
+ /// </returns>
+ IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer);
+ }
+}
diff --git a/src/Language/Def/Language/QuickInfo/IInteractiveQuickInfoContent.cs b/src/Language/Def/Language/QuickInfo/IInteractiveQuickInfoContent.cs
new file mode 100644
index 0000000..4f39082
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/IInteractiveQuickInfoContent.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation
+// All rights reserved
+
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ /// <summary>
+ /// Represents an interactive Quick Info content. This interface can be used to add an interactive content such as hyperlinks to
+ /// the Quick Info popup.
+ /// If any object implementing this interface is provided to
+ /// <see cref="IAsyncQuickInfoSource"/> via <see cref="IAsyncQuickInfoSource.GetQuickInfoItemAsync(IAsyncQuickInfoSession, System.Threading.CancellationToken,)"/>,
+ /// the Quick Info presenter will allow to interact with this content, particulartly it will keep Quick Info popup open when mouse
+ /// is over it and will allow this content to recieve mouse events.
+ /// </summary>
+ public interface IInteractiveQuickInfoContent
+ {
+ /// <summary>
+ /// Gets whether the interactive Quick Info content wants to keep current Quick Info session open. Until this property is true,
+ /// the <see cref="IAsyncQuickInfoSession"/> containing this content won't be dismissed even if mouse is moved somewhere else.
+ /// This is useful in very rare scenarios when an interactive Quick Info content handles all input interaction, while needs to
+ /// keep this <see cref="IAsyncQuickInfoSession"/> open (the only known example so far is LightBulb in its expanded state hosted in
+ /// Quick Info).
+ /// </summary>
+ bool KeepQuickInfoOpen { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the mouse pointer is located over this interactive Quick Info content,
+ /// including any parts that are out of the Quick Info visual tree (such as popups).
+ /// </summary>
+ bool IsMouseOverAggregated { get; }
+ }
+}
diff --git a/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoBrokerSupport.cs b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoBrokerSupport.cs
new file mode 100644
index 0000000..9def5c8
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoBrokerSupport.cs
@@ -0,0 +1,30 @@
+namespace Microsoft.Internal.VisualStudio.Language.Intellisense
+{
+ using System;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Microsoft.VisualStudio.Language.Intellisense;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Editor;
+ using Microsoft.VisualStudio.Utilities;
+
+ // Bug #512117: Remove compatibility shims for 2nd gen. Quick Info APIs.
+#pragma warning disable 618
+ /// <summary>
+ /// This interface supports the product infrastructure and should not be used.
+ /// </summary>
+ [Obsolete("This interface supports legacy product infrastructure, is subject to breakage without notice, and should not be used")]
+ internal interface ILegacyQuickInfoBrokerSupport : IAsyncQuickInfoBroker
+ {
+ /// <summary>
+ /// This method supports the product infrastructure and should not be used.
+ /// </summary>
+ Task<IAsyncQuickInfoSession> TriggerQuickInfoAsync(
+ ITextView textView,
+ ITrackingPoint triggerPoint,
+ QuickInfoSessionOptions options,
+ PropertyCollection propertyCollection,
+ CancellationToken cancellationToken);
+ }
+#pragma warning restore 618
+}
diff --git a/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoMetadata.cs b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoMetadata.cs
new file mode 100644
index 0000000..3db2d0a
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoMetadata.cs
@@ -0,0 +1,56 @@
+namespace Microsoft.Internal.VisualStudio.Language.Intellisense
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using Microsoft.VisualStudio.Utilities;
+
+#pragma warning disable 618
+ /// <summary>
+ /// This interface supports the product infrastructure and should not be used.
+ /// </summary>
+ /// <remarks>
+ /// This is a MEF metadata view, similar to IContentTypeMetadata, however it uses
+ /// an explicit metadata class to allow it to be internal. Internal MEF metadata
+ /// view interfaces are supported but are currently suffering from intermittent
+ /// type load exceptions resulting from a bug in either the CLR or VS MEF.
+ /// </remarks>
+ [Obsolete("This interface supports legacy product infrastructure, is subject to breakage without notice, and should not be used")]
+ internal class LegacyQuickInfoMetadata : IContentTypeMetadata, IOrderable
+ {
+ public LegacyQuickInfoMetadata(IDictionary<string, object> data)
+ {
+ // Values are all optional.
+ data.TryGetValue(nameof(Name), out var name);
+ data.TryGetValue(nameof(ContentTypes), out var contentTypes);
+ data.TryGetValue(nameof(Before), out var before);
+ data.TryGetValue(nameof(After), out var after);
+
+ this.ContentTypes = (IEnumerable<string>)contentTypes;
+ this.Name = (string)name;
+ this.Before = (IEnumerable<string>)before;
+ this.After = (IEnumerable<string>)after;
+ }
+
+ internal LegacyQuickInfoMetadata(
+ string name,
+ IEnumerable<string> contentTypes,
+ IEnumerable<string> before,
+ IEnumerable<string> after)
+ {
+ this.Name = name;
+ this.ContentTypes = contentTypes;
+ this.Before = before ?? Enumerable.Empty<string>();
+ this.After = after ?? Enumerable.Empty<string>();
+ }
+
+ public IEnumerable<string> ContentTypes { get; }
+
+ public string Name { get; }
+
+ public IEnumerable<string> Before { get; }
+
+ public IEnumerable<string> After { get; }
+ }
+#pragma warning restore 618
+}
diff --git a/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoRecalculateSupport.cs b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoRecalculateSupport.cs
new file mode 100644
index 0000000..290ed4f
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoRecalculateSupport.cs
@@ -0,0 +1,15 @@
+namespace Microsoft.Internal.VisualStudio.Language.Intellisense
+{
+ using System;
+
+#pragma warning disable 618
+ /// <summary>
+ /// This interface supports the product infrastructure and should not be used.
+ /// </summary>
+ [Obsolete("This interface supports legacy product infrastructure, is subject to breakage without notice, and should not be used")]
+ internal interface ILegacyQuickInfoRecalculateSupport
+ {
+ void Recalculate();
+ }
+#pragma warning restore 618
+}
diff --git a/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoSource.cs b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoSource.cs
new file mode 100644
index 0000000..94860a4
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoSource.cs
@@ -0,0 +1,22 @@
+namespace Microsoft.Internal.VisualStudio.Language.Intellisense
+{
+ using System;
+ using System.Collections.Generic;
+ using Microsoft.VisualStudio.Language.Intellisense;
+ using Microsoft.VisualStudio.Text;
+
+ // Bug #512117: Remove compatibility shims for 2nd gen. Quick Info APIs.
+#pragma warning disable 618
+ /// <summary>
+ /// This interface supports the product infrastructure and should not be used.
+ /// </summary>
+ [Obsolete("This interface supports legacy product infrastructure, is subject to breakage without notice, and should not be used")]
+ internal interface ILegacyQuickInfoSource : IAsyncQuickInfoSource
+ {
+ /// <summary>
+ /// This interface supports the product infrastructure and should not be used.
+ /// </summary>
+ void AugmentQuickInfoSession(IAsyncQuickInfoSession session, IList<object> content, out ITrackingSpan applicableToSpan);
+ }
+#pragma warning restore 618
+}
diff --git a/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoSourcesSupport.cs b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoSourcesSupport.cs
new file mode 100644
index 0000000..e47a03f
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/Legacy/ILegacyQuickInfoSourcesSupport.cs
@@ -0,0 +1,21 @@
+namespace Microsoft.Internal.VisualStudio.Language.Intellisense
+{
+ using System;
+ using System.Collections.Generic;
+ using Microsoft.VisualStudio.Language.Intellisense;
+
+ // Bug #512117: Remove compatibility shims for 2nd gen. Quick Info APIs.
+#pragma warning disable 618
+ /// <summary>
+ /// This interface supports the product infrastructure and should not be used.
+ /// </summary>
+ [Obsolete("This interface supports legacy product infrastructure, is subject to breakage without notice, and should not be used")]
+ internal interface ILegacyQuickInfoSourcesSupport
+ {
+ /// <summary>
+ /// This interface supports the product infrastructure and should not be used.
+ /// </summary>
+ IEnumerable<Lazy<IAsyncQuickInfoSourceProvider, LegacyQuickInfoMetadata>> LegacySources { get; }
+ }
+#pragma warning restore 618
+}
diff --git a/src/Language/Def/Language/QuickInfo/QuickInfoItem.cs b/src/Language/Def/Language/QuickInfo/QuickInfoItem.cs
new file mode 100644
index 0000000..b890b7a
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/QuickInfoItem.cs
@@ -0,0 +1,37 @@
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ using System;
+ using Microsoft.VisualStudio.Text;
+ using Microsoft.VisualStudio.Text.Adornments;
+
+ /// <summary>
+ /// The result generated by an <see cref="IAsyncQuickInfoSource"/>.
+ /// </summary>
+ public sealed class QuickInfoItem
+ {
+ /// <summary>
+ /// Constructs a new instance of <see cref="QuickInfoItem"/>.
+ /// </summary>
+ /// <exception cref="ArgumentNullException">Thrown if item is null.</exception>
+ /// <param name="applicableToSpan">The span to which <paramref name="item"/> is applicable.</param>
+ /// <param name="item">The Quick Info item.</param>
+ public QuickInfoItem(ITrackingSpan applicableToSpan, object item)
+ {
+ this.ApplicableToSpan = applicableToSpan;
+ this.Item = item ?? throw new ArgumentNullException(nameof(item));
+ }
+
+ /// <summary>
+ /// The <see cref="ITrackingSpan"/> to which <see cref="Item"/> is applicable.
+ /// </summary>
+ /// <remarks>
+ /// This parameter can be null.
+ /// </remarks>
+ public ITrackingSpan ApplicableToSpan { get; }
+
+ /// <summary>
+ /// The item to be displayed in the Quick Info <see cref="IToolTipPresenter"/>.
+ /// </summary>
+ public object Item { get; }
+ }
+}
diff --git a/src/Language/Def/Language/QuickInfo/QuickInfoItemsCollection.cs b/src/Language/Def/Language/QuickInfo/QuickInfoItemsCollection.cs
new file mode 100644
index 0000000..1d89f8c
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/QuickInfoItemsCollection.cs
@@ -0,0 +1,35 @@
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Immutable;
+ using Microsoft.VisualStudio.Text;
+
+ /// <summary>
+ /// An immutable collection of Quick Info items and the span to which they are applicable.
+ /// </summary>
+ public sealed class QuickInfoItemsCollection
+ {
+ /// <summary>
+ /// The collection of Quick Info items.
+ /// </summary>
+ public IEnumerable<object> Items { get; }
+
+ /// <summary>
+ /// The span to which the Quick Info items apply.
+ /// </summary>
+ public ITrackingSpan ApplicableToSpan { get; }
+
+ /// <summary>
+ /// Creates a new <see cref="QuickInfoItemsCollection"/>.
+ /// </summary>
+ /// <param name="items">The Quick Info items.</param>
+ /// <param name="applicableToSpan">The span to which the items are applicable.</param>
+ public QuickInfoItemsCollection(IEnumerable<object> items, ITrackingSpan applicableToSpan)
+ {
+ this.Items = items.ToImmutableList() ?? throw new ArgumentNullException(nameof(items));
+ this.ApplicableToSpan = applicableToSpan ?? throw new ArgumentNullException(nameof(applicableToSpan));
+ }
+ }
+}
+
diff --git a/src/Language/Def/Language/QuickInfo/QuickInfoSessionOptions.cs b/src/Language/Def/Language/QuickInfo/QuickInfoSessionOptions.cs
new file mode 100644
index 0000000..38b4e05
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/QuickInfoSessionOptions.cs
@@ -0,0 +1,21 @@
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ using System;
+
+ /// <summary>
+ /// Options for customization of Quick Info behavior.
+ /// </summary>
+ [Flags]
+ public enum QuickInfoSessionOptions
+ {
+ /// <summary>
+ /// No options.
+ /// </summary>
+ None = 0b00000000,
+
+ /// <summary>
+ /// Dismisses Quick Info when the mouse moves away.
+ /// </summary>
+ TrackMouse = 0b00000001
+ }
+}
diff --git a/src/Language/Def/Language/QuickInfo/QuickInfoSessionState.cs b/src/Language/Def/Language/QuickInfo/QuickInfoSessionState.cs
new file mode 100644
index 0000000..6767c1f
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/QuickInfoSessionState.cs
@@ -0,0 +1,28 @@
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ /// <summary>
+ /// Defines the possible <see cref="IAsyncQuickInfoSession"/> states.
+ /// </summary>
+ public enum QuickInfoSessionState
+ {
+ /// <summary>
+ /// Session has been created but is not yet active.
+ /// </summary>
+ Created,
+
+ /// <summary>
+ /// Session is currently computing Quick Info content.
+ /// </summary>
+ Calculating,
+
+ /// <summary>
+ /// Session has been dismissed and is no longer active.
+ /// </summary>
+ Dismissed,
+
+ /// <summary>
+ /// Computation is complete and session is visible.
+ /// </summary>
+ Visible
+ }
+}
diff --git a/src/Language/Def/Language/QuickInfo/QuickInfoStateChangedEventArgs.cs b/src/Language/Def/Language/QuickInfo/QuickInfoStateChangedEventArgs.cs
new file mode 100644
index 0000000..539dbf0
--- /dev/null
+++ b/src/Language/Def/Language/QuickInfo/QuickInfoStateChangedEventArgs.cs
@@ -0,0 +1,31 @@
+namespace Microsoft.VisualStudio.Language.Intellisense
+{
+ using System;
+
+ /// <summary>
+ /// Arguments for the <see cref="IAsyncQuickInfoSession.StateChanged"/> event.
+ /// </summary>
+ public sealed class QuickInfoSessionStateChangedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// Creates a new instance of <see cref="QuickInfoSessionStateChangedEventArgs"/>.
+ /// </summary>
+ /// <param name="oldState">The state before the transition.</param>
+ /// <param name="newState">The state after the transition.</param>
+ public QuickInfoSessionStateChangedEventArgs(QuickInfoSessionState oldState, QuickInfoSessionState newState)
+ {
+ this.OldState = oldState;
+ this.NewState = newState;
+ }
+
+ /// <summary>
+ /// The state before the transition.
+ /// </summary>
+ public QuickInfoSessionState OldState { get; }
+
+ /// <summary>
+ /// The state after the transition.
+ /// </summary>
+ public QuickInfoSessionState NewState { get; }
+ }
+}
diff --git a/src/Language/Impl/Language/AsyncCompletion/AsyncCompletionBroker.cs b/src/Language/Impl/Language/AsyncCompletion/AsyncCompletionBroker.cs
new file mode 100644
index 0000000..66c3976
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/AsyncCompletionBroker.cs
@@ -0,0 +1,423 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Threading;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Projection;
+using Microsoft.VisualStudio.Text.Utilities;
+using Microsoft.VisualStudio.Threading;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ [Export(typeof(IAsyncCompletionBroker))]
+ [Export(typeof(AsyncCompletionBroker))]
+ internal sealed class AsyncCompletionBroker : IAsyncCompletionBroker
+ {
+ [Import]
+ private IGuardedOperations GuardedOperations;
+
+ [Import]
+ private JoinableTaskContext JoinableTaskContext;
+
+ [Import]
+ private IContentTypeRegistryService ContentTypeRegistryService;
+
+ [Import]
+ private CompletionAvailabilityUtility CompletionAvailability;
+
+ [ImportMany]
+ private IEnumerable<Lazy<IAsyncCompletionSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> UnorderedCompletionSourceProviders;
+
+ [ImportMany]
+ private IEnumerable<Lazy<IAsyncCompletionItemManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> UnorderedCompletionItemManagerProviders;
+
+ [ImportMany]
+ private IEnumerable<Lazy<IAsyncCompletionCommitManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> UnorderedCompletionCommitManagerProviders;
+
+ [ImportMany]
+ private IEnumerable<Lazy<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> UnorderedPresenterProviders;
+
+ // Used for telemetry
+ [Import(AllowDefault = true)]
+ private ILoggingServiceInternal Logger;
+
+ // Used for legacy telemetry
+ [Import(AllowDefault = true)]
+ private ITextDocumentFactoryService TextDocumentFactoryService;
+
+ private IList<Lazy<IAsyncCompletionSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> _orderedCompletionSourceProviders;
+ private IList<Lazy<IAsyncCompletionSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> OrderedCompletionSourceProviders
+ => _orderedCompletionSourceProviders ?? (_orderedCompletionSourceProviders = Orderer.Order(UnorderedCompletionSourceProviders));
+
+ private IList<Lazy<IAsyncCompletionItemManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> _orderedCompletionItemManagerProviders;
+ private IList<Lazy<IAsyncCompletionItemManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> OrderedCompletionItemManagerProviders
+ => _orderedCompletionItemManagerProviders ?? (_orderedCompletionItemManagerProviders = Orderer.Order(UnorderedCompletionItemManagerProviders));
+
+ private IList<Lazy<IAsyncCompletionCommitManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> _orderedCompletionCommitManagerProviders;
+ private IList<Lazy<IAsyncCompletionCommitManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> OrderedCompletionCommitManagerProviders
+ => _orderedCompletionCommitManagerProviders ?? (_orderedCompletionCommitManagerProviders = Orderer.Order(UnorderedCompletionCommitManagerProviders));
+
+ private IList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> _orderedPresenterProviders;
+ private IList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> OrderedPresenterProviders
+ => _orderedPresenterProviders ?? (_orderedPresenterProviders = Orderer.Order(UnorderedPresenterProviders));
+
+ private bool firstRun = true; // used only for diagnostics
+ private bool _firstInvocationReported; // used for "time to code"
+ private StableContentTypeComparer _contentTypeComparer;
+ private Dictionary<IContentType, bool> _providerAvailabilityByContentType = new Dictionary<IContentType, bool>();
+
+ public event EventHandler<CompletionTriggeredEventArgs> CompletionTriggered;
+
+ #region IAsyncCompletionBroker implementation
+
+ public bool IsCompletionActive(ITextView textView)
+ {
+ return textView.Properties.ContainsProperty(typeof(IAsyncCompletionSession));
+ }
+
+ public bool IsCompletionSupported(IContentType contentType)
+ {
+ // This will call HasCompletionProviders among doing other checks
+ return CompletionAvailability.IsAvailable(contentType);
+ }
+
+ /// <summary>
+ /// Returns whether there exist any <see cref="IAsyncCompletionSourceProvider"/>
+ /// for the provided <see cref="IContentType"/> or any of its base content types.
+ /// Since MEF parts don't change on runtime, the answer is cached per <see cref="IContentType"/> for faster retrieval.
+ /// </summary>
+ internal bool HasCompletionProviders(IContentType contentType)
+ {
+ // Use cache if available
+ if (_providerAvailabilityByContentType.TryGetValue(contentType, out bool featureIsAvailable))
+ return featureIsAvailable;
+
+ featureIsAvailable = UnorderedCompletionSourceProviders.Any(n => n.Metadata.ContentTypes.Any(ct => contentType.IsOfType(ct)));
+
+ _providerAvailabilityByContentType[contentType] = featureIsAvailable;
+ return featureIsAvailable;
+ }
+
+ public IAsyncCompletionSession GetSession(ITextView textView)
+ {
+ if (textView.Properties.TryGetProperty(typeof(IAsyncCompletionSession), out IAsyncCompletionSession session))
+ {
+ return session;
+ }
+ return null;
+ }
+
+ public IAsyncCompletionSession TriggerCompletion(ITextView textView, SnapshotPoint triggerLocation, char typedChar, CancellationToken token)
+ {
+ var session = GetSession(textView);
+ if (session != null)
+ {
+ return session;
+ }
+
+ // This is a simple check that only queries the feature service.
+ // If it succeeds, we will map triggerLocation to available buffers to discover MEF parts.
+ // This is expensive but projected languages require it to discover parts in all available buffers.
+ // To avoid doing this work, call IsCompletionSupported with appropriate IContentType prior to calling TriggerCompletion
+ if (!CompletionAvailability.IsAvailable(textView, contentTypeToCheckBlacklist: triggerLocation.Snapshot.ContentType))
+ return null;
+
+ if (!JoinableTaskContext.IsOnMainThread)
+ throw new InvalidOperationException($"This method must be callled on the UI thread.");
+
+ var telemetryHost = GetOrCreateTelemetry(textView);
+ var telemetry = new CompletionSessionTelemetry(telemetryHost);
+
+ GetCommitManagersAndChars(textView.BufferGraph, textView.Roles, textView, triggerLocation, GetCommitManagerProviders, telemetry,
+ out var managersWithBuffers, out var potentialCommitChars);
+
+ GetCompletionSources(textView.TextBuffer, textView.Roles, textView, textView.BufferGraph, triggerLocation, GetItemSourceProviders, telemetry, typedChar, token,
+ out var sourcesWithLocations, out var applicableToSpan);
+
+ // No source declared an appropriate ApplicableToSpan
+ if (applicableToSpan == default)
+ return null;
+
+ if (_contentTypeComparer == null)
+ _contentTypeComparer = new StableContentTypeComparer(ContentTypeRegistryService);
+
+ var itemManager = GetItemManager(textView.BufferGraph, textView.Roles, textView, triggerLocation, GetItemManagerProviders, _contentTypeComparer);
+ var presenterProvider = GetPresenterProvider(textView.BufferGraph, textView.Roles, triggerLocation, GetPresenters, _contentTypeComparer);
+
+ session = new AsyncCompletionSession(applicableToSpan, potentialCommitChars, JoinableTaskContext, presenterProvider, sourcesWithLocations, managersWithBuffers, itemManager, this, textView, telemetry, GuardedOperations);
+ textView.Properties.AddProperty(typeof(IAsyncCompletionSession), session);
+
+ textView.Closed += TextView_Closed;
+ EmulateLegacyCompletionTelemetry(textView);
+ GuardedOperations.RaiseEvent(this, CompletionTriggered, new CompletionTriggeredEventArgs(session, textView));
+
+ return session;
+ }
+
+ #endregion
+
+ #region Internal communication with AsyncCompletionSession
+
+ /// <summary>
+ /// This method is used by <see cref="IAsyncCompletionSession"/> to inform the broker that it should forget about the session.
+ /// Invoked as a result of dismissing. This method does not dismiss the session!
+ /// </summary>
+ /// <param name="session">Session being dismissed</param>
+#pragma warning disable CA1822 // Member does not access instance data and can be marked as static
+ internal void ForgetSession(IAsyncCompletionSession session)
+ {
+ session.TextView.Properties.RemoveProperty(typeof(IAsyncCompletionSession));
+ }
+#pragma warning restore CA1822
+
+ #endregion
+
+ #region MEF part helper methods
+
+ private void GetCommitManagersAndChars(
+ IBufferGraph bufferGraph,
+ ITextViewRoleSet roles,
+ ITextView textViewForGetOrCreate, /* This name conveys that we're using ITextView only to init the MEF part. this is subject to change. */
+ SnapshotPoint triggerLocation,
+ Func<IContentType, ITextViewRoleSet, IReadOnlyList<Lazy<IAsyncCompletionCommitManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>>> getImports,
+ CompletionSessionTelemetry telemetry,
+ out IList<(IAsyncCompletionCommitManager, ITextBuffer)> managersWithBuffers,
+ out ImmutableArray<char> potentialCommitChars)
+ {
+ var commitManagersWithData = MetadataUtilities<IAsyncCompletionCommitManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>
+ .GetBuffersAndImports(bufferGraph, roles, triggerLocation, getImports);
+
+ var potentialCommitCharsBuilder = ImmutableArray.CreateBuilder<char>();
+ managersWithBuffers = new List<(IAsyncCompletionCommitManager, ITextBuffer)>(1);
+ foreach (var (buffer, point, import) in commitManagersWithData)
+ {
+ telemetry.UiStopwatch.Restart();
+ var managerProvider = GuardedOperations.InstantiateExtension(this, import);
+ var manager = GuardedOperations.CallExtensionPoint(
+ errorSource: managerProvider,
+ call: () => managerProvider.GetOrCreate(textViewForGetOrCreate),
+ valueOnThrow: null);
+
+ if (manager == null)
+ continue;
+
+ GuardedOperations.CallExtensionPoint(
+ errorSource: manager,
+ call: () =>
+ {
+ var characters = manager.PotentialCommitCharacters;
+ potentialCommitCharsBuilder.AddRange(characters);
+ });
+ managersWithBuffers.Add((manager, buffer));
+ telemetry.UiStopwatch.Stop();
+ telemetry.RecordObtainingCommitManagerData(manager, telemetry.UiStopwatch.ElapsedMilliseconds);
+ }
+ potentialCommitChars = potentialCommitCharsBuilder.ToImmutable();
+ }
+
+ private void GetCompletionSources(
+ ITextBuffer editBuffer,
+ ITextViewRoleSet roles,
+ ITextView textViewForGetOrCreate, /* This name conveys that we're using ITextView only to init the MEF part. this is subject to change. */
+ IBufferGraph bufferGraph,
+ SnapshotPoint triggerLocation,
+ Func<IContentType, ITextViewRoleSet, IReadOnlyList<Lazy<IAsyncCompletionSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>>> getImports,
+ CompletionSessionTelemetry telemetry,
+ char typedChar,
+ CancellationToken token,
+ out IList<(IAsyncCompletionSource Source, SnapshotPoint Point)> sourcesWithLocations,
+ out SnapshotSpan applicableToSpan)
+ {
+ var sourcesWithData = MetadataUtilities<IAsyncCompletionSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>
+ .GetBuffersAndImports(bufferGraph, roles, triggerLocation, getImports);
+
+ var applicableToSpanBuilder = default(SnapshotSpan);
+ bool applicableToSpanExists = false;
+ var sourcesWithLocationsBuider = new List<(IAsyncCompletionSource, SnapshotPoint)>(sourcesWithData.Count());
+
+ foreach (var (buffer, point, import) in sourcesWithData)
+ {
+ telemetry.UiStopwatch.Restart();
+
+ var sourceProvider = GuardedOperations.InstantiateExtension(this, import);
+ var source = GuardedOperations.CallExtensionPoint(
+ errorSource: sourceProvider,
+ call: () => sourceProvider.GetOrCreate(textViewForGetOrCreate),
+ valueOnThrow: null);
+
+ if (source == null)
+ continue;
+
+ applicableToSpanExists |= GuardedOperations.CallExtensionPoint(
+ errorSource: source,
+ call: () =>
+ {
+ // We want to iterate through all sources and add them to collection
+ sourcesWithLocationsBuider.Add((source, point));
+ // Get the span only if we haven't received one yet
+ if (!applicableToSpanExists)
+ return source.TryGetApplicableToSpan(typedChar, point, out applicableToSpanBuilder, token);
+ else
+ return false; // applicableToSpanExists is already true, so it doesn't matter what we return here
+ },
+ valueOnThrow: false);
+
+ telemetry.UiStopwatch.Stop();
+ telemetry.RecordObtainingSourceSpan(source, telemetry.UiStopwatch.ElapsedMilliseconds);
+ }
+
+ // Assume that sources are ordered. If this source is the first one to provide span, map it to the view's edit buffer and use it for completion,
+ if (applicableToSpanExists)
+ {
+ var mappingSpan = bufferGraph.CreateMappingSpan(applicableToSpanBuilder, SpanTrackingMode.EdgeInclusive);
+ applicableToSpanBuilder = mappingSpan.GetSpans(editBuffer)[0];
+ }
+
+ // Copying temporary values because we can't access out&ref params in lambdas
+ sourcesWithLocations = sourcesWithLocationsBuider;
+ applicableToSpan = applicableToSpanBuilder;
+ }
+
+ private IAsyncCompletionItemManager GetItemManager(
+ IBufferGraph bufferGraph,
+ ITextViewRoleSet textViewRoles,
+ ITextView textViewForGetOrCreate, /* This name conveys that we're using ITextView only to init the MEF part. this is subject to change. */
+ SnapshotPoint triggerLocation,
+ Func<IContentType, ITextViewRoleSet, IReadOnlyList<Lazy<IAsyncCompletionItemManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>>> getImports,
+ StableContentTypeComparer contentTypeComparer
+ )
+ {
+ var itemManagerProvidersWithData = MetadataUtilities<IAsyncCompletionItemManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>
+ .GetOrderedBuffersAndImports(bufferGraph, textViewRoles, triggerLocation, getImports, contentTypeComparer);
+ if (!itemManagerProvidersWithData.Any())
+ {
+ // This should never happen because we provide a default and IsCompletionFeatureAvailable would have returned false
+ throw new InvalidOperationException("No completion services not found. Completion will be unavailable.");
+ }
+
+ var bestItemManagerProvider = GuardedOperations.InstantiateExtension(this, itemManagerProvidersWithData.First().import);
+ return GuardedOperations.CallExtensionPoint(bestItemManagerProvider, () => bestItemManagerProvider.GetOrCreate(textViewForGetOrCreate), null);
+ }
+
+ private ICompletionPresenterProvider GetPresenterProvider(
+ IBufferGraph bufferGraph,
+ ITextViewRoleSet textViewRoles,
+ SnapshotPoint triggerLocation,
+ Func<IContentType, ITextViewRoleSet, IReadOnlyList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>>> getImports,
+ StableContentTypeComparer contentTypeComparer)
+ {
+ var presenterProvidersWithData = MetadataUtilities<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>
+ .GetOrderedBuffersAndImports(bufferGraph, textViewRoles, triggerLocation, getImports, contentTypeComparer);
+ ICompletionPresenterProvider presenterProvider = null;
+ if (presenterProvidersWithData.Any())
+ presenterProvider = GuardedOperations.InstantiateExtension(this, presenterProvidersWithData.First().import);
+
+ if (firstRun)
+ {
+ System.Diagnostics.Debug.Assert(presenterProvider != null, $"No instance of {nameof(ICompletionPresenterProvider)} is loaded. Completion will work without the UI.");
+ firstRun = false;
+ }
+
+ return presenterProvider;
+ }
+
+ private IReadOnlyList<Lazy<IAsyncCompletionSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> GetItemSourceProviders(IContentType contentType, ITextViewRoleSet textViewRoles)
+ {
+ return OrderedCompletionSourceProviders.Where(n => n.Metadata.ContentTypes.Any(c => contentType.IsOfType(c)) && (n.Metadata.TextViewRoles == null || textViewRoles.ContainsAny(n.Metadata.TextViewRoles))).ToList();
+ }
+
+ private IReadOnlyList<Lazy<IAsyncCompletionItemManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> GetItemManagerProviders(IContentType contentType, ITextViewRoleSet textViewRoles)
+ {
+ return OrderedCompletionItemManagerProviders.Where(n => n.Metadata.ContentTypes.Any(c => contentType.IsOfType(c)) && (n.Metadata.TextViewRoles == null || textViewRoles.ContainsAny(n.Metadata.TextViewRoles))).OrderBy(n => n.Metadata.ContentTypes, _contentTypeComparer).ToList();
+ }
+
+ private IReadOnlyList<Lazy<IAsyncCompletionCommitManagerProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> GetCommitManagerProviders(IContentType contentType, ITextViewRoleSet textViewRoles)
+ {
+ return OrderedCompletionCommitManagerProviders.Where(n => n.Metadata.ContentTypes.Any(c => contentType.IsOfType(c)) && (n.Metadata.TextViewRoles == null || textViewRoles.ContainsAny(n.Metadata.TextViewRoles))).ToList();
+ }
+
+ private IReadOnlyList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> GetPresenters(IContentType contentType, ITextViewRoleSet textViewRoles)
+ {
+ return OrderedPresenterProviders.Where(n => n.Metadata.ContentTypes.Any(c => contentType.IsOfType(c)) && (n.Metadata.TextViewRoles == null || textViewRoles.ContainsAny(n.Metadata.TextViewRoles))).OrderBy(n => n.Metadata.ContentTypes, _contentTypeComparer).ToList();
+ }
+
+ #endregion
+
+ #region Telemetry
+
+ private CompletionTelemetryHost GetOrCreateTelemetry(ITextView textView)
+ {
+ if (textView.Properties.TryGetProperty(typeof(CompletionTelemetryHost), out CompletionTelemetryHost telemetry))
+ {
+ return telemetry;
+ }
+ else
+ {
+ var newTelemetry = new CompletionTelemetryHost(Logger, this);
+ textView.Properties.AddProperty(typeof(CompletionTelemetryHost), newTelemetry);
+ return newTelemetry;
+ }
+ }
+
+#pragma warning disable CA1822 // Member does not access instance data and can be marked as static
+ private static void SendTelemetry(ITextView textView)
+ {
+ if (textView.Properties.TryGetProperty(typeof(CompletionTelemetryHost), out CompletionTelemetryHost telemetry))
+ {
+ telemetry.Send();
+ textView.Properties.RemoveProperty(typeof(CompletionTelemetryHost));
+ }
+ }
+#pragma warning restore CA1822
+
+ // Parity with legacy telemetry
+ private void EmulateLegacyCompletionTelemetry(ITextView textView)
+ {
+ if (Logger == null || _firstInvocationReported)
+ return;
+
+ string GetFileExtension(ITextBuffer buffer)
+ {
+ var documentFactoryService = TextDocumentFactoryService;
+ if (buffer != null && documentFactoryService != null)
+ {
+ documentFactoryService.TryGetTextDocument(buffer, out ITextDocument currentDocument);
+ if (currentDocument != null && currentDocument.FilePath != null)
+ {
+ return System.IO.Path.GetExtension(currentDocument.FilePath);
+ }
+ }
+ return null;
+ }
+ var fileExtension = GetFileExtension(textView.TextBuffer) ?? "Unknown";
+ var reportedContentType = textView.TextBuffer.ContentType?.ToString() ?? "Unknown";
+
+ _firstInvocationReported = true;
+ Logger.PostEvent(TelemetryEventType.Operation, "VS/Editor/IntellisenseFirstRun/Opened", TelemetryResult.Success,
+ ("VS.Editor.IntellisenseFirstRun.Opened.ContentType", reportedContentType),
+ ("VS.Editor.IntellisenseFirstRun.Opened.FileExtension", fileExtension));
+ }
+
+ #endregion
+
+ private void TextView_Closed(object sender, EventArgs e)
+ {
+ var view = (ITextView)sender;
+ view.Closed -= TextView_Closed;
+ GetSession(view)?.Dismiss();
+ try
+ {
+ SendTelemetry(view);
+ }
+ catch (Exception ex)
+ {
+ GuardedOperations.HandleException(this, ex);
+ }
+ }
+ }
+}
diff --git a/src/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs b/src/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs
new file mode 100644
index 0000000..aad950d
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/AsyncCompletionSession.cs
@@ -0,0 +1,1030 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Threading;
+using Microsoft.VisualStudio.Utilities;
+using Strings = Microsoft.VisualStudio.Language.Intellisense.Implementation.Strings;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ /// <summary>
+ /// Holds a state of the session
+ /// and a reference to the UI element
+ /// </summary>
+ internal class AsyncCompletionSession : IAsyncCompletionSession, IAsyncCompletionSessionOperations, IModelComputationCallbackHandler<CompletionModel>
+ {
+ // Available data and services
+ private readonly IList<(IAsyncCompletionSource Source, SnapshotPoint Point)> _completionSources;
+ private readonly IList<(IAsyncCompletionCommitManager, ITextBuffer)> _commitManagers;
+ private readonly IAsyncCompletionItemManager _completionItemManager;
+ private readonly JoinableTaskContext JoinableTaskContext;
+ private readonly ICompletionPresenterProvider _presenterProvider;
+ private readonly AsyncCompletionBroker _broker;
+ private readonly ITextView _textView;
+ private readonly IGuardedOperations _guardedOperations;
+ private readonly ImmutableArray<char> _potentialCommitChars;
+
+ // Presentation:
+ private ICompletionPresenter _gui; // Must be accessed from GUI thread
+ private readonly int PageStepSize;
+ private const int FirstIndex = 0;
+
+ // Computation state machine
+ private ModelComputation<CompletionModel> _computation;
+ private readonly CancellationTokenSource _computationCancellation = new CancellationTokenSource();
+ private int _lastFilteringTaskId;
+
+ // IAsyncCompletionSessionOperations properties for shims
+ public bool IsStarted => _computation != null;
+
+ // ------------------------------------------------------------------------
+ // Fixed completion model data that is guaranteed not to change when another thread accesses it.
+ // Rare exceptions:
+ // * model is Unavailable - we change ApplicableToSpan on the worker thread, but we know that UI thread won't access it
+ // * session was triggered in virtual whitespace, but not updated yet. We update ApplicableToSpan, and we know that worker thread won't access it.
+
+ /// <summary>
+ /// Span pertinent to this completion.
+ /// </summary>
+ public ITrackingSpan ApplicableToSpan { get; set; }
+
+ /// <summary>
+ /// Stores the initial reason this session was triggererd.
+ /// </summary>
+ private InitialTrigger InitialTrigger { get; set; }
+
+ /// <summary>
+ /// Text to display in place of suggestion mode when filtered text is empty.
+ /// </summary>
+ private SuggestionItemOptions SuggestionItemOptions { get; set; }
+
+ /// <summary>
+ /// Source that will provide tooltip for the suggestion item.
+ /// </summary>
+ private IAsyncCompletionSource SuggestionModeCompletionItemSource { get; set; }
+
+ // ------------------------------------------------------------------------
+
+ /// <summary>
+ /// Telemetry aggregator for this session
+ /// </summary>
+ private readonly CompletionSessionTelemetry _telemetry;
+
+ /// <summary>
+ /// Self imposed maximum delay for commits due to user double-clicking completion item in the UI
+ /// </summary>
+ private static readonly TimeSpan MaxCommitDelayWhenClicked = TimeSpan.FromSeconds(1);
+
+ private static SuggestionItemOptions DefaultSuggestionModeOptions = new SuggestionItemOptions(string.Empty, Strings.SuggestionModeDefaultTooltip);
+
+ // Facilitate experience when there are no items to display
+ private bool _selectionModeBeforeNoResultFallback;
+ private bool _inNoResultFallback;
+ private bool _ignoreCaretMovement;
+
+ public event EventHandler<CompletionItemEventArgs> ItemCommitted;
+ public event EventHandler Dismissed;
+ public event EventHandler<ComputedCompletionItemsEventArgs> ItemsUpdated;
+
+ public ITextView TextView => _textView;
+
+ // When set, UI will no longer be updated
+ public bool IsDismissed { get; private set; }
+
+ public PropertyCollection Properties { get; }
+
+ public AsyncCompletionSession(SnapshotSpan initialApplicableToSpan, ImmutableArray<char> potentialCommitChars,
+ JoinableTaskContext joinableTaskContext, ICompletionPresenterProvider presenterProvider,
+ IList<(IAsyncCompletionSource, SnapshotPoint)> completionSources, IList<(IAsyncCompletionCommitManager, ITextBuffer)> commitManagers,
+ IAsyncCompletionItemManager completionService, AsyncCompletionBroker broker, ITextView textView, CompletionSessionTelemetry telemetry,
+ IGuardedOperations guardedOperations)
+ {
+ _potentialCommitChars = potentialCommitChars;
+ JoinableTaskContext = joinableTaskContext;
+ _presenterProvider = presenterProvider;
+ _broker = broker;
+ _completionSources = completionSources; // still prorotype at the momemnt.
+ _commitManagers = commitManagers;
+ _completionItemManager = completionService;
+ _textView = textView;
+ _guardedOperations = guardedOperations;
+ ApplicableToSpan = initialApplicableToSpan.Snapshot.CreateTrackingSpan(initialApplicableToSpan, SpanTrackingMode.EdgeInclusive);
+ _telemetry = telemetry;
+ PageStepSize = presenterProvider?.Options.ResultsPerPage ?? 1;
+ _textView.Caret.PositionChanged += OnCaretPositionChanged;
+ Properties = new PropertyCollection();
+ }
+
+ bool IAsyncCompletionSession.ShouldCommit(char typedChar, SnapshotPoint triggerLocation, CancellationToken token)
+ {
+ if (!JoinableTaskContext.IsOnMainThread)
+ throw new InvalidOperationException($"This method must be callled on the UI thread.");
+
+ if (!_potentialCommitChars.Contains(typedChar))
+ return false;
+
+ var mappingPoint = _textView.BufferGraph.CreateMappingPoint(triggerLocation, PointTrackingMode.Negative);
+ return _commitManagers
+ .Select(n => (n.Item1, mappingPoint.GetPoint(n.Item2, PositionAffinity.Predecessor)))
+ .Where(n => n.Item2.HasValue)
+ .Any(n => _guardedOperations.CallExtensionPoint(
+ errorSource: n.Item1,
+ call: () => n.Item1.ShouldCommitCompletion(typedChar, n.Item2.Value, token),
+ valueOnThrow: false));
+ }
+
+ bool IAsyncCompletionSession.CommitIfUnique(CancellationToken token)
+ {
+ if (IsDismissed)
+ return false;
+
+ if (!JoinableTaskContext.IsOnMainThread)
+ throw new InvalidOperationException($"This method must be callled on the UI thread.");
+
+ _telemetry.UiStopwatch.Restart();
+ var lastModel = _computation.WaitAndGetResult(cancelUi: true, token);
+ _telemetry.UiStopwatch.Stop();
+ _telemetry.RecordBlockingWaitForComputation(_telemetry.UiStopwatch.ElapsedMilliseconds);
+
+ if (lastModel == null)
+ {
+ return false;
+ }
+ else if (lastModel.Uninitialized)
+ {
+ return false;
+ }
+ else if (lastModel.UniqueItem != null)
+ {
+ var behavior = CommitItem(default, lastModel.UniqueItem, ApplicableToSpan, token);
+ if (behavior == CommitBehavior.CancelCommit)
+ {
+ // Show the UI, because waitAndGetResult canceled showing the UI.
+ UpdateUiInner(lastModel); // We are on the UI thread, so we may call UpdateUiInner
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ else if (!lastModel.PresentedItems.IsDefaultOrEmpty && lastModel.PresentedItems.Length == 1)
+ {
+ var behavior = CommitItem(default, lastModel.PresentedItems[0].CompletionItem, ApplicableToSpan, token);
+ if (behavior == CommitBehavior.CancelCommit)
+ {
+ // Show the UI, because waitAndGetResult canceled showing the UI.
+ UpdateUiInner(lastModel); // We are on the UI thread, so we may call UpdateUiInner
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ else
+ {
+ // Show the UI, because waitAndGetResult canceled showing the UI.
+ UpdateUiInner(lastModel); // We are on the UI thread, so we may call UpdateUiInner
+ return false;
+ }
+ }
+
+ CommitBehavior IAsyncCompletionSession.Commit(char typedChar, CancellationToken token)
+ {
+ if (IsDismissed)
+ return CommitBehavior.None;
+
+ if (!JoinableTaskContext.IsOnMainThread)
+ throw new InvalidOperationException($"This method must be callled on the UI thread.");
+
+ _telemetry.UiStopwatch.Restart();
+ var lastModel = _computation.WaitAndGetResult(cancelUi: true, token);
+ _telemetry.UiStopwatch.Stop();
+ _telemetry.RecordBlockingWaitForComputation(_telemetry.UiStopwatch.ElapsedMilliseconds);
+
+ if (lastModel == null)
+ {
+ ((IAsyncCompletionSession)this).Dismiss();
+ return CommitBehavior.None;
+ }
+ else if (lastModel.Uninitialized)
+ {
+ ((IAsyncCompletionSession)this).Dismiss();
+ return CommitBehavior.None;
+ }
+ else if (lastModel.UseSoftSelection && !(typedChar.Equals(default) || typedChar.Equals('\t')) )
+ {
+ // In soft selection mode, user commits explicitly (click, tab, e.g. not tied to a text change). Otherwise, we dismiss the session
+ ((IAsyncCompletionSession)this).Dismiss();
+ return CommitBehavior.None;
+ }
+ else if (lastModel.SelectSuggestionItem && string.IsNullOrWhiteSpace(lastModel.SuggestionItem?.InsertText))
+ {
+ // When suggestion mode is selected, don't commit empty suggestion
+ return CommitBehavior.None;
+ }
+ else if (lastModel.SelectSuggestionItem)
+ {
+ // Commit the suggestion mode item
+ return CommitItem(typedChar, lastModel.SuggestionItem, ApplicableToSpan, token);
+ }
+ else if (lastModel.PresentedItems.IsDefaultOrEmpty)
+ {
+ // There is nothing to commit
+ Dismiss();
+ return CommitBehavior.None;
+ }
+ else
+ {
+ // Regular commit
+ return CommitItem(typedChar, lastModel.PresentedItems[lastModel.SelectedIndex].CompletionItem, ApplicableToSpan, token);
+ }
+ }
+
+ private CommitBehavior CommitItem(char typedChar, CompletionItem itemToCommit, ITrackingSpan applicableToSpan, CancellationToken token)
+ {
+ CommitBehavior behavior = CommitBehavior.None;
+ if (IsDismissed)
+ return behavior;
+
+ _telemetry.UiStopwatch.Restart();
+ IAsyncCompletionCommitManager managerWhoCommitted = null;
+
+ bool commitHandled = false;
+ foreach (var commitManager in _commitManagers)
+ {
+ var commitResult = _guardedOperations.CallExtensionPoint(
+ errorSource: commitManager,
+ call: () => commitManager.Item1.TryCommit(_textView, commitManager.Item2 /* buffer */, itemToCommit, applicableToSpan, typedChar, token),
+ valueOnThrow: CommitResult.Unhandled);
+
+ if (commitResult.Behavior == CommitBehavior.CancelCommit)
+ {
+ // Return quickly without dismissing.
+ // Return this behavior so that CommitIfUnique displays the UI
+ _telemetry.UiStopwatch.Stop();
+ return commitResult.Behavior;
+ }
+
+ if (behavior == CommitBehavior.None) // Don't override behavior returned by higher priority commit manager
+ behavior = commitResult.Behavior;
+
+ commitHandled |= commitResult.IsHandled;
+ if (commitResult.IsHandled)
+ {
+ managerWhoCommitted = commitManager.Item1;
+ break;
+ }
+ }
+ if (!commitHandled)
+ {
+ // Fallback if item is still not committed.
+ InsertIntoBuffer(_textView, applicableToSpan, itemToCommit.InsertText);
+ }
+
+ _telemetry.UiStopwatch.Stop();
+ _guardedOperations.RaiseEvent(this, ItemCommitted, new CompletionItemEventArgs(itemToCommit));
+ _telemetry.RecordCommitted(_telemetry.UiStopwatch.ElapsedMilliseconds, managerWhoCommitted);
+
+ Dismiss();
+
+ return behavior;
+ }
+
+ private static void InsertIntoBuffer(ITextView view, ITrackingSpan applicableToSpan, string insertText)
+ {
+ var buffer = view.TextBuffer;
+ var bufferEdit = buffer.CreateEdit();
+
+ // ApplicableToSpan already contains the typedChar and brace completion. Replacing this span will cause us to lose this data.
+ // The command handler who invoked this code needs to re-play the type char command, such that we get these changes back.
+ bufferEdit.Replace(applicableToSpan.GetSpan(buffer.CurrentSnapshot), insertText);
+ bufferEdit.Apply();
+ }
+
+ public void Dismiss()
+ {
+ if (IsDismissed)
+ return;
+
+ IsDismissed = true;
+ _broker.ForgetSession(this);
+ _guardedOperations.RaiseEvent(this, Dismissed);
+ _textView.Caret.PositionChanged -= OnCaretPositionChanged;
+ _computationCancellation.Cancel();
+
+ if (_gui != null)
+ {
+ var copyOfGui = _gui;
+ _guardedOperations.CallExtensionPointAsync(
+ errorSource: _gui,
+ asyncAction: async () =>
+ {
+ await JoinableTaskContext.Factory.SwitchToMainThreadAsync();
+ _telemetry.UiStopwatch.Restart();
+ copyOfGui.FiltersChanged -= OnFiltersChanged;
+ copyOfGui.CommitRequested -= OnCommitRequested;
+ copyOfGui.CompletionItemSelected -= OnItemSelected;
+ copyOfGui.CompletionClosed -= OnGuiClosed;
+ copyOfGui.Close();
+ _telemetry.UiStopwatch.Stop();
+ _telemetry.RecordClosing(_telemetry.UiStopwatch.ElapsedMilliseconds);
+ await Task.Yield();
+ _telemetry.Save(_completionItemManager, _presenterProvider);
+ });
+ _gui = null;
+ }
+ }
+
+ void IAsyncCompletionSession.OpenOrUpdate(InitialTrigger trigger, SnapshotPoint triggerLocation, CancellationToken commandToken)
+ {
+ if (IsDismissed)
+ return;
+
+ if (!JoinableTaskContext.IsOnMainThread)
+ throw new InvalidOperationException($"This method must be callled on the UI thread.");
+
+ commandToken.Register(_computationCancellation.Cancel);
+
+ if (_computation == null)
+ {
+ _computation = new ModelComputation<CompletionModel>(
+ PrioritizedTaskScheduler.AboveNormalInstance,
+ JoinableTaskContext,
+ (model, token) => GetInitialModel(trigger, triggerLocation, token),
+ _computationCancellation.Token,
+ _guardedOperations,
+ this
+ );
+ }
+
+ var taskId = Interlocked.Increment(ref _lastFilteringTaskId);
+ _computation.Enqueue((model, token) => UpdateSnapshot(model, trigger, new UpdateTrigger(FromCompletionTriggerReason(trigger.Reason), trigger.Character), triggerLocation, taskId, token), updateUi: true);
+ }
+
+ ComputedCompletionItems IAsyncCompletionSession.GetComputedItems(CancellationToken token)
+ {
+ if (_computation == null)
+ return ComputedCompletionItems.Empty; // Call OpenOrUpdate first to kick off computation
+
+ var model = _computation.WaitAndGetResult(cancelUi: true, token); // We don't want user initiated action to hide UI
+ return ComputeCompletionItems(model);
+ }
+
+ private static UpdateTriggerReason FromCompletionTriggerReason(InitialTriggerReason reason)
+ {
+ switch (reason)
+ {
+ case InitialTriggerReason.Invoke:
+ case InitialTriggerReason.InvokeAndCommitIfUnique:
+ return UpdateTriggerReason.Initial;
+ case InitialTriggerReason.Insertion:
+ return UpdateTriggerReason.Insertion;
+ case InitialTriggerReason.Deletion:
+ return UpdateTriggerReason.Deletion;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(reason));
+ }
+ }
+
+ #region IAsyncCompletionSessionOperations implementation
+
+ public void InvokeAndCommitIfUnique(InitialTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token)
+ {
+ if (IsDismissed)
+ return;
+
+ if (_computation == null)
+ {
+ // Compute the unique item.
+ // Don't recompute If we already have a model, so that we don't change user's selection.
+ ((IAsyncCompletionSession)this).OpenOrUpdate(trigger, triggerLocation, token);
+ }
+
+ if (((IAsyncCompletionSession)this).CommitIfUnique(token))
+ {
+ ((IAsyncCompletionSession)this).Dismiss();
+ }
+ }
+
+ public void SetSuggestionMode(bool useSuggestionMode)
+ {
+ _computation.Enqueue((model, token) => ToggleCompletionModeInner(model, useSuggestionMode, token), updateUi: true);
+ }
+
+ public void SelectDown()
+ {
+ _computation.Enqueue((model, token) => UpdateSelectedItem(model, +1, token), updateUi: true);
+ }
+
+ public void SelectPageDown()
+ {
+ _computation.Enqueue((model, token) => UpdateSelectedItem(model, +PageStepSize, token), updateUi: true);
+ }
+
+ public void SelectUp()
+ {
+ _computation.Enqueue((model, token) => UpdateSelectedItem(model, -1, token), updateUi: true);
+ }
+
+ public void SelectPageUp()
+ {
+ _computation.Enqueue((model, token) => UpdateSelectedItem(model, -PageStepSize, token), updateUi: true);
+ }
+
+ public void SelectCompletionItem(CompletionItem item)
+ {
+ // To prevent inifinite loops, UI interacts with computation using the OnItemSelected event handler
+ _computation.Enqueue((model, token) => UpdateSelectedItem(model, item, false, token), updateUi: true);
+ }
+
+ #endregion
+
+ #region Internal methods that are implementation specific
+
+ internal void IgnoreCaretMovement(bool ignore)
+ {
+ if (IsDismissed)
+ return; // This method will be called after committing. Don't act on it.
+
+ _ignoreCaretMovement = ignore;
+ if (!ignore)
+ {
+ // Don't let the session exist in invalid state: ensure that the location of the session is still valid
+ HandleCaretPositionChanged(_textView.Caret.Position);
+ }
+ }
+
+ #endregion
+
+ private void OnFiltersChanged(object sender, CompletionFilterChangedEventArgs args)
+ {
+ var taskId = Interlocked.Increment(ref _lastFilteringTaskId);
+ _computation.Enqueue((model, token) => UpdateFilters(model, args.Filters, taskId, token), updateUi: true);
+ }
+
+ /// <summary>
+ /// Handler for GUI requesting commit, usually through double-clicking.
+ /// There is no UI for cancellation, so use self-imposed expiration.
+ /// </summary>
+ private void OnCommitRequested(object sender, CompletionItemEventArgs args)
+ {
+ try
+ {
+ if (_computation == null)
+ return;
+ var expiringTokenSource = new CancellationTokenSource(MaxCommitDelayWhenClicked);
+ CommitItem(default, args.Item, ApplicableToSpan, expiringTokenSource.Token);
+ }
+ catch (Exception ex)
+ {
+ _guardedOperations.HandleException(this, ex);
+ }
+ }
+
+ private void OnItemSelected(object sender, CompletionItemSelectedEventArgs args)
+ {
+ // Note 1: Use this only to react to selection changes initiated by user's mouse\touch operation in the UI, since they cancel the soft selection
+ // Note 2: we are not enqueuing a call to update the UI, since this would put us in infinite loop, and the UI is already updated
+ _computation.Enqueue((model, token) => UpdateSelectedItem(model, args.SelectedItem, args.SuggestionItemSelected, token), updateUi: false);
+ }
+
+ private void OnGuiClosed(object sender, CompletionClosedEventArgs args)
+ {
+ Dismiss();
+ }
+
+ /// <summary>
+ /// Monitors when user scrolled outside of the applicable span. Note that:
+ /// * This event is not raised during regular typing.
+ /// * This event is raised by brace completion.
+ /// * Typing stretches the applicable span
+ /// </summary>
+ private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
+ {
+ // http://source.roslyn.io/#Microsoft.CodeAnalysis.EditorFeatures/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs,40
+ if (_ignoreCaretMovement)
+ return;
+
+ HandleCaretPositionChanged(e.NewPosition);
+ }
+
+ async Task IModelComputationCallbackHandler<CompletionModel>.UpdateUI(CompletionModel model, CancellationToken token)
+ {
+ if (_presenterProvider == null) return;
+ await JoinableTaskContext.Factory.SwitchToMainThreadAsync(token);
+ if (token.IsCancellationRequested) return;
+ UpdateUiInner(model);
+ }
+
+ /// <summary>
+ /// Opens or updates the UI. Must be called on UI thread.
+ /// </summary>
+ /// <param name="model"></param>
+ private void UpdateUiInner(CompletionModel model)
+ {
+ if (IsDismissed)
+ return;
+ if (model == null)
+ throw new ArgumentNullException(nameof(model));
+ if (model.Uninitialized)
+ return; // Language service wishes to not show completion yet.
+ if (!JoinableTaskContext.IsOnMainThread)
+ throw new InvalidOperationException($"This method must be callled on the UI thread.");
+
+ // TODO: Consider building CompletionPresentationViewModel in BG and passing it here
+ _telemetry.UiStopwatch.Restart();
+ if (_gui == null)
+ {
+ _gui = _guardedOperations.CallExtensionPoint(errorSource: _presenterProvider, call: () => _presenterProvider.GetOrCreate(_textView), valueOnThrow: null);
+ if (_gui != null)
+ {
+ _guardedOperations.CallExtensionPoint(
+ errorSource: _gui,
+ call: () =>
+ {
+ _gui = _presenterProvider.GetOrCreate(_textView);
+ _gui.Open(new CompletionPresentationViewModel(model.PresentedItems, model.Filters,
+ model.SelectedIndex, ApplicableToSpan, model.UseSoftSelection, model.DisplaySuggestionItem,
+ model.SelectSuggestionItem, model.SuggestionItem, SuggestionItemOptions));
+ _gui.FiltersChanged += OnFiltersChanged;
+ _gui.CommitRequested += OnCommitRequested;
+ _gui.CompletionItemSelected += OnItemSelected;
+ _gui.CompletionClosed += OnGuiClosed;
+ });
+ }
+ }
+ else
+ {
+ _guardedOperations.CallExtensionPoint(
+ errorSource: _gui,
+ call: () => _gui.Update(new CompletionPresentationViewModel(model.PresentedItems, model.Filters,
+ model.SelectedIndex, ApplicableToSpan, model.UseSoftSelection, model.DisplaySuggestionItem,
+ model.SelectSuggestionItem, model.SuggestionItem, SuggestionItemOptions)));
+ }
+ _telemetry.UiStopwatch.Stop();
+ _telemetry.RecordRendering(_telemetry.UiStopwatch.ElapsedMilliseconds);
+ }
+
+ /// <summary>
+ /// Creates a new model and populates it with initial data
+ /// </summary>
+ private async Task<CompletionModel> GetInitialModel(InitialTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token)
+ {
+ bool sourceUsesSuggestionMode = false;
+ SuggestionItemOptions requestedSuggestionItemOptions = null;
+ InitialSelectionHint initialSelectionHint = InitialSelectionHint.RegularSelection;
+ var initialItemsBuilder = ImmutableArray.CreateBuilder<CompletionItem>();
+
+ for (int i = 0; i < _completionSources.Count; i++)
+ {
+ var index = i; // Capture i, since it will change during the async call
+
+ _telemetry.ComputationStopwatch.Restart();
+ var context = await _guardedOperations.CallExtensionPointAsync(
+ errorSource: _completionSources[index].Source,
+ asyncCall: () => _completionSources[index].Source.GetCompletionContextAsync(trigger, _completionSources[index].Point, ApplicableToSpan.GetSpan(ApplicableToSpan.TextBuffer.CurrentSnapshot), token),
+ valueOnThrow: null
+ ).ConfigureAwait(true);
+ _telemetry.ComputationStopwatch.Stop();
+ _telemetry.RecordObtainingSourceContext(_completionSources[index].Source, _telemetry.ComputationStopwatch.ElapsedMilliseconds);
+
+ if (context == null)
+ continue;
+
+ sourceUsesSuggestionMode |= context.SuggestionItemOptions != null;
+
+ // Set initial selection option, in order of precedence: soft selection, regular selection
+ if (context.SelectionHint == InitialSelectionHint.SoftSelection)
+ initialSelectionHint = InitialSelectionHint.SoftSelection;
+
+ if (!context.Items.IsDefaultOrEmpty)
+ initialItemsBuilder.AddRange(context.Items);
+ // We use SuggestionModeOptions of the first source that provides it
+ if (requestedSuggestionItemOptions == null && context.SuggestionItemOptions != null)
+ requestedSuggestionItemOptions = context.SuggestionItemOptions;
+ }
+
+ // Do not continue without items
+ if (initialItemsBuilder.Count == 0)
+ {
+ return CompletionModel.GetUninitializedModel(triggerLocation.Snapshot);
+ }
+
+ // If no source provided suggestion item options, provide default options for suggestion mode
+ SuggestionItemOptions = requestedSuggestionItemOptions ?? DefaultSuggestionModeOptions;
+
+ // Store the data that won't change throughout the session
+ InitialTrigger = trigger;
+ SuggestionModeCompletionItemSource = new SuggestionModeCompletionItemSource(SuggestionItemOptions);
+
+ var initialCompletionItems = initialItemsBuilder.ToImmutable();
+
+ var availableFilters = initialCompletionItems
+ .SelectMany(n => n.Filters)
+ .Distinct()
+ .Select(n => new CompletionFilterWithState(n, true))
+ .ToImmutableArray();
+
+ var customerUsesSuggestionMode = CompletionUtilities.GetSuggestionModeOption(_textView);
+ var viewUsesSuggestionMode = CompletionUtilities.IsDebuggerTextView(_textView);
+
+ var useSuggestionMode = customerUsesSuggestionMode || sourceUsesSuggestionMode || viewUsesSuggestionMode;
+ // Select suggestion item only if source explicity provided it. This means that debugger view or ctrl+alt+space won't select the suggestion item.
+ var selectSuggestionItem = sourceUsesSuggestionMode;
+ // Use soft selection if suggestion item is present, unless source selects that item. Also, use soft selection if source wants to.
+ var useSoftSelection = useSuggestionMode && !selectSuggestionItem || initialSelectionHint == InitialSelectionHint.SoftSelection;
+
+ _telemetry.ComputationStopwatch.Restart();
+ var sortedList = await _guardedOperations.CallExtensionPointAsync(
+ errorSource: _completionItemManager,
+ asyncCall: () => _completionItemManager.SortCompletionListAsync(
+ session: this,
+ data: new AsyncCompletionSessionInitialDataSnapshot(initialCompletionItems, triggerLocation.Snapshot, InitialTrigger),
+ token: token),
+ valueOnThrow: initialCompletionItems).ConfigureAwait(true);
+ _telemetry.ComputationStopwatch.Stop();
+ _telemetry.RecordProcessing(_telemetry.ComputationStopwatch.ElapsedMilliseconds, initialCompletionItems.Length);
+ _telemetry.RecordKeystroke();
+
+ return new CompletionModel(initialCompletionItems, sortedList, triggerLocation.Snapshot,
+ availableFilters, useSoftSelection, useSuggestionMode, selectSuggestionItem, suggestionItem: null);
+ }
+
+ /// <summary>
+ /// User has moved the caret. Ensure that the caret is still within the applicable span. If not, dismiss the session.
+ /// </summary>
+ private void HandleCaretPositionChanged(CaretPosition caretPosition)
+ {
+ // TODO: when caret goes to the beginning of the span, we should enter soft selection
+ // when caret moves back into another location in the span, we should resume previous selection mode.
+ if (!ApplicableToSpan.GetSpan(caretPosition.VirtualBufferPosition.Position.Snapshot).IntersectsWith(new SnapshotSpan(caretPosition.VirtualBufferPosition.Position, 0)))
+ {
+ ((IAsyncCompletionSession)this).Dismiss();
+ }
+ }
+
+ /// <summary>
+ /// Sets or unsets suggestion mode.
+ /// </summary>
+#pragma warning disable CA1822 // Member does not access instance data and can be marked as static
+#pragma warning disable CA1801 // Parameter token is never used
+ private Task<CompletionModel> ToggleCompletionModeInner(CompletionModel model, bool useSuggestionMode, CancellationToken token)
+ {
+ return Task.FromResult(model.WithSuggestionItemVisibility(useSuggestionMode));
+ }
+#pragma warning restore CA1822
+#pragma warning restore CA1801
+
+ /// <summary>
+ /// User has typed. Update the known snapshot, filter the items and update the model.
+ /// </summary>
+ private async Task<CompletionModel> UpdateSnapshot(CompletionModel model, InitialTrigger initialTrigger, UpdateTrigger updateTrigger, SnapshotPoint updateLocation, int thisId, CancellationToken token)
+ {
+ // Always record keystrokes, even if filtering is preempted
+ _telemetry.RecordKeystroke();
+
+ // Completion got cancelled
+ if (token.IsCancellationRequested || model == null)
+ return default;
+
+ var instantenousSnapshot = updateLocation.Snapshot;
+
+ // Dismiss if we are outside of the applicable span
+ var currentlyApplicableToSpan = ApplicableToSpan.GetSpan(instantenousSnapshot);
+ if (updateLocation < currentlyApplicableToSpan.Start
+ || updateLocation > currentlyApplicableToSpan.End)
+ {
+ ((IAsyncCompletionSession)this).Dismiss();
+ return model;
+ }
+ // Record the first time the span is empty. If it is empty the second time we're here, and user is deleting, then dismiss
+ if (currentlyApplicableToSpan.IsEmpty && model.ApplicableToSpanWasEmpty && initialTrigger.Reason == InitialTriggerReason.Deletion)
+ {
+ ((IAsyncCompletionSession)this).Dismiss();
+ return model;
+ }
+ // If we were soft selected at the beginning of the span
+ model = model.WithApplicableToSpanStatus(currentlyApplicableToSpan.IsEmpty);
+
+ // The model has no items. There is a chance that there will be items available
+ // after user types something. Due to timing issues, we can't just dismiss and start another session,
+ // so we need to attempt to get items again within this session.
+ if (model.Uninitialized && thisId > 1) // Don't attempt to get items on the very first UpdateSnapshot
+ {
+ // previous ApplicableToSpan returned no items.
+ // When we try getting items again, use a span that doesn't have characters present in the previous span
+ // Update the applicable span to the new snapshot, without the span that previously did not return any items
+ var previousSpan = ApplicableToSpan.GetSpan(model.Snapshot);
+ var pointThatDoesntTrackAdditions = model.Snapshot.CreateTrackingPoint(previousSpan.End, PointTrackingMode.Negative);
+ var newSpan = ApplicableToSpan.GetSpan(updateLocation.Snapshot);
+
+ var newApplicableToSpanStart = pointThatDoesntTrackAdditions.GetPosition(updateLocation.Snapshot);
+ var newApplicableToSpanEnd = newSpan.End;
+
+ var newApplicableToSpan = updateLocation.Snapshot.CreateTrackingSpan(newApplicableToSpanStart, newApplicableToSpanEnd - newApplicableToSpanStart, SpanTrackingMode.EdgeInclusive);
+
+ this.ApplicableToSpan = newApplicableToSpan; // Everyone expects this to not change, but we are confident that the UI thread is waiting for this method to complete.
+ // Attempt to get new completion items
+ model = await GetInitialModel(initialTrigger, updateLocation, token).ConfigureAwait(true);
+ }
+
+ if (model.Uninitialized) // Check if we just received some items
+ {
+ // If not, dismiss, unless there is another task queued.
+ var dismissed = await TryDismissSafely(thisId).ConfigureAwait(true);
+ return model;
+ }
+
+ // Filtering got preempted, so store the most recent snapshot for the next time we filter. UpdateSnapshot will be called again.
+ if (thisId != _lastFilteringTaskId)
+ return model.WithSnapshot(instantenousSnapshot);
+
+ _telemetry.ComputationStopwatch.Restart();
+
+ var filteredCompletion = await _guardedOperations.CallExtensionPointAsync(
+ errorSource: _completionItemManager,
+ asyncCall: () => _completionItemManager.UpdateCompletionListAsync(
+ session: this,
+ data: new AsyncCompletionSessionDataSnapshot(
+ model.InitialItems,
+ instantenousSnapshot,
+ initialTrigger,
+ updateTrigger,
+ model.Filters,
+ model.UseSoftSelection,
+ model.DisplaySuggestionItem),
+ token: token),
+ valueOnThrow: null).ConfigureAwait(true);
+
+ // Error cases are handled by logging them above and dismissing the session.
+ if (filteredCompletion == null)
+ {
+ ((IAsyncCompletionSession)this).Dismiss();
+ return model;
+ }
+
+ // Special experience when there are no more selected items:
+ ImmutableArray<CompletionItemWithHighlight> returnedItems;
+ int selectedIndex = filteredCompletion.SelectedItemIndex;
+ if (filteredCompletion.Items.IsDefault)
+ {
+ // Prevent null references when service returns default(ImmutableArray)
+ returnedItems = ImmutableArray<CompletionItemWithHighlight>.Empty;
+ }
+ else if (filteredCompletion.Items.IsEmpty)
+ {
+ if (model.PresentedItems.IsDefaultOrEmpty)
+ {
+ // There were no previously visible results. Return a valid empty array
+ returnedItems = ImmutableArray<CompletionItemWithHighlight>.Empty;
+ }
+ else
+ {
+ // Show previously visible results, without highlighting
+ returnedItems = model.PresentedItems.Select(n => new CompletionItemWithHighlight(n.CompletionItem)).ToImmutableArray();
+ selectedIndex = model.SelectedIndex;
+ if (!_inNoResultFallback)
+ {
+ // Enter the no results mode to preserve the selection state
+ _selectionModeBeforeNoResultFallback = model.UseSoftSelection;
+ _inNoResultFallback = true;
+ model = model.WithSoftSelection(true);
+ }
+ }
+ }
+ else
+ {
+ if (_inNoResultFallback)
+ {
+ // we were in the no result mode and just received no items. Restore the selection mode.
+ model = model.WithSoftSelection(_selectionModeBeforeNoResultFallback);
+ _inNoResultFallback = false;
+ }
+ returnedItems = filteredCompletion.Items;
+ }
+
+ _telemetry.ComputationStopwatch.Stop();
+ _telemetry.RecordProcessing(_telemetry.ComputationStopwatch.ElapsedMilliseconds, returnedItems.Length);
+
+ // Allow the item manager to control the selection of the suggestion item
+ if (model.DisplaySuggestionItem)
+ {
+ if (filteredCompletion.SelectedItemIndex == -1)
+ model = model.WithSuggestionItemSelected();
+ else
+ model = model.WithSelectedIndex(selectedIndex, preserveSoftSelection: true);
+ // If suggestion item is present, we default to soft selection.
+ model = model.WithSoftSelection(true);
+ }
+ else
+ {
+ model = model.WithSelectedIndex(selectedIndex, preserveSoftSelection: true);
+ }
+
+ // Allow the item manager to override the selection style.
+ // Our recommendation for extenders is to use UpdateSelectionHint.NoChange whenever possible
+ if (filteredCompletion.SelectionHint == UpdateSelectionHint.SoftSelected)
+ model = model.WithSoftSelection(true);
+ else if (filteredCompletion.SelectionHint == UpdateSelectionHint.Selected
+ && (!model.DisplaySuggestionItem || model.SelectSuggestionItem))
+ // Allow the language service wishes to fully select the item if we are not in suggestion mode,
+ // or if the item to select is the suggestion item.
+ model = model.WithSoftSelection(false);
+
+ // Prepare the suggestionItem if user ever activates suggestion mode
+ var enteredText = currentlyApplicableToSpan.GetText();
+ var suggestionItem = new CompletionItem(enteredText, SuggestionModeCompletionItemSource);
+
+ var updatedModel = model.WithSnapshotItemsAndFilters(updateLocation.Snapshot, returnedItems, filteredCompletion.UniqueItem, suggestionItem, filteredCompletion.Filters);
+ RaiseCompletionItemsComputedEvent(updatedModel);
+ return updatedModel;
+ }
+
+ /// <summary>
+ /// Dismisses this <see cref="AsyncCompletionSession"/> only if called from the last task.
+ /// If there are any extra tasks, this method will return <code>false</code>
+ /// </summary>
+ /// <param name="currentTaskId"></param>
+ /// <returns></returns>
+ private async Task<bool> TryDismissSafely(int currentTaskId)
+ {
+ await JoinableTaskContext.Factory.SwitchToMainThreadAsync();
+
+ // Tasks are enqueued on the UI thread, so we know that _lastFilteringTaskId won't change
+ if (currentTaskId < _lastFilteringTaskId)
+ {
+ // This is not the last task, so we should not dismiss.
+ return false;
+ }
+ else
+ {
+ Dismiss();
+ return true;
+ }
+ }
+
+ /// <summary>
+ /// Reacts to user toggling a filter
+ /// </summary>
+ /// <param name="newFilters">Filters with updated Selected state, as indicated by the user.</param>
+ private async Task<CompletionModel> UpdateFilters(CompletionModel model, ImmutableArray<CompletionFilterWithState> newFilters, int thisId, CancellationToken token)
+ {
+ _telemetry.RecordChangingFilters();
+ _telemetry.RecordKeystroke();
+
+ // Filtering got preempted, so store the most updated filters for the next time we filter
+ if (token.IsCancellationRequested || thisId != _lastFilteringTaskId)
+ return model.WithFilters(newFilters);
+
+ var filteredCompletion = await _guardedOperations.CallExtensionPointAsync(
+ errorSource: _completionItemManager,
+ asyncCall: () => _completionItemManager.UpdateCompletionListAsync(
+ session: this,
+ data: new AsyncCompletionSessionDataSnapshot(
+ model.InitialItems,
+ model.Snapshot,
+ InitialTrigger,
+ new UpdateTrigger(UpdateTriggerReason.FilterChange),
+ newFilters,
+ model.UseSoftSelection,
+ model.DisplaySuggestionItem),
+ token: token),
+ valueOnThrow: null).ConfigureAwait(true);
+
+ // Handle error cases by logging the issue and discarding the request to filter
+ if (filteredCompletion == null)
+ return model;
+ if (filteredCompletion.Filters.Length != newFilters.Length)
+ {
+ _guardedOperations.HandleException(
+ errorSource: _completionItemManager,
+ e: new InvalidOperationException("Completion service returned incorrect set of filters."));
+ return model;
+ }
+
+ var updatedModel = model.WithFilters(filteredCompletion.Filters).WithPresentedItems(filteredCompletion.Items, filteredCompletion.SelectedItemIndex);
+ RaiseCompletionItemsComputedEvent(updatedModel);
+ return updatedModel;
+ }
+
+#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
+#pragma warning disable CA1801 // Parameter token is never used
+ /// <summary>
+ /// Reacts to user scrolling the list using keyboard
+ /// </summary>
+ private async Task<CompletionModel> UpdateSelectedItem(CompletionModel model, int offset, CancellationToken token)
+#pragma warning restore CS1998
+#pragma warning restore CA1801
+ {
+ _telemetry.RecordScrolling();
+ _telemetry.RecordKeystroke();
+
+ if (!model.PresentedItems.Any())
+ {
+ // No-op if there are no items, unless there is a suggestion item.
+ if (model.DisplaySuggestionItem)
+ {
+ return model.WithSuggestionItemSelected(); // Select the sole item which is a suggestion item.
+ }
+ return model;
+ }
+
+ var lastIndex = model.PresentedItems.Count() - 1;
+ var currentIndex = model.SelectSuggestionItem ? -1 : model.SelectedIndex;
+
+ if (offset > 0) // Scrolling down. Stop at last index and don't wrap around.
+ {
+ if (currentIndex == lastIndex)
+ return model;
+
+ var newIndex = currentIndex + offset;
+ return model.WithSelectedIndex(Math.Min(newIndex, lastIndex));
+ }
+ else // Scrolling up. Stop at first index and don't wrap around.
+ {
+ if (currentIndex < FirstIndex) // Suggestion mode item is selected.
+ {
+ return model; // Don't wrap around.
+ }
+ else if (currentIndex == FirstIndex) // The first item is selected.
+ {
+ if (model.DisplaySuggestionItem) // If there is a suggestion, select it.
+ return model.WithSuggestionItemSelected();
+ else
+ return model; // Don't wrap around.
+ }
+ var newIndex = currentIndex + offset;
+ return model.WithSelectedIndex(Math.Max(newIndex, FirstIndex));
+ }
+ }
+
+#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
+#pragma warning disable CA1801 // Parameter token is never used
+ /// <summary>
+ /// Reacts to user selecting a specific item in the list
+ /// </summary>
+ private async Task<CompletionModel> UpdateSelectedItem(CompletionModel model, CompletionItem selectedItem, bool suggestionItemSelected, CancellationToken token)
+#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
+#pragma warning restore CA1801
+ {
+ _telemetry.RecordScrolling();
+ if (suggestionItemSelected)
+ {
+ var updatedModel = model.WithSuggestionItemSelected();
+ RaiseCompletionItemsComputedEvent(updatedModel);
+ return updatedModel;
+ }
+ else
+ {
+ for (int i = 0; i < model.PresentedItems.Length; i++)
+ {
+ if (model.PresentedItems[i].CompletionItem == selectedItem)
+ {
+ var updatedModel = model.WithSelectedIndex(i);
+ RaiseCompletionItemsComputedEvent(updatedModel);
+ return updatedModel;
+ }
+ }
+ // This item is not in the model
+ return model;
+ }
+ }
+
+ private void RaiseCompletionItemsComputedEvent(CompletionModel model)
+ {
+ if (ItemsUpdated == null)
+ return;
+
+ var computedItems = ComputeCompletionItems(model);
+
+ // Warning: if the event handler throws and anyone blocks UI thread now, there will be a deadlock.
+ // This won't happen for now, because all callers of this method are private and nobody waits on them.
+ _guardedOperations.RaiseEvent(this, ItemsUpdated, new ComputedCompletionItemsEventArgs(computedItems));
+ }
+
+ private static ComputedCompletionItems ComputeCompletionItems(CompletionModel model)
+ {
+ if (model == null || model.Uninitialized)
+ return ComputedCompletionItems.Empty;
+
+ return new ComputedCompletionItems(
+ itemsWithHighlight: model.PresentedItems,
+ suggestionItem: model.DisplaySuggestionItem ? model.SuggestionItem : null,
+ selectedItem: model.SelectSuggestionItem
+ ? model.SuggestionItem
+ : model.PresentedItems.IsDefaultOrEmpty || model.SelectedIndex < 0
+ ? null
+ : model.PresentedItems[model.SelectedIndex].CompletionItem,
+ suggestionItemSelected: model.SelectSuggestionItem,
+ usesSoftSelection: model.UseSoftSelection);
+ }
+ }
+}
diff --git a/src/Language/Impl/Language/Completion/CaretPreservingEditTransaction.cs b/src/Language/Impl/Language/AsyncCompletion/CaretPreservingEditTransaction.cs
index 45c0aed..9c2337b 100644
--- a/src/Language/Impl/Language/Completion/CaretPreservingEditTransaction.cs
+++ b/src/Language/Impl/Language/AsyncCompletion/CaretPreservingEditTransaction.cs
@@ -4,7 +4,7 @@ using System;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Operations;
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
{
internal class CaretPreservingEditTransaction : IDisposable
{
@@ -74,6 +74,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
EndTransaction();
}
+#pragma warning disable CA1063 // Dispose pattern
public void Dispose()
{
if (_transaction != null)
@@ -82,6 +83,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
Cancel();
}
}
+#pragma warning restore CA1063
public IMergeTextUndoTransactionPolicy MergePolicy
{
diff --git a/src/Language/Impl/Language/AsyncCompletion/CompletionAvailabilityUtility.cs b/src/Language/Impl/Language/AsyncCompletion/CompletionAvailabilityUtility.cs
new file mode 100644
index 0000000..a46c0b6
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/CompletionAvailabilityUtility.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Utilities;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ /// <summary>
+ /// Provides information whether modern completion should be enabled,
+ /// based on the state of <see cref="IExperimentationServiceInternal"/> and <see cref="IFeatureServiceFactory" />
+ /// for the given <see cref="IContentType"/> and <see cref="ITextView"/>.
+ /// </summary>
+ [Export]
+ internal class CompletionAvailabilityUtility
+ {
+ [Import]
+ private IExperimentationServiceInternal ExperimentationService;
+
+ [Import]
+ private IFeatureServiceFactory FeatureServiceFactory;
+
+ [Import]
+ private AsyncCompletionBroker Broker; // We're using internal method to check if relevant MEF parts exist.
+
+ // Black list by content type
+ private const string CompletionFlightName = "CompletionAPI";
+ private const string RoslynLanguagesContentType = "Roslyn Languages";
+ private const string RazorContentType = "Razor";
+ private bool _treatmentFlightDataInitialized;
+
+ // Quick access data:
+ private bool _treatmentFlightEnabled;
+ private IFeatureCookie _globalCompletionCookie;
+ private IFeatureCookie GlobalCompletionCookie =>
+ _globalCompletionCookie
+ ?? (_globalCompletionCookie = FeatureServiceFactory.GlobalFeatureService.GetCookie(PredefinedEditorFeatureNames.Completion));
+
+ /// <summary>
+ /// Returns whether completion is available for the given <see cref="IContentType" />.
+ /// </summary>
+ /// <returns>true if experiment is enabled, feature is enabled in the global scope, and broker has providers that match the supplied <see cref="IContentType" /></returns>
+ internal bool IsAvailable(IContentType contentType)
+ {
+ if (!GlobalCompletionCookie.IsEnabled)
+ return false;
+
+ if (!Broker.HasCompletionProviders(contentType))
+ return false;
+
+ // Roslyn and Razor providers exist in the MEF cache, but Roslyn is not ready for public rollout yet.
+ // However, We do want other languages (e.g. AXML, EditorConfig) to work with Async Completion API
+ // We will remove this check once Roslyn fully embraces Async Completion API.
+ if (!IsExperimentEnabled() && (contentType.IsOfType(RoslynLanguagesContentType) || contentType.IsOfType(RazorContentType)))
+ return false;
+
+ return true;
+ }
+
+ /// <summary>
+ /// Returns whether completion is available for the given <see cref="IContentType"/> in the given <see cref="ITextView" />.
+ /// </summary>
+ /// <returns>true if experiment is enabled, feature is enabled in the <see cref="ITextView" />'s scope, and broker has providers that match the supplied <see cref="IContentType" /></returns>
+ internal bool IsAvailable(IContentType contentType, ITextView textView)
+ {
+ if (!Broker.HasCompletionProviders(contentType))
+ return false;
+
+ var featureService = FeatureServiceFactory.GetOrCreate(textView);
+ if (!featureService.IsEnabled(PredefinedEditorFeatureNames.Completion))
+ return false;
+
+ // Roslyn and Razor providers exist in the MEF cache, but Roslyn is not ready for public rollout yet.
+ // However, We do want other languages (e.g. AXML, EditorConfig) to work with Async Completion API
+ // We will remove this check once Roslyn fully embraces Async Completion API.
+ if (!IsExperimentEnabled() && (contentType.IsOfType(RoslynLanguagesContentType) || contentType.IsOfType(RazorContentType)))
+ return false;
+
+ return true;
+ }
+
+ /// <summary>
+ /// Returns whether completion is available in the given <see cref="ITextView" />.
+ /// Note: the second parameter <see cref="IContentType"/> is to be removed in dev16 when the experiment ends.
+ /// </summary>
+ /// <returns>true if experiment is enabled and feature is enabled in <see cref="ITextView"/>'s scope</returns>
+ internal bool IsAvailable(ITextView textView, IContentType contentTypeToCheckBlacklist)
+ {
+ var featureService = FeatureServiceFactory.GetOrCreate(textView);
+ if (!featureService.IsEnabled(PredefinedEditorFeatureNames.Completion))
+ return false;
+
+ // Roslyn and Razor providers exist in the MEF cache, but Roslyn is not ready for public rollout yet.
+ // However, We do want other languages (e.g. AXML, EditorConfig) to work with Async Completion API
+ // We will remove this check once Roslyn fully embraces Async Completion API.
+ if (!IsExperimentEnabled() && (contentTypeToCheckBlacklist.IsOfType(RoslynLanguagesContentType) || contentTypeToCheckBlacklist.IsOfType(RazorContentType)))
+ return false;
+
+ return true;
+ }
+
+ private bool IsExperimentEnabled()
+ {
+ if (_treatmentFlightDataInitialized)
+ return _treatmentFlightEnabled;
+
+ _treatmentFlightEnabled = ExperimentationService.IsCachedFlightEnabled(CompletionFlightName);
+ _treatmentFlightDataInitialized = true;
+ return _treatmentFlightEnabled;
+ }
+ }
+}
diff --git a/src/Language/Impl/Language/AsyncCompletion/CompletionCommandHandlers.cs b/src/Language/Impl/Language/AsyncCompletion/CompletionCommandHandlers.cs
new file mode 100644
index 0000000..e0599f1
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/CompletionCommandHandlers.cs
@@ -0,0 +1,564 @@
+using System;
+using System.ComponentModel.Composition;
+using Microsoft.VisualStudio.Commanding;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
+using Microsoft.VisualStudio.Text.Operations;
+using Microsoft.VisualStudio.Text.Utilities;
+using Microsoft.VisualStudio.Utilities;
+using CommonImplementation = Microsoft.VisualStudio.Language.Intellisense.Implementation;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ /// <summary>
+ /// Reacts to the down arrow command and attempts to scroll the completion list.
+ /// </summary>
+ [Name(PredefinedCompletionNames.CompletionCommandHandler)]
+ [ContentType("text")]
+ [TextViewRole(PredefinedTextViewRoles.Interactive)]
+ [Export(typeof(ICommandHandler))]
+ internal sealed class CompletionCommandHandler :
+ ICommandHandler<DownKeyCommandArgs>,
+ ICommandHandler<PageDownKeyCommandArgs>,
+ ICommandHandler<PageUpKeyCommandArgs>,
+ ICommandHandler<UpKeyCommandArgs>,
+ IChainedCommandHandler<BackspaceKeyCommandArgs>,
+ IDynamicCommandHandler<BackspaceKeyCommandArgs>,
+ ICommandHandler<EscapeKeyCommandArgs>,
+ IDynamicCommandHandler<EscapeKeyCommandArgs>,
+ ICommandHandler<InvokeCompletionListCommandArgs>,
+ ICommandHandler<CommitUniqueCompletionListItemCommandArgs>,
+ ICommandHandler<InsertSnippetCommandArgs>,
+ ICommandHandler<SurroundWithCommandArgs>,
+ ICommandHandler<ToggleCompletionModeCommandArgs>,
+ IChainedCommandHandler<DeleteKeyCommandArgs>,
+ IDynamicCommandHandler<DeleteKeyCommandArgs>,
+ ICommandHandler<WordDeleteToEndCommandArgs>,
+ ICommandHandler<WordDeleteToStartCommandArgs>,
+ ICommandHandler<SaveCommandArgs>,
+ ICommandHandler<SelectAllCommandArgs>,
+ ICommandHandler<RenameCommandArgs>,
+ ICommandHandler<UndoCommandArgs>,
+ ICommandHandler<RedoCommandArgs>,
+ IChainedCommandHandler<ReturnKeyCommandArgs>,
+ IDynamicCommandHandler<ReturnKeyCommandArgs>,
+ IChainedCommandHandler<TabKeyCommandArgs>,
+ IDynamicCommandHandler<TabKeyCommandArgs>,
+ IChainedCommandHandler<TypeCharCommandArgs>,
+ IDynamicCommandHandler<TypeCharCommandArgs>
+ {
+ [Import]
+ private IAsyncCompletionBroker Broker;
+
+ [Import]
+ private ITextUndoHistoryRegistry UndoHistoryRegistry;
+
+ [Import]
+ private IEditorOperationsFactoryService EditorOperationsFactoryService;
+
+ [Import]
+ private CompletionAvailabilityUtility CompletionAvailability;
+
+ string INamed.DisplayName => CommonImplementation.Strings.CompletionCommandHandlerName;
+
+ /// <summary>
+ /// Helper method that returns command state for commands
+ /// that are always available - unless the completion feature is available.
+ /// </summary>
+ private CommandState GetCommandStateIfCompletionIsAvailable(IContentType contentType, ITextView textView)
+ {
+ return CompletionAvailability.IsAvailable(contentType, textView)
+ ? CommandState.Available
+ : CommandState.Unspecified;
+ }
+
+ /// <summary>
+ /// Helper method that returns command state
+ /// for commands that are available IF AND ONLY IF completion is active,
+ /// even if the commands would be otherwise unavailable.
+ /// </summary>
+ /// <remarks>
+ /// For commands whose availability is not influenced by completion, use <see cref="CommandState.Unspecified"/>
+ /// </remarks>
+ private CommandState GetCommandStateIfCompletionIsActive(ITextView textView)
+ {
+ return Broker.IsCompletionActive(textView)
+ ? CommandState.Available
+ : CommandState.Unspecified;
+ }
+
+ /// <summary>
+ /// Helper method that returns command state for commands that are available when completion is
+ /// either currently active, or available.
+ /// This is used by commands that may trigger completion session on a specified buffer, or interact with an active completion session on another buffer
+ /// </summary>
+ private CommandState GetCommandStateIfCompletionIsActiveOrAvailable(IContentType contentType, ITextView textView)
+ {
+ return Broker.IsCompletionActive(textView) || CompletionAvailability.IsAvailable(contentType, textView)
+ ? CommandState.Available
+ : CommandState.Unspecified;
+ }
+
+ /// <summary>
+ /// Helper method that returns command state for the suggestion mode toggle button.
+ /// This command state controls not only whether the toggle button is enabled, but also if it's toggled.
+ /// </summary>
+ private CommandState GetCommandStateForSuggestionModeToggle(IContentType contentType, ITextView textView)
+ {
+ var isAvailable = CompletionAvailability.IsAvailable(contentType, textView);
+ var isChecked = CompletionUtilities.IsDebuggerTextView(textView)
+ ? CompletionUtilities.GetSuggestionModeOption(textView)
+ : CompletionUtilities.GetSuggestionModeInDebuggerCompletionOption(textView);
+ return new CommandState(isAvailable, isChecked);
+ }
+
+ /// <summary>
+ /// Realizes the virtual space and updates session's applicable to span
+ /// </summary>
+ private void RealizeVirtualSpaceUpdateApplicableToSpan(IAsyncCompletionSessionOperations session, ITextView textView)
+ {
+ if (session == null // We may only act if we have internal reference to the session
+ || !textView.Caret.InVirtualSpace // We only act if caret is in virtual space
+ || !session.ApplicableToSpan.GetSpan(textView.TextSnapshot).IsEmpty) // We only act if the applicable to span is of zero length (at the beginning of the line)
+ {
+ return;
+ }
+
+ // Realize the virtual space before triggering the session by inserting nothing through the editor opertaions.
+ IEditorOperations editorOperations = EditorOperationsFactoryService.GetEditorOperations(textView);
+ editorOperations?.InsertText("");
+
+ // ApplicableToSpan just grew to include the realized white space.
+ // We know that ApplicableToSpan was zero length, so let's recreate a zero length span at the caret location
+ session.ApplicableToSpan = textView.TextSnapshot.CreateTrackingSpan(
+ start: textView.Caret.Position.BufferPosition.Position,
+ length: 0,
+ trackingMode: SpanTrackingMode.EdgePositive);
+ }
+
+ // ----- Command handlers:
+
+ CommandState IChainedCommandHandler<BackspaceKeyCommandArgs>.GetCommandState(BackspaceKeyCommandArgs args, Func<CommandState> nextCommandHandler)
+ => CommandState.Unspecified;
+
+ bool IDynamicCommandHandler<BackspaceKeyCommandArgs>.CanExecuteCommand(BackspaceKeyCommandArgs args)
+ => Broker.IsCompletionActive(args.TextView);
+
+ void IChainedCommandHandler<BackspaceKeyCommandArgs>.ExecuteCommand(BackspaceKeyCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
+ {
+ // Execute other commands in the chain to see the change in the buffer.
+ nextCommandHandler();
+
+ var session = Broker.GetSession(args.TextView);
+ if (session != null)
+ {
+ var trigger = new InitialTrigger(InitialTriggerReason.Deletion);
+ var location = args.TextView.Caret.Position.BufferPosition;
+ session.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
+ }
+ }
+
+ CommandState ICommandHandler<EscapeKeyCommandArgs>.GetCommandState(EscapeKeyCommandArgs args)
+ => GetCommandStateIfCompletionIsActive(args.TextView);
+
+ bool IDynamicCommandHandler<EscapeKeyCommandArgs>.CanExecuteCommand(EscapeKeyCommandArgs args)
+ => Broker.IsCompletionActive(args.TextView);
+
+ bool ICommandHandler<EscapeKeyCommandArgs>.ExecuteCommand(EscapeKeyCommandArgs args, CommandExecutionContext executionContext)
+ {
+ var session = Broker.GetSession(args.TextView);
+ if (session != null)
+ {
+ session.Dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ CommandState ICommandHandler<InvokeCompletionListCommandArgs>.GetCommandState(InvokeCompletionListCommandArgs args)
+ => GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView);
+
+ bool ICommandHandler<InvokeCompletionListCommandArgs>.ExecuteCommand(InvokeCompletionListCommandArgs args, CommandExecutionContext executionContext)
+ {
+ if (!GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView).IsAvailable)
+ return false;
+
+ var trigger = new InitialTrigger(InitialTriggerReason.Invoke);
+ var location = args.TextView.Caret.Position.BufferPosition;
+ var session = Broker.TriggerCompletion(args.TextView, location, default, executionContext.OperationContext.UserCancellationToken);
+ if (session is IAsyncCompletionSessionOperations sessionInternal)
+ {
+ RealizeVirtualSpaceUpdateApplicableToSpan(sessionInternal, args.TextView);
+ location = args.TextView.Caret.Position.BufferPosition; // Buffer may have changed. Update the location.
+ sessionInternal.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
+ return true;
+ }
+ return false;
+ }
+
+ CommandState ICommandHandler<CommitUniqueCompletionListItemCommandArgs>.GetCommandState(CommitUniqueCompletionListItemCommandArgs args)
+ => GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView);
+
+ bool ICommandHandler<CommitUniqueCompletionListItemCommandArgs>.ExecuteCommand(CommitUniqueCompletionListItemCommandArgs args, CommandExecutionContext executionContext)
+ {
+ if (!GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView).IsAvailable)
+ return false;
+
+ var trigger = new InitialTrigger(InitialTriggerReason.InvokeAndCommitIfUnique);
+ var location = args.TextView.Caret.Position.BufferPosition;
+ var session = Broker.TriggerCompletion(args.TextView, location, default, executionContext.OperationContext.UserCancellationToken);
+ if (session is IAsyncCompletionSessionOperations sessionInternal)
+ {
+ RealizeVirtualSpaceUpdateApplicableToSpan(sessionInternal, args.TextView);
+ location = args.TextView.Caret.Position.BufferPosition; // Buffer may have changed. Update the location.
+ sessionInternal.InvokeAndCommitIfUnique(trigger, location, executionContext.OperationContext.UserCancellationToken);
+ return true;
+ }
+ return false;
+ }
+
+ CommandState ICommandHandler<InsertSnippetCommandArgs>.GetCommandState(InsertSnippetCommandArgs args)
+ => CommandState.Unspecified;
+
+ bool ICommandHandler<InsertSnippetCommandArgs>.ExecuteCommand(InsertSnippetCommandArgs args, CommandExecutionContext executionContext)
+ {
+ Broker.GetSession(args.TextView)?.Dismiss();
+ return false;
+ }
+
+ CommandState ICommandHandler<SurroundWithCommandArgs>.GetCommandState(SurroundWithCommandArgs args)
+ => CommandState.Unspecified;
+
+ bool ICommandHandler<SurroundWithCommandArgs>.ExecuteCommand(SurroundWithCommandArgs args, CommandExecutionContext executionContext)
+ {
+ Broker.GetSession(args.TextView)?.Dismiss();
+ return false;
+ }
+
+ CommandState ICommandHandler<ToggleCompletionModeCommandArgs>.GetCommandState(ToggleCompletionModeCommandArgs args)
+ => GetCommandStateForSuggestionModeToggle(args.SubjectBuffer.ContentType, args.TextView);
+
+ bool ICommandHandler<ToggleCompletionModeCommandArgs>.ExecuteCommand(ToggleCompletionModeCommandArgs args, CommandExecutionContext executionContext)
+ {
+ var toggledValue = !CompletionUtilities.GetSuggestionModeOption(args.TextView);
+ CompletionUtilities.SetSuggestionModeOption(args.TextView, toggledValue);
+
+ if (Broker.GetSession(args.TextView) is IAsyncCompletionSessionOperations sessionInternal) // we are accessing an internal method
+ {
+ sessionInternal.SetSuggestionMode(toggledValue);
+ return true;
+ }
+ return false;
+ }
+
+ CommandState IChainedCommandHandler<DeleteKeyCommandArgs>.GetCommandState(DeleteKeyCommandArgs args, Func<CommandState> nextCommandHandler)
+ => CommandState.Unspecified;
+
+ bool IDynamicCommandHandler<DeleteKeyCommandArgs>.CanExecuteCommand(DeleteKeyCommandArgs args)
+ => Broker.IsCompletionActive(args.TextView);
+
+ void IChainedCommandHandler<DeleteKeyCommandArgs>.ExecuteCommand(DeleteKeyCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
+ {
+ // Execute other commands in the chain to see the change in the buffer.
+ nextCommandHandler();
+
+ var session = Broker.GetSession(args.TextView);
+ if (session != null)
+ {
+ var trigger = new InitialTrigger(InitialTriggerReason.Deletion);
+ var location = args.TextView.Caret.Position.BufferPosition;
+ session.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
+ }
+ }
+
+ CommandState ICommandHandler<WordDeleteToEndCommandArgs>.GetCommandState(WordDeleteToEndCommandArgs args)
+ => CommandState.Unspecified;
+
+ bool ICommandHandler<WordDeleteToEndCommandArgs>.ExecuteCommand(WordDeleteToEndCommandArgs args, CommandExecutionContext executionContext)
+ {
+ Broker.GetSession(args.TextView)?.Dismiss();
+ return false;
+ }
+
+ CommandState ICommandHandler<WordDeleteToStartCommandArgs>.GetCommandState(WordDeleteToStartCommandArgs args)
+ => CommandState.Unspecified;
+
+ bool ICommandHandler<WordDeleteToStartCommandArgs>.ExecuteCommand(WordDeleteToStartCommandArgs args, CommandExecutionContext executionContext)
+ {
+ Broker.GetSession(args.TextView)?.Dismiss();
+ return false;
+ }
+
+ CommandState ICommandHandler<SaveCommandArgs>.GetCommandState(SaveCommandArgs args)
+ => CommandState.Unspecified;
+
+ bool ICommandHandler<SaveCommandArgs>.ExecuteCommand(SaveCommandArgs args, CommandExecutionContext executionContext)
+ {
+ Broker.GetSession(args.TextView)?.Dismiss();
+ return false;
+ }
+
+ CommandState ICommandHandler<SelectAllCommandArgs>.GetCommandState(SelectAllCommandArgs args)
+ => CommandState.Unspecified;
+
+ bool ICommandHandler<SelectAllCommandArgs>.ExecuteCommand(SelectAllCommandArgs args, CommandExecutionContext executionContext)
+ {
+ Broker.GetSession(args.TextView)?.Dismiss();
+ return false;
+ }
+
+ CommandState ICommandHandler<RenameCommandArgs>.GetCommandState(RenameCommandArgs args)
+ => CommandState.Unspecified;
+
+ bool ICommandHandler<RenameCommandArgs>.ExecuteCommand(RenameCommandArgs args, CommandExecutionContext executionContext)
+ {
+ Broker.GetSession(args.TextView)?.Dismiss();
+ return false;
+ }
+
+ CommandState ICommandHandler<UndoCommandArgs>.GetCommandState(UndoCommandArgs args)
+ => CommandState.Unspecified;
+
+ bool ICommandHandler<UndoCommandArgs>.ExecuteCommand(UndoCommandArgs args, CommandExecutionContext executionContext)
+ {
+ Broker.GetSession(args.TextView)?.Dismiss();
+ return false;
+ }
+
+ CommandState ICommandHandler<RedoCommandArgs>.GetCommandState(RedoCommandArgs args)
+ => CommandState.Unspecified;
+
+ bool ICommandHandler<RedoCommandArgs>.ExecuteCommand(RedoCommandArgs args, CommandExecutionContext executionContext)
+ {
+ Broker.GetSession(args.TextView)?.Dismiss();
+ return false;
+ }
+
+ CommandState IChainedCommandHandler<ReturnKeyCommandArgs>.GetCommandState(ReturnKeyCommandArgs args, Func<CommandState> nextCommandHandler)
+ => GetCommandStateIfCompletionIsActiveOrAvailable(args.SubjectBuffer.ContentType, args.TextView);
+
+ bool IDynamicCommandHandler<ReturnKeyCommandArgs>.CanExecuteCommand(ReturnKeyCommandArgs args)
+ => Broker.IsCompletionActive(args.TextView) || Broker.IsCompletionSupported(args.SubjectBuffer.ContentType);
+
+ void IChainedCommandHandler<ReturnKeyCommandArgs>.ExecuteCommand(ReturnKeyCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
+ {
+ if (!GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView).IsAvailable)
+ {
+ // In IChainedCommandHandler, we have to explicitly call the next command handler
+ nextCommandHandler();
+ return;
+ }
+ char typedChar = '\n';
+
+ var session = Broker.GetSession(args.TextView);
+ if (session != null)
+ {
+ var commitBehavior = session.Commit(typedChar, executionContext.OperationContext.UserCancellationToken);
+ session.Dismiss();
+
+ // Mark this command as handled (return true),
+ // unless extender set the RaiseFurtherCommandHandlers flag - with exception of the debugger text view
+ if ((commitBehavior & CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers) == 0
+ || CompletionUtilities.IsDebuggerTextView(args.TextView))
+ return;
+ }
+
+ nextCommandHandler();
+
+ // Buffer has changed. Update it for when we try to trigger new session.
+ var location = args.TextView.Caret.Position.BufferPosition;
+
+ var trigger = new InitialTrigger(InitialTriggerReason.Insertion, typedChar);
+ var newSession = Broker.TriggerCompletion(args.TextView, location, typedChar, executionContext.OperationContext.UserCancellationToken);
+ if (newSession is IAsyncCompletionSessionOperations sessionInternal)
+ {
+ RealizeVirtualSpaceUpdateApplicableToSpan(sessionInternal, args.TextView);
+ location = args.TextView.Caret.Position.BufferPosition; // Buffer may have changed. Update the location.
+ sessionInternal.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
+ }
+ }
+
+ CommandState IChainedCommandHandler<TabKeyCommandArgs>.GetCommandState(TabKeyCommandArgs args, Func<CommandState> nextCommandHandler)
+ => GetCommandStateIfCompletionIsActiveOrAvailable(args.SubjectBuffer.ContentType, args.TextView);
+
+ bool IDynamicCommandHandler<TabKeyCommandArgs>.CanExecuteCommand(TabKeyCommandArgs args)
+ => Broker.IsCompletionActive(args.TextView) || Broker.IsCompletionSupported(args.SubjectBuffer.ContentType);
+
+ void IChainedCommandHandler<TabKeyCommandArgs>.ExecuteCommand(TabKeyCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
+ {
+ if (!GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView).IsAvailable)
+ {
+ // In IChainedCommandHandler, we have to explicitly call the next command handler
+ nextCommandHandler();
+ return;
+ }
+ char typedChar = '\t';
+
+ var session = Broker.GetSession(args.TextView);
+ if (session != null)
+ {
+ var commitBehavior = session.Commit(typedChar, executionContext.OperationContext.UserCancellationToken);
+ session.Dismiss();
+
+ // Mark this command as handled (return true),
+ // unless extender set the RaiseFurtherCommandHandlers flag - with exception of the debugger text view
+ if ((commitBehavior & CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers) == 0
+ || CompletionUtilities.IsDebuggerTextView(args.TextView))
+ return;
+ }
+
+ nextCommandHandler();
+
+ // Buffer has changed. Update it for when we try to trigger new session.
+ var location = args.TextView.Caret.Position.BufferPosition;
+
+ var trigger = new InitialTrigger(InitialTriggerReason.Insertion, typedChar);
+ var newSession = Broker.TriggerCompletion(args.TextView, location, typedChar, executionContext.OperationContext.UserCancellationToken);
+ newSession?.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
+ }
+
+ CommandState IChainedCommandHandler<TypeCharCommandArgs>.GetCommandState(TypeCharCommandArgs args, Func<CommandState> nextCommandHandler)
+ => GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView);
+
+ bool IDynamicCommandHandler<TypeCharCommandArgs>.CanExecuteCommand(TypeCharCommandArgs args)
+ => CompletionAvailability.IsAvailable(args.SubjectBuffer.ContentType, args.TextView);
+
+ void IChainedCommandHandler<TypeCharCommandArgs>.ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
+ {
+ if (!GetCommandStateIfCompletionIsAvailable(args.SubjectBuffer.ContentType, args.TextView).IsAvailable)
+ {
+ // In IChainedCommandHandler, we have to explicitly call the next command handler
+ nextCommandHandler();
+ return;
+ }
+
+ var view = args.TextView;
+ var location = view.Caret.Position.BufferPosition;
+ var initialTextSnapshot = args.SubjectBuffer.CurrentSnapshot;
+
+ // Note regarding undo: When completion and brace completion happen together, completion should be first on the undo stack.
+ // Effectively, we want to first undo the completion, leaving brace completion intact. Second undo should undo brace completion.
+ // To achieve this, we create a transaction in which we commit and reapply brace completion (via nextCommandHandler).
+ // Please read "Note regarding undo" comments in this method that explain the implementation choices.
+ // Hopefully an upcoming upgrade of the undo mechanism will allow us to undo out of order and vastly simplify this method.
+
+ // Note regarding undo: In a corner case of typing closing brace over existing closing brace,
+ // Roslyn brace completion does not perform an edit. It moves the caret outside of session's applicable span,
+ // which dismisses the session. Put the session in a state where it will not dismiss when caret leaves the applicable span.
+ var sessionToCommit = Broker.GetSession(args.TextView);
+ if (sessionToCommit != null)
+ {
+ ((AsyncCompletionSession)sessionToCommit).IgnoreCaretMovement(ignore: true);
+ }
+
+ // Execute other commands in the chain to see the change in the buffer. This includes brace completion.
+ // Note regarding undo: This will be 2nd in the undo stack
+ nextCommandHandler();
+
+ // if on different version than initialTextSnapshot, we will NOT rollback and we will NOT replay the nextCommandHandler
+ // DP to figure out why ShouldCommit returns false or Commit doesn't do anything
+ var braceCompletionSpecialHandling = args.SubjectBuffer.CurrentSnapshot.Version == initialTextSnapshot.Version;
+
+ // Pass location from before calling nextCommandHandler
+ // so that extenders get the same view of the buffer in both ShouldCommit and Commit
+ if (sessionToCommit?.ShouldCommit(args.TypedChar, location, executionContext.OperationContext.UserCancellationToken) == true)
+ {
+ // Buffer has changed, update the snapshot
+ location = view.Caret.Position.BufferPosition;
+
+ // Note regarding undo: this transaction will be 1st in the undo stack
+ using (var undoTransaction = new CaretPreservingEditTransaction("Completion", view, UndoHistoryRegistry, EditorOperationsFactoryService))
+ {
+ if (!braceCompletionSpecialHandling)
+ UndoUtilities.RollbackToBeforeTypeChar(initialTextSnapshot, args.SubjectBuffer);
+ // Now the buffer doesn't have the commit character nor the matching brace, if any
+
+ var commitBehavior = sessionToCommit.Commit(args.TypedChar, executionContext.OperationContext.UserCancellationToken);
+
+ if (!braceCompletionSpecialHandling && (commitBehavior & CommitBehavior.SuppressFurtherTypeCharCommandHandlers) == 0)
+ nextCommandHandler(); // Replay the key, so that we get brace completion.
+
+ // Complete the transaction before stopping it.
+ undoTransaction.Complete();
+ }
+ }
+
+ // Restore the default state where session dismisses when caret is outside of the applicable span.
+ if (sessionToCommit != null)
+ {
+ ((AsyncCompletionSession)sessionToCommit).IgnoreCaretMovement(ignore: false);
+ }
+
+ // Buffer might have changed. Update it for when we try to trigger new session.
+ location = view.Caret.Position.BufferPosition;
+
+ var trigger = new InitialTrigger(InitialTriggerReason.Insertion, args.TypedChar);
+ var session = Broker.GetSession(args.TextView);
+ if (session != null)
+ {
+ session.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
+ }
+ else
+ {
+ var newSession = Broker.TriggerCompletion(args.TextView, location, args.TypedChar, executionContext.OperationContext.UserCancellationToken);
+ newSession?.OpenOrUpdate(trigger, location, executionContext.OperationContext.UserCancellationToken);
+ }
+ }
+
+ CommandState ICommandHandler<DownKeyCommandArgs>.GetCommandState(DownKeyCommandArgs args)
+ => GetCommandStateIfCompletionIsActive(args.TextView);
+
+ bool ICommandHandler<DownKeyCommandArgs>.ExecuteCommand(DownKeyCommandArgs args, CommandExecutionContext executionContext)
+ {
+ if (Broker.GetSession(args.TextView) is AsyncCompletionSession session) // we are accessing an internal method
+ {
+ session.SelectDown();
+ return true;
+ }
+ return false;
+ }
+
+ CommandState ICommandHandler<PageDownKeyCommandArgs>.GetCommandState(PageDownKeyCommandArgs args)
+ => GetCommandStateIfCompletionIsActive(args.TextView);
+
+ bool ICommandHandler<PageDownKeyCommandArgs>.ExecuteCommand(PageDownKeyCommandArgs args, CommandExecutionContext executionContext)
+ {
+ if (Broker.GetSession(args.TextView) is AsyncCompletionSession session) // we are accessing an internal method
+ {
+ session.SelectPageDown();
+ return true;
+ }
+ return false;
+ }
+
+ CommandState ICommandHandler<PageUpKeyCommandArgs>.GetCommandState(PageUpKeyCommandArgs args)
+ => GetCommandStateIfCompletionIsActive(args.TextView);
+
+ bool ICommandHandler<PageUpKeyCommandArgs>.ExecuteCommand(PageUpKeyCommandArgs args, CommandExecutionContext executionContext)
+ {
+ if (Broker.GetSession(args.TextView) is AsyncCompletionSession session) // we are accessing an internal method
+ {
+ session.SelectPageUp();
+ return true;
+ }
+ return false;
+ }
+
+ CommandState ICommandHandler<UpKeyCommandArgs>.GetCommandState(UpKeyCommandArgs args)
+ => GetCommandStateIfCompletionIsActive(args.TextView);
+
+ bool ICommandHandler<UpKeyCommandArgs>.ExecuteCommand(UpKeyCommandArgs args, CommandExecutionContext executionContext)
+ {
+ if (Broker.GetSession(args.TextView) is AsyncCompletionSession session) // we are accessing an internal method
+ {
+ session.SelectUp();
+ System.Diagnostics.Debug.WriteLine("Completions's UpKey command handler returns true (handled)");
+ return true;
+ }
+ System.Diagnostics.Debug.WriteLine("Completions's UpKey command handler returns false (unhandled)");
+ return false;
+ }
+ }
+}
diff --git a/src/Language/Impl/Language/AsyncCompletion/CompletionModel.cs b/src/Language/Impl/Language/AsyncCompletion/CompletionModel.cs
new file mode 100644
index 0000000..2d75b0e
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/CompletionModel.cs
@@ -0,0 +1,330 @@
+using System;
+using System.Collections.Immutable;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ /// <summary>
+ /// Represents an immutable snapshot of state of the async completion feature.
+ /// </summary>
+ internal sealed class CompletionModel
+ {
+ /// <summary>
+ /// All items, as provided by completion item sources.
+ /// </summary>
+ public readonly ImmutableArray<CompletionItem> InitialItems;
+
+ /// <summary>
+ /// Sorted array of all items, as provided by the completion service.
+ /// </summary>
+ public readonly ImmutableArray<CompletionItem> SortedItems;
+
+ /// <summary>
+ /// Snapshot pertinent to this completion model.
+ /// </summary>
+ public readonly ITextSnapshot Snapshot;
+
+ /// <summary>
+ /// Filters involved in this completion model, including their availability and selection state.
+ /// </summary>
+ public readonly ImmutableArray<CompletionFilterWithState> Filters;
+
+ /// <summary>
+ /// Items to be displayed in the UI.
+ /// </summary>
+ public readonly ImmutableArray<CompletionItemWithHighlight> PresentedItems;
+
+ /// <summary>
+ /// Index of item to select. Use -1 to select nothing, when suggestion mode item should be selected.
+ /// </summary>
+ public readonly int SelectedIndex;
+
+ /// <summary>
+ /// Whether selection should be displayed as soft selection.
+ /// </summary>
+ public readonly bool UseSoftSelection;
+
+ /// <summary>
+ /// Whether suggestion mode item should be visible.
+ /// </summary>
+ public readonly bool DisplaySuggestionItem;
+
+ /// <summary>
+ /// Whether suggestion mode item should be selected.
+ /// </summary>
+ public readonly bool SelectSuggestionItem;
+
+ /// <summary>
+ /// <see cref="CompletionItem"/> which contains user-entered text.
+ /// Used to display and commit the suggestion mode item
+ /// </summary>
+ public readonly CompletionItem SuggestionItem;
+
+ /// <summary>
+ /// <see cref="CompletionItem"/> which overrides regular unique item selection.
+ /// When this is null, the single item from <see cref="PresentedItems"/> is used as unique item.
+ /// </summary>
+ public readonly CompletionItem UniqueItem;
+
+ /// <summary>
+ /// This flags prevents <see cref="IAsyncCompletionSession"/> from dismissing when it initially becomes empty.
+ /// We dismiss when this flag is set (span is empty) and user attempts to remove characters.
+ /// </summary>
+ public readonly bool ApplicableToSpanWasEmpty;
+
+ /// <summary>
+ /// Indicates the state where the model received no items from the completion sources.
+ /// We keep the session (and its model) around to attempt getting items at the next keystroke, while preventing race conditions.
+ /// </summary>
+ public readonly bool Uninitialized;
+
+ /// <summary>
+ /// Creates an instance of <see cref="CompletionModel"/> for session
+ /// that did not receive any <see cref="CompletionItem"/>s yet, but may receive them soon thereafter.
+ /// </summary>
+ public static CompletionModel GetUninitializedModel(ITextSnapshot snapshot)
+ {
+ return new CompletionModel(default, default, snapshot, default, default, default, default, default, default, default, default, default, uninitialized: true);
+ }
+
+ /// <summary>
+ /// Constructor for the initial model
+ /// </summary>
+ public CompletionModel(ImmutableArray<CompletionItem> initialItems, ImmutableArray<CompletionItem> sortedItems,
+ ITextSnapshot snapshot, ImmutableArray<CompletionFilterWithState> filters, bool useSoftSelection,
+ bool displaySuggestionItem, bool selectSuggestionItem, CompletionItem suggestionItem)
+ {
+ InitialItems = initialItems;
+ SortedItems = sortedItems;
+ Snapshot = snapshot;
+ Filters = filters;
+ SelectedIndex = 0;
+ UseSoftSelection = useSoftSelection;
+ DisplaySuggestionItem = displaySuggestionItem;
+ SelectSuggestionItem = selectSuggestionItem;
+ SuggestionItem = suggestionItem;
+ UniqueItem = null;
+ ApplicableToSpanWasEmpty = false;
+ Uninitialized = false;
+ }
+
+ /// <summary>
+ /// Private constructor for the With* methods
+ /// </summary>
+ private CompletionModel(ImmutableArray<CompletionItem> initialItems, ImmutableArray<CompletionItem> sortedItems,
+ ITextSnapshot snapshot, ImmutableArray<CompletionFilterWithState> filters, ImmutableArray<CompletionItemWithHighlight> presentedItems,
+ bool useSoftSelection, bool displaySuggestionItem, int selectedIndex, bool selectSuggestionItem, CompletionItem suggestionItem,
+ CompletionItem uniqueItem, bool applicableToSpanWasEmpty, bool uninitialized)
+ {
+ InitialItems = initialItems;
+ SortedItems = sortedItems;
+ Snapshot = snapshot;
+ Filters = filters;
+ PresentedItems = presentedItems;
+ SelectedIndex = selectedIndex;
+ UseSoftSelection = useSoftSelection;
+ DisplaySuggestionItem = displaySuggestionItem;
+ SelectSuggestionItem = selectSuggestionItem;
+ SuggestionItem = suggestionItem;
+ UniqueItem = uniqueItem;
+ ApplicableToSpanWasEmpty = applicableToSpanWasEmpty;
+ Uninitialized = uninitialized;
+ }
+
+ public CompletionModel WithPresentedItems(ImmutableArray<CompletionItemWithHighlight> newPresentedItems, int newSelectedIndex)
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: Snapshot,
+ filters: Filters,
+ presentedItems: newPresentedItems, // Updated
+ useSoftSelection: UseSoftSelection,
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: newSelectedIndex, // Updated
+ selectSuggestionItem: SelectSuggestionItem,
+ suggestionItem: SuggestionItem,
+ uniqueItem: UniqueItem,
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
+
+ public CompletionModel WithSnapshot(ITextSnapshot newSnapshot)
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: newSnapshot, // Updated
+ filters: Filters,
+ presentedItems: PresentedItems,
+ useSoftSelection: UseSoftSelection,
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: SelectedIndex,
+ selectSuggestionItem: SelectSuggestionItem,
+ suggestionItem: SuggestionItem,
+ uniqueItem: UniqueItem,
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
+
+ public CompletionModel WithFilters(ImmutableArray<CompletionFilterWithState> newFilters)
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: Snapshot,
+ filters: newFilters, // Updated
+ presentedItems: PresentedItems,
+ useSoftSelection: UseSoftSelection,
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: SelectedIndex,
+ selectSuggestionItem: SelectSuggestionItem,
+ suggestionItem: SuggestionItem,
+ uniqueItem: UniqueItem,
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
+
+ public CompletionModel WithSelectedIndex(int newIndex, bool preserveSoftSelection = false)
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: Snapshot,
+ filters: Filters,
+ presentedItems: PresentedItems,
+ useSoftSelection: preserveSoftSelection ? UseSoftSelection : false, // Updated conditionally
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: newIndex, // Updated
+ selectSuggestionItem: false, // Explicit selection of regular item
+ suggestionItem: SuggestionItem,
+ uniqueItem: UniqueItem,
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
+
+ public CompletionModel WithSuggestionItemSelected()
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: Snapshot,
+ filters: Filters,
+ presentedItems: PresentedItems,
+ useSoftSelection: false, // Explicit selection and soft selection are mutually exclusive
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: -1, // Deselect regular item
+ selectSuggestionItem: true, // Explicit selection of suggestion item
+ suggestionItem: SuggestionItem,
+ uniqueItem: UniqueItem,
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
+
+ public CompletionModel WithSuggestionItemVisibility(bool newDisplaySuggestionItem)
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: Snapshot,
+ filters: Filters,
+ presentedItems: PresentedItems,
+ useSoftSelection: UseSoftSelection | newDisplaySuggestionItem, // Enabling suggestion mode also enables soft selection
+ displaySuggestionItem: newDisplaySuggestionItem, // Updated
+ selectedIndex: SelectedIndex,
+ selectSuggestionItem: SelectSuggestionItem,
+ suggestionItem: SuggestionItem,
+ uniqueItem: UniqueItem,
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
+
+ /// <summary>
+ /// </summary>
+ /// <param name="newUniqueItem">Overrides typical unique item selection.
+ /// Pass in null to use regular behavior: treating single <see cref="PresentedItems"/> item as the unique item.</param>
+ internal CompletionModel WithUniqueItem(CompletionItem newUniqueItem)
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: Snapshot,
+ filters: Filters,
+ presentedItems: PresentedItems,
+ useSoftSelection: UseSoftSelection,
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: SelectedIndex,
+ selectSuggestionItem: SelectSuggestionItem,
+ suggestionItem: SuggestionItem,
+ uniqueItem: newUniqueItem,
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
+
+ internal CompletionModel WithSoftSelection(bool newSoftSelection)
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: Snapshot,
+ filters: Filters,
+ presentedItems: PresentedItems,
+ useSoftSelection: newSoftSelection, // Updated
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: SelectedIndex,
+ selectSuggestionItem: SelectSuggestionItem,
+ suggestionItem: SuggestionItem,
+ uniqueItem: UniqueItem,
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
+
+ internal CompletionModel WithSnapshotItemsAndFilters(ITextSnapshot snapshot, ImmutableArray<CompletionItemWithHighlight> presentedItems,
+ CompletionItem uniqueItem, CompletionItem suggestionItem, ImmutableArray<CompletionFilterWithState> filters)
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: snapshot, // Updated
+ filters: filters, // Updated
+ presentedItems: presentedItems, // Updated
+ useSoftSelection: UseSoftSelection,
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: SelectedIndex,
+ selectSuggestionItem: SelectSuggestionItem,
+ suggestionItem: suggestionItem, // Updated
+ uniqueItem: uniqueItem, // Updated
+ applicableToSpanWasEmpty: ApplicableToSpanWasEmpty,
+ uninitialized: Uninitialized
+ );
+ }
+
+ internal CompletionModel WithApplicableToSpanStatus(bool applicableToSpanIsEmpty)
+ {
+ return new CompletionModel(
+ initialItems: InitialItems,
+ sortedItems: SortedItems,
+ snapshot: Snapshot,
+ filters: Filters,
+ presentedItems: PresentedItems,
+ useSoftSelection: UseSoftSelection,
+ displaySuggestionItem: DisplaySuggestionItem,
+ selectedIndex: SelectedIndex,
+ selectSuggestionItem: SelectSuggestionItem,
+ suggestionItem: SuggestionItem,
+ uniqueItem: UniqueItem,
+ applicableToSpanWasEmpty: applicableToSpanIsEmpty, // Updated
+ uninitialized: Uninitialized
+ );
+ }
+ }
+}
diff --git a/src/Language/Impl/Language/AsyncCompletion/CompletionTelemetry.cs b/src/Language/Impl/Language/AsyncCompletion/CompletionTelemetry.cs
new file mode 100644
index 0000000..7faaa25
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/CompletionTelemetry.cs
@@ -0,0 +1,478 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Text.Utilities;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ /// <summary>
+ /// Telemetry data pertinent to a single <see cref="AsyncCompletionSession"/>
+ /// </summary>
+ internal class CompletionSessionTelemetry
+ {
+ private readonly CompletionTelemetryHost _telemetryHost;
+
+ /// <summary>
+ /// Tracks time spent on the worker thread - getting data, filtering and sorting. Used for telemetry.
+ /// </summary>
+ internal Stopwatch ComputationStopwatch { get; } = new Stopwatch();
+
+ /// <summary>
+ /// Tracks time spent on the UI thread - either rendering or committing. Used for telemetry.
+ /// </summary>
+ internal Stopwatch UiStopwatch { get; } = new Stopwatch();
+
+ // Names of parts that participated in completion
+ internal string ItemManagerName { get; private set; }
+ internal string PresenterProviderName { get; private set; }
+ internal string CommitManagerName { get; private set; }
+
+ // "Setup" is work done on UI thread by IAsyncCompletionBroker
+ // since there are many participating MEF parts, we record their names together with the time
+ internal Dictionary<string, long> CommitManagerSetupDuration { get; } = new Dictionary<string, long>();
+ internal Dictionary<string, long> ItemSourceSetupDuration { get; } = new Dictionary<string, long>();
+
+ // "Get Context" is work done by IAsyncCompletionItemSource
+ // multiple sources may participate in a single completion session
+ internal Dictionary<string, long> ItemSourceGetContextDuration { get; } = new Dictionary<string, long>();
+
+ // "Processing" is work done by IAsyncCompletionItemManager
+ internal long InitialProcessingDuration { get; private set; }
+ internal long TotalProcessingDuration { get; private set; }
+ internal int TotalProcessingCount { get; private set; }
+
+ // "Rendering" is work done on UI thread by ICompletionPresenter
+ internal long InitialRenderingDuration { get; private set; }
+ internal long TotalRenderingDuration { get; private set; }
+ internal int TotalRenderingCount { get; private set; }
+
+ // "Closing" is also work done on UI thread by ICompletionPresenter
+ internal long ClosingDuration { get; private set; }
+
+ // "Commit" is work done on UI thread by IAsyncCompletionCommitManager
+ internal long CommitDuration { get; private set; }
+
+ // The following work is a mix of "Get Context" and "Processing" and blocks UI thread
+ internal long BlockingComputationDuration { get; private set; }
+
+ // Additional parameters related to work done by IAsyncCompletionItemManager
+ internal bool UserEverScrolled { get; private set; }
+ internal bool UserEverSetFilters { get; private set; }
+ internal int FinalItemCount { get; private set; }
+ internal int NumberOfKeystrokes { get; private set; }
+
+ public CompletionSessionTelemetry(CompletionTelemetryHost telemetryHost)
+ {
+ _telemetryHost = telemetryHost;
+ }
+
+ internal void RecordProcessing(long duration, int itemCount)
+ {
+ if (TotalProcessingCount == 0)
+ {
+ InitialProcessingDuration = duration;
+ }
+ else
+ {
+ TotalProcessingDuration += duration;
+ FinalItemCount = itemCount;
+ }
+ TotalProcessingCount++;
+ }
+
+ internal void RecordRendering(long duration)
+ {
+ if (TotalRenderingCount == 0)
+ InitialRenderingDuration = duration;
+ TotalRenderingCount++;
+ TotalRenderingDuration += duration;
+ }
+
+ internal void RecordScrolling()
+ {
+ UserEverScrolled = true;
+ }
+
+ internal void RecordChangingFilters()
+ {
+ UserEverSetFilters = true;
+ }
+
+ internal void RecordKeystroke()
+ {
+ NumberOfKeystrokes++;
+ }
+
+ internal void RecordCommitted(long duration,
+ IAsyncCompletionCommitManager manager)
+ {
+ CommitManagerName = CompletionTelemetryHost.GetCommitManagerName(manager);
+ CommitDuration = duration;
+ }
+
+ internal void RecordClosing(long duration)
+ {
+ ClosingDuration += duration;
+ }
+
+ internal void Save(
+ IAsyncCompletionItemManager itemManager,
+ ICompletionPresenterProvider presenterProvider)
+ {
+ ItemManagerName = CompletionTelemetryHost.GetItemManagerName(itemManager);
+ PresenterProviderName = CompletionTelemetryHost.GetPresenterProviderName(presenterProvider);
+ _telemetryHost.Add(this);
+ }
+
+ internal void RecordObtainingCommitManagerData(IAsyncCompletionCommitManager manager, long elapsedMilliseconds)
+ {
+ var name = CompletionTelemetryHost.GetCommitManagerName(manager);
+ CommitManagerSetupDuration[name] = elapsedMilliseconds;
+ }
+
+ internal void RecordObtainingSourceSpan(IAsyncCompletionSource source, long elapsedMilliseconds)
+ {
+ var name = CompletionTelemetryHost.GetSourceName(source);
+ ItemSourceSetupDuration[name] = elapsedMilliseconds;
+ }
+
+ internal void RecordObtainingSourceContext(IAsyncCompletionSource source, long elapsedMilliseconds)
+ {
+ var name = CompletionTelemetryHost.GetSourceName(source);
+ ItemSourceGetContextDuration[name] = elapsedMilliseconds;
+ }
+
+ internal void RecordBlockingWaitForComputation(long elapsedMilliseconds)
+ {
+ BlockingComputationDuration = elapsedMilliseconds;
+ }
+ }
+
+ /// <summary>
+ /// Aggregates <see cref="CompletionSessionTelemetry"/>.
+ /// </summary>
+ internal class CompletionTelemetryHost
+ {
+ private class AggregateCommitManagerData
+ {
+ internal long TotalCommitTime;
+ internal long TotalSetupTime;
+
+ // These values are used to calculate averages
+ internal long CommitCount;
+ internal long SetupCount;
+
+ // We persist the slowest duration for operations on the UI thread
+ internal long MaxCommitTime;
+ internal long MaxSetupTime;
+ }
+
+ private class AggregateSourceData
+ {
+ internal long TotalGetContextTime;
+ internal long TotalSetupTime;
+
+ // These values are used to calculate averages
+ internal long GetContextCount;
+ internal long SetupCount;
+
+ // We persist the slowest duration for operations on the UI thread
+ internal long MaxSetupTime;
+ }
+
+ private class AggregateItemManagerData
+ {
+ internal long InitialProcessTime;
+ internal long TotalProcessTime;
+ internal long TotalBlockingComputationTime;
+ internal long MaxBlockingComputationTime;
+
+ internal int TotalKeystrokes;
+ internal int UserEverScrolled;
+ internal int UserEverSetFilters;
+ internal int FinalItemCount;
+
+ // These values are used to calculate averages
+ internal int SessionCount;
+ // This value is used to calculate average processing time. One session may have multiple processing operations.
+ internal int ProcessCount;
+ }
+
+ private class AggregatePresenterData
+ {
+ internal long InitialRenderTime;
+ internal long TotalRenderTime;
+ internal long TotalClosingTime;
+
+ // These values are used to calculate averages
+ internal int RenderCount;
+ internal int ClosingCount;
+
+ // We persist the slowest duration for operations on the UI thread
+ internal long MaxRenderTime;
+ internal long MaxClosingTime;
+ }
+
+ Dictionary<string, AggregateCommitManagerData> CommitManagerData = new Dictionary<string, AggregateCommitManagerData>();
+ Dictionary<string, AggregateItemManagerData> ItemManagerData = new Dictionary<string, AggregateItemManagerData>();
+ Dictionary<string, AggregatePresenterData> PresenterData = new Dictionary<string, AggregatePresenterData>();
+ Dictionary<string, AggregateSourceData> SourceData = new Dictionary<string, AggregateSourceData>();
+
+ private readonly ILoggingServiceInternal _logger;
+ private readonly AsyncCompletionBroker _broker;
+
+ public CompletionTelemetryHost(ILoggingServiceInternal logger, AsyncCompletionBroker broker)
+ {
+ _logger = logger;
+ _broker = broker;
+ }
+
+ internal static string GetSourceName(IAsyncCompletionSource source) => source?.GetType().ToString() ?? string.Empty;
+ internal static string GetCommitManagerName(IAsyncCompletionCommitManager commitManager) => commitManager?.GetType().ToString() ?? string.Empty;
+ internal static string GetItemManagerName(IAsyncCompletionItemManager itemManager) => itemManager?.GetType().ToString() ?? string.Empty;
+ internal static string GetPresenterProviderName(ICompletionPresenterProvider provider) => provider?.GetType().ToString() ?? string.Empty;
+
+ /// <summary>
+ /// Adds data from <see cref="CompletionSessionTelemetry" /> to appropriate buckets.
+ /// </summary>
+ /// <param name=""></param>
+ internal void Add(CompletionSessionTelemetry telemetry)
+ {
+ if (_logger == null)
+ return;
+
+ AddSourceData(telemetry, SourceData);
+ AddItemManagerData(telemetry, ItemManagerData);
+ AddCommitManagerData(telemetry, CommitManagerData);
+ AddPresenterData(telemetry, PresenterData);
+ }
+
+ /// <summary>
+ /// Sends batch of collected data.
+ /// </summary>
+ internal void Send()
+ {
+ if (_logger == null)
+ return;
+
+ foreach (var data in ItemManagerData)
+ {
+ if (data.Value.SessionCount == 0)
+ continue;
+ if (data.Value.ProcessCount == 0)
+ continue;
+
+ _logger.PostEvent(TelemetryEventType.Operation,
+ ItemManagerEventName,
+ TelemetryResult.Success,
+ (ItemManagerName, data.Key),
+ (ItemManagerAverageFinalItemCount, data.Value.FinalItemCount / (double)data.Value.SessionCount),
+ (ItemManagerAverageInitialProcessDuration, data.Value.InitialProcessTime / (double)data.Value.SessionCount),
+ (ItemManagerAverageFilterDuration, data.Value.TotalProcessTime / (double)data.Value.ProcessCount),
+ (ItemManagerAverageKeystrokeCount, data.Value.TotalKeystrokes / (double)data.Value.SessionCount),
+ (ItemManagerAverageScrolled, data.Value.UserEverScrolled / (double)data.Value.SessionCount),
+ (ItemManagerAverageSetFilters, data.Value.UserEverSetFilters / (double)data.Value.SessionCount),
+ (ItemManagerAverageBlockingComputationDuration, data.Value.TotalBlockingComputationTime / (double)data.Value.SessionCount),
+ (ItemManagerMaxBlockingComputationDuration, data.Value.MaxBlockingComputationTime)
+ );
+ }
+
+ foreach (var data in SourceData)
+ {
+ if (data.Value.SetupCount == 0)
+ continue;
+ if (data.Value.GetContextCount == 0)
+ data.Value.GetContextCount = 1; // the result of division will remain 0 and the division won't throw
+
+ _logger.PostEvent(TelemetryEventType.Operation,
+ SourceEventName,
+ TelemetryResult.Success,
+ (SourceName, data.Key),
+ (SourceAverageGetContextDuration, data.Value.TotalGetContextTime / (double)data.Value.GetContextCount),
+ (SourceAverageSetupDuration, data.Value.TotalSetupTime / (double)data.Value.SetupCount),
+ (SourceMaxSetupDuration, data.Value.MaxSetupTime)
+ );
+ }
+
+ foreach (var data in CommitManagerData)
+ {
+ if (data.Value.CommitCount == 0)
+ continue;
+
+ _logger.PostEvent(TelemetryEventType.Operation,
+ CommitManagerEventName,
+ TelemetryResult.Success,
+ (CommitManagerName, data.Key),
+ (CommitManagerAverageCommitDuration, data.Value.TotalCommitTime / (double)data.Value.CommitCount),
+ (CommitManagerAverageSetupDuration, data.Value.TotalSetupTime / (double)data.Value.SetupCount),
+ (CommitManagerMaxCommitDuration, data.Value.MaxCommitTime),
+ (CommitManagerMaxSetupDuration, data.Value.MaxSetupTime)
+ );
+ }
+
+ foreach (var data in PresenterData)
+ {
+ if (data.Value.RenderCount == 0)
+ continue;
+
+ _logger.PostEvent(TelemetryEventType.Operation,
+ PresenterEventName,
+ TelemetryResult.Success,
+ (PresenterName, data.Key),
+ (PresenterAverageInitialRendering, data.Value.InitialRenderTime / (double)data.Value.ClosingCount),
+ (PresenterAverageRendering, data.Value.TotalRenderTime / (double)data.Value.RenderCount),
+ (PresenterAverageClosing, data.Value.TotalClosingTime / (double)data.Value.ClosingCount),
+ (PresenterMaxRendering, data.Value.MaxRenderTime),
+ (PresenterMaxClosing, data.Value.MaxClosingTime)
+ );
+ }
+ }
+
+ /// <summary>
+ /// Tracks obtaining applicable span and getting items
+ /// </summary>
+ /// <param name="telemetry">Telemetry from <see cref="IAsyncCompletionSession"/></param>
+ /// <param name="sourceData">Data aggregator</param>
+ private static void AddSourceData(CompletionSessionTelemetry telemetry, Dictionary<string, AggregateSourceData> sourceData)
+ {
+ foreach (var setupData in telemetry.ItemSourceSetupDuration)
+ {
+ if (!sourceData.ContainsKey(setupData.Key))
+ sourceData[setupData.Key] = new AggregateSourceData();
+ var aggregateSourceData = sourceData[setupData.Key];
+
+ aggregateSourceData.TotalSetupTime += setupData.Value;
+ aggregateSourceData.SetupCount++;
+
+ aggregateSourceData.MaxSetupTime = Math.Max(aggregateSourceData.MaxSetupTime, setupData.Value);
+ }
+
+ foreach (var getContextData in telemetry.ItemSourceGetContextDuration)
+ {
+ if (!sourceData.ContainsKey(getContextData.Key))
+ sourceData[getContextData.Key] = new AggregateSourceData();
+ var aggregateSourceData = sourceData[getContextData.Key];
+
+ aggregateSourceData.TotalGetContextTime += getContextData.Value;
+ aggregateSourceData.GetContextCount++;
+ }
+ }
+
+ /// <summary>
+ /// Tracks sorting and filtering items
+ /// </summary>
+ /// <param name="telemetry">Telemetry from <see cref="IAsyncCompletionSession"/></param>
+ /// <param name="sourceData">Data aggregator</param>
+ private static void AddItemManagerData(CompletionSessionTelemetry telemetry, Dictionary<string, AggregateItemManagerData> itemManagerData)
+ {
+ var itemManagerKey = telemetry.ItemManagerName;
+ if (!itemManagerData.ContainsKey(itemManagerKey))
+ itemManagerData[itemManagerKey] = new AggregateItemManagerData();
+ var aggregateItemManagerData = itemManagerData[itemManagerKey];
+
+ aggregateItemManagerData.InitialProcessTime += telemetry.InitialProcessingDuration;
+ aggregateItemManagerData.TotalProcessTime += telemetry.TotalProcessingDuration;
+ aggregateItemManagerData.TotalBlockingComputationTime += telemetry.BlockingComputationDuration;
+ aggregateItemManagerData.ProcessCount += telemetry.TotalProcessingCount;
+ aggregateItemManagerData.TotalKeystrokes += telemetry.NumberOfKeystrokes;
+ aggregateItemManagerData.UserEverScrolled += telemetry.UserEverScrolled ? 1 : 0;
+ aggregateItemManagerData.UserEverSetFilters += telemetry.UserEverSetFilters ? 1 : 0;
+ aggregateItemManagerData.FinalItemCount += telemetry.FinalItemCount;
+ aggregateItemManagerData.SessionCount++;
+
+ aggregateItemManagerData.MaxBlockingComputationTime = Math.Max(aggregateItemManagerData.MaxBlockingComputationTime, telemetry.BlockingComputationDuration);
+ }
+
+ /// <summary>
+ /// Tracks obtaining commit characters and committing
+ /// </summary>
+ /// <param name="telemetry">Telemetry from <see cref="IAsyncCompletionSession"/></param>
+ /// <param name="sourceData">Data aggregator</param>
+ private static void AddCommitManagerData(CompletionSessionTelemetry telemetry, Dictionary<string, AggregateCommitManagerData> commitManagerData)
+ {
+ var commitKey = telemetry.CommitManagerName;
+ if (!string.IsNullOrEmpty(commitKey))
+ {
+ // commitKey is empty when session is dismissed without committing.
+ if (!commitManagerData.ContainsKey(commitKey))
+ commitManagerData[commitKey] = new AggregateCommitManagerData();
+ var aggregateCommitManagerData = commitManagerData[commitKey];
+
+ aggregateCommitManagerData.TotalCommitTime += telemetry.CommitDuration;
+ aggregateCommitManagerData.CommitCount++;
+
+ aggregateCommitManagerData.MaxCommitTime = Math.Max(aggregateCommitManagerData.MaxCommitTime, telemetry.CommitDuration);
+ }
+
+ foreach (var commitManagerSetupData in telemetry.CommitManagerSetupDuration)
+ {
+ if (!commitManagerData.ContainsKey(commitManagerSetupData.Key))
+ commitManagerData[commitManagerSetupData.Key] = new AggregateCommitManagerData();
+ var aggregateCommitManagerData = commitManagerData[commitManagerSetupData.Key];
+
+ aggregateCommitManagerData.TotalSetupTime += commitManagerSetupData.Value;
+ aggregateCommitManagerData.SetupCount++;
+
+ aggregateCommitManagerData.MaxSetupTime = Math.Max(aggregateCommitManagerData.MaxSetupTime, commitManagerSetupData.Value);
+ }
+ }
+
+ /// <summary>
+ /// Tracks opening, updating and closing the GUI
+ /// </summary>
+ /// <param name="telemetry">Telemetry from <see cref="IAsyncCompletionSession"/></param>
+ /// <param name="sourceData">Data aggregator</param>
+ private static void AddPresenterData(CompletionSessionTelemetry telemetry, Dictionary<string, AggregatePresenterData> presenterData)
+ {
+ var presenterKey = telemetry.PresenterProviderName;
+ if (!presenterData.ContainsKey(presenterKey))
+ presenterData[presenterKey] = new AggregatePresenterData();
+ var aggregatePresenterData = presenterData[presenterKey];
+
+ aggregatePresenterData.InitialRenderTime += telemetry.InitialRenderingDuration;
+ aggregatePresenterData.TotalRenderTime += telemetry.TotalRenderingDuration;
+ aggregatePresenterData.RenderCount += telemetry.TotalRenderingCount;
+ aggregatePresenterData.TotalClosingTime += telemetry.ClosingDuration;
+ aggregatePresenterData.ClosingCount++;
+
+ aggregatePresenterData.MaxRenderTime = Math.Max(aggregatePresenterData.MaxRenderTime, telemetry.InitialRenderingDuration);
+ aggregatePresenterData.MaxClosingTime = Math.Max(aggregatePresenterData.MaxClosingTime, telemetry.ClosingDuration);
+ }
+
+ // Property and event names
+ internal const string PresenterEventName = "VS/Editor/Completion/PresenterData";
+ internal const string PresenterName = "Property.Presenter.Name";
+ internal const string PresenterAverageInitialRendering = "Property.Presenter.InitialRenderDuration";
+ internal const string PresenterAverageRendering = "Property.Presenter.AllRenderDuration";
+ internal const string PresenterAverageClosing = "Property.Presenter.AllClosingDuration";
+ internal const string PresenterMaxRendering = "Property.Presenter.MaxRenderDuration";
+ internal const string PresenterMaxClosing = "Property.Presenter.MaxClosingDuration";
+
+ internal const string ItemManagerEventName = "VS/Editor/Completion/ItemManagerData";
+ internal const string ItemManagerName = "Property.ItemManager.Name";
+ internal const string ItemManagerAverageFinalItemCount = "Property.ItemManager.FinalItemCount";
+ internal const string ItemManagerAverageInitialProcessDuration = "Property.ItemManager.InitialDuration";
+ internal const string ItemManagerAverageFilterDuration = "Property.ItemManager.AnyDuration";
+ internal const string ItemManagerAverageKeystrokeCount = "Property.ItemManager.KeystrokeCount";
+ internal const string ItemManagerAverageScrolled = "Property.ItemManager.Scrolled";
+ internal const string ItemManagerAverageSetFilters = "Property.ItemManager.SetFilters";
+ internal const string ItemManagerAverageBlockingComputationDuration = "Property.ItemManager.BlockingComputationDuration";
+ internal const string ItemManagerMaxBlockingComputationDuration = "Property.ItemManager.MaxBlockingComputationDuration";
+
+ internal const string CommitManagerEventName = "VS/Editor/Completion/CommitManagerData";
+ internal const string CommitManagerName = "Property.CommitManager.Name";
+ internal const string CommitManagerAverageCommitDuration = "Property.Commit.CommitDuration";
+ internal const string CommitManagerAverageSetupDuration = "Property.Commit.SetupDuration";
+ internal const string CommitManagerMaxCommitDuration = "Property.Commit.MaxCommitDuration";
+ internal const string CommitManagerMaxSetupDuration = "Property.Commit.MaxSetupDuration";
+
+ internal const string SourceEventName = "VS/Editor/Completion/SourceData";
+ internal const string SourceName = "Property.Source.Name";
+ internal const string SourceAverageGetContextDuration = "Property.Source.GetContextDuration";
+ internal const string SourceAverageSetupDuration = "Property.Source.SetupDuration";
+ internal const string SourceMaxSetupDuration = "Property.Source.MaxSetupDuration";
+ }
+}
diff --git a/src/Language/Impl/Language/AsyncCompletion/CompletionUtilities.cs b/src/Language/Impl/Language/AsyncCompletion/CompletionUtilities.cs
new file mode 100644
index 0000000..6392376
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/CompletionUtilities.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.Composition;
+using System.Linq;
+using Microsoft.VisualStudio.Language.Intellisense;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Utilities;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ internal static class CompletionUtilities
+ {
+ /// <summary>
+ /// Maps given point to buffers that contain this point. Requires UI thread.
+ /// </summary>
+ /// <param name="textView"></param>
+ /// <param name="point"></param>
+ /// <returns></returns>
+ internal static IEnumerable<ITextBuffer> GetBuffersForPoint(ITextView textView, SnapshotPoint point)
+ {
+ // We are looking at the buffer to the left of the caret.
+ return textView.BufferGraph.GetTextBuffers(n =>
+ textView.BufferGraph.MapDownToBuffer(point, PointTrackingMode.Negative, n, PositionAffinity.Predecessor) != null);
+ }
+
+ /// <summary>
+ /// Returns whether the <see cref="ITextView"/> is furnished by the debugger,
+ /// e.g. it is a view in the breakpoint settings window or watch window.
+ /// </summary>
+ /// <param name="textView">View to examine</param>
+ /// <returns>True if the view has "DEBUGVIEW" text view role.</returns>
+ internal static bool IsDebuggerTextView(ITextView textView) => textView.Roles.Contains("DEBUGVIEW");
+
+ static readonly EditorOptionKey<bool> SuggestionModeOptionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInCompletionOptionName);
+ static readonly EditorOptionKey<bool> SuggestionModeInDebuggerCompletionOptionKey = new EditorOptionKey<bool>(PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName);
+ private const bool UseSuggestionModeDefaultValue = false;
+ private const bool UseSuggestionModeInDebuggerCompletionDefaultValue = true;
+
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(PredefinedCompletionNames.SuggestionModeInCompletionOptionName)]
+ class SuggestionModeOptionDefinition : EditorOptionDefinition
+ {
+ public override object DefaultValue => UseSuggestionModeDefaultValue;
+
+ public override Type ValueType => typeof(bool);
+
+ public override string Name => PredefinedCompletionNames.SuggestionModeInCompletionOptionName;
+ }
+
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName)]
+ class SuggestionModeInDebuggerCompletionOptionDefinition : EditorOptionDefinition
+ {
+ public override object DefaultValue => UseSuggestionModeInDebuggerCompletionDefaultValue;
+
+ public override Type ValueType => typeof(bool);
+
+ public override string Name => PredefinedCompletionNames.SuggestionModeInDebuggerCompletionOptionName;
+ }
+
+ internal static bool GetSuggestionModeOption(ITextView textView)
+ {
+ var options = textView.Options.GlobalOptions;
+ if (!(options.IsOptionDefined(SuggestionModeOptionKey, localScopeOnly: false)))
+ options.SetOptionValue(SuggestionModeOptionKey, UseSuggestionModeDefaultValue);
+ return options.GetOptionValue(SuggestionModeOptionKey);
+ }
+
+ internal static void SetSuggestionModeOption(ITextView textView, bool value)
+ {
+ var options = textView.Options.GlobalOptions;
+ options.SetOptionValue(SuggestionModeOptionKey, value);
+ }
+
+ internal static bool GetSuggestionModeInDebuggerCompletionOption(ITextView textView)
+ {
+ var options = textView.Options.GlobalOptions;
+ if (!(options.IsOptionDefined(SuggestionModeInDebuggerCompletionOptionKey, localScopeOnly: false)))
+ options.SetOptionValue(SuggestionModeInDebuggerCompletionOptionKey, UseSuggestionModeInDebuggerCompletionDefaultValue);
+ return options.GetOptionValue(SuggestionModeInDebuggerCompletionOptionKey);
+ }
+
+ internal static void SetSuggestionModeDuringDebuggingOption(ITextView textView, bool value)
+ {
+ var options = textView.Options.GlobalOptions;
+ options.SetOptionValue(SuggestionModeInDebuggerCompletionOptionKey, value);
+ }
+ }
+}
diff --git a/src/Language/Impl/Language/AsyncCompletion/DefaultCompletionItemManager.cs b/src/Language/Impl/Language/AsyncCompletion/DefaultCompletionItemManager.cs
new file mode 100644
index 0000000..9ae95b5
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/DefaultCompletionItemManager.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Core.Imaging;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.PatternMatching;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ [Export(typeof(IAsyncCompletionItemManagerProvider))]
+ [Name(PredefinedCompletionNames.DefaultCompletionItemManager)]
+ [ContentType("text")]
+ internal sealed class DefaultCompletionItemManagerProvider : IAsyncCompletionItemManagerProvider
+ {
+ [Import]
+ public IPatternMatcherFactory PatternMatcherFactory;
+
+ DefaultCompletionItemManager _instance;
+
+ IAsyncCompletionItemManager IAsyncCompletionItemManagerProvider.GetOrCreate(ITextView textView)
+ {
+ if (_instance == null)
+ _instance = new DefaultCompletionItemManager(PatternMatcherFactory);
+ return _instance;
+ }
+ }
+
+ internal sealed class DefaultCompletionItemManager : IAsyncCompletionItemManager
+ {
+ readonly IPatternMatcherFactory _patternMatcherFactory;
+
+ internal DefaultCompletionItemManager(IPatternMatcherFactory patternMatcherFactory)
+ {
+ _patternMatcherFactory = patternMatcherFactory;
+ }
+
+ Task<FilteredCompletionModel> IAsyncCompletionItemManager.UpdateCompletionListAsync
+ (IAsyncCompletionSession session, AsyncCompletionSessionDataSnapshot data, CancellationToken token)
+ {
+ // Filter by text
+ var filterText = session.ApplicableToSpan.GetText(data.Snapshot);
+ if (string.IsNullOrWhiteSpace(filterText))
+ {
+ // There is no text filtering. Just apply user filters, sort alphabetically and return.
+ IEnumerable<CompletionItem> listFiltered = data.InitialSortedList;
+ if (data.SelectedFilters.Any(n => n.IsSelected))
+ {
+ listFiltered = listFiltered.Where(n => ShouldBeInCompletionList(n, data.SelectedFilters));
+ }
+ var listSorted = listFiltered.OrderBy(n => n.SortText);
+ var listHighlighted = listSorted.Select(n => new CompletionItemWithHighlight(n)).ToImmutableArray();
+ return Task.FromResult(new FilteredCompletionModel(listHighlighted, 0, data.SelectedFilters));
+ }
+
+ // Pattern matcher not only filters, but also provides a way to order the results by their match quality.
+ // The relevant CompletionItem is match.Item1, its PatternMatch is match.Item2
+ var patternMatcher = _patternMatcherFactory.CreatePatternMatcher(
+ filterText,
+ new PatternMatcherCreationOptions(System.Globalization.CultureInfo.CurrentCulture, PatternMatcherCreationFlags.IncludeMatchedSpans));
+
+ var matches = data.InitialSortedList
+ // Perform pattern matching
+ .Select(completionItem => (completionItem, patternMatcher.TryMatch(completionItem.FilterText)))
+ // Pick only items that were matched, unless length of filter text is 1
+ .Where(n => (filterText.Length == 1 || n.Item2.HasValue));
+
+ // See which filters might be enabled based on the typed code
+ var textFilteredFilters = matches.SelectMany(n => n.completionItem.Filters).Distinct();
+
+ // When no items are available for a given filter, it becomes unavailable
+ var updatedFilters = ImmutableArray.CreateRange(data.SelectedFilters.Select(n => n.WithAvailability(textFilteredFilters.Contains(n.Filter))));
+
+ // Filter by user-selected filters. The value on availableFiltersWithSelectionState conveys whether the filter is selected.
+ var filterFilteredList = matches;
+ if (data.SelectedFilters.Any(n => n.IsSelected))
+ {
+ filterFilteredList = matches.Where(n => ShouldBeInCompletionList(n.completionItem, data.SelectedFilters));
+ }
+
+ var bestMatch = filterFilteredList.OrderByDescending(n => n.Item2.HasValue).ThenBy(n => n.Item2).FirstOrDefault();
+ var listWithHighlights = filterFilteredList.Select(n => n.Item2.HasValue ? new CompletionItemWithHighlight(n.completionItem, n.Item2.Value.MatchedSpans) : new CompletionItemWithHighlight(n.completionItem)).ToImmutableArray();
+
+ int selectedItemIndex = 0;
+ if (data.DisplaySuggestionItem)
+ {
+ selectedItemIndex = -1;
+ }
+ else
+ {
+ for (int i = 0; i < listWithHighlights.Length; i++)
+ {
+ if (listWithHighlights[i].CompletionItem == bestMatch.completionItem)
+ {
+ selectedItemIndex = i;
+ break;
+ }
+ }
+ }
+
+ return Task.FromResult(new FilteredCompletionModel(listWithHighlights, selectedItemIndex, updatedFilters));
+ }
+
+ Task<ImmutableArray<CompletionItem>> IAsyncCompletionItemManager.SortCompletionListAsync
+ (IAsyncCompletionSession session, AsyncCompletionSessionInitialDataSnapshot data, CancellationToken token)
+ {
+ return Task.FromResult(data.InitialList.OrderBy(n => n.SortText).ToImmutableArray());
+ }
+
+ #region Filtering
+
+ private static bool ShouldBeInCompletionList(
+ CompletionItem item,
+ ImmutableArray<CompletionFilterWithState> filtersWithState)
+ {
+ foreach (var filterWithState in filtersWithState.Where(n => n.IsSelected))
+ {
+ if (item.Filters.Any(n => n == filterWithState.Filter))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Language/Impl/Language/AsyncCompletion/IAsyncCompletionSessionOperations.cs b/src/Language/Impl/Language/AsyncCompletion/IAsyncCompletionSessionOperations.cs
new file mode 100644
index 0000000..d2a0a2a
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/IAsyncCompletionSessionOperations.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Threading;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ /// <summary>
+ /// Exposes non-public functionality to commanding and tests
+ /// </summary>
+ public interface IAsyncCompletionSessionOperations : IAsyncCompletionSession
+ {
+ /// <summary>
+ /// Sets span applicable to this completion session.
+ /// The span is defined on the session's <see cref="ITextView.TextBuffer"/>.
+ /// </summary>
+ new ITrackingSpan ApplicableToSpan { get; set; }
+
+ /// <summary>
+ /// Returns whether computation has begun.
+ /// Computation starts after calling <see cref="IAsyncCompletionSession.OpenOrUpdate(InitialTrigger, SnapshotPoint, CancellationToken)"/>
+ /// </summary>
+ bool IsStarted { get; }
+
+ /// <summary>
+ /// Enqueues selection a specified item. When all queued tasks are completed, the UI updates.
+ /// </summary>
+ void SelectCompletionItem(CompletionItem item);
+
+ /// <summary>
+ /// Enqueues setting suggestion mode. When all queued tasks are completed, the UI updates.
+ /// </summary>
+ void SetSuggestionMode(bool useSuggestionMode);
+
+ /// <summary>
+ /// Commits unique item. If no items were computed, performs computation. If there is no unique item, shows the UI.
+ /// </summary>
+ void InvokeAndCommitIfUnique(InitialTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token);
+
+ /// <summary>
+ /// Enqueues selecting the next item. When all queued tasks are completed, the UI updates.
+ /// </summary>
+ void SelectDown();
+
+ /// <summary>
+ /// Enqueues selecting the item on the next page. When all queued tasks are completed, the UI updates.
+ /// </summary>
+ void SelectPageDown();
+
+ /// <summary>
+ /// Enqueues selecting the previous item. When all queued tasks are completed, the UI updates.
+ /// </summary>
+ void SelectUp();
+
+ /// <summary>
+ /// Enqueues selecting the item on the previous page. When all queued tasks are completed, the UI updates.
+ /// </summary>
+ void SelectPageUp();
+ }
+}
diff --git a/src/Language/Impl/Language/AsyncCompletion/IModelComputationCallbackHandler.cs b/src/Language/Impl/Language/AsyncCompletion/IModelComputationCallbackHandler.cs
new file mode 100644
index 0000000..a271ca1
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/IModelComputationCallbackHandler.cs
@@ -0,0 +1,11 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ internal interface IModelComputationCallbackHandler<TModel>
+ {
+ Task UpdateUI(TModel model, CancellationToken token);
+ void Dismiss();
+ }
+}
diff --git a/src/Language/Impl/Language/Completion/ImportBucket.cs b/src/Language/Impl/Language/AsyncCompletion/ImportBucket.cs
index 9876600..7d87cf0 100644
--- a/src/Language/Impl/Language/Completion/ImportBucket.cs
+++ b/src/Language/Impl/Language/AsyncCompletion/ImportBucket.cs
@@ -6,10 +6,10 @@ using System.Threading.Tasks;
using Microsoft.VisualStudio.Text.Utilities;
using Microsoft.VisualStudio.Utilities;
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
{
/// <summary>
- /// Lightweight stack-like view over a readonly ordered list of command handlers.
+ /// Lightweight stack-like view over a readonly ordered list of imports.
/// </summary>
internal class ImportBucket<T, TMetadata>
where T : class
diff --git a/src/Language/Impl/Language/Completion/CompletionUtilities.cs b/src/Language/Impl/Language/AsyncCompletion/MetadataUtilities.cs
index ec0d97f..39c7e7e 100644
--- a/src/Language/Impl/Language/Completion/CompletionUtilities.cs
+++ b/src/Language/Impl/Language/AsyncCompletion/MetadataUtilities.cs
@@ -2,28 +2,29 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
-using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.Text.Utilities;
+using Microsoft.VisualStudio.Text.Projection;
using Microsoft.VisualStudio.Utilities;
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
{
internal class MetadataUtilities<T, TMetadata>
- where T : class
- where TMetadata : IContentTypeMetadata
+ where T : class
+ where TMetadata : IContentTypeMetadata
{
/// <summary>
/// This method creates a collection of (T, SnapshotPoint) pairs where the SnapshotPoint is the originalPoint
/// translated to the buffer whose Content Type best matches Content Type associated with T
/// Each instance of T appears only once in the returned collection.
+ /// Must be invoked on UI thread.
/// </summary>
- /// <param name="textView"></param>
- /// <param name="originalPoint"></param>
- /// <param name="imports"></param>
- /// <returns></returns>
- internal static IEnumerable<(ITextBuffer buffer, SnapshotPoint point, Lazy<T, TMetadata> import)> GetOrderedBuffersAndImports(ITextView textView, SnapshotPoint location, Func<IContentType, ITextViewRoleSet, IReadOnlyList<Lazy<T, TMetadata>>> getImports, IComparer<IEnumerable<string>> contentTypeComparer)
+ internal static IEnumerable<(ITextBuffer buffer, SnapshotPoint point, Lazy<T, TMetadata> import)> GetOrderedBuffersAndImports(
+ IBufferGraph bufferGraph,
+ ITextViewRoleSet roles,
+ SnapshotPoint location,
+ Func<IContentType, ITextViewRoleSet, IReadOnlyList<Lazy<T, TMetadata>>> getImports,
+ IComparer<IEnumerable<string>> contentTypeComparer)
{
// This method is created based on EditorCommandHandlerService.GetOrderedBuffersAndCommandHandlers
@@ -53,7 +54,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
// list of (buffer, handler) pairs: (projection buffer, projection handler), (C# buffer, C# handler),
// (projection buffer, any handler).
- var mappedPointsEnumeration = GetPointsOnAvailableBuffers(textView, location);
+ var mappedPointsEnumeration = GetPointsOnAvailableBuffers(bufferGraph, location);
if (!mappedPointsEnumeration.Any())
yield break;
@@ -65,7 +66,7 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
var importBuckets = new ImportBucket<T, TMetadata>[buffers.Length];
for (int i = 0; i < buffers.Length; i++)
{
- importBuckets[i] = new ImportBucket<T, TMetadata>(getImports(buffers[i].ContentType, textView.Roles));
+ importBuckets[i] = new ImportBucket<T, TMetadata>(getImports(buffers[i].ContentType, roles));
}
while (true)
@@ -120,16 +121,16 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
}
/// <summary>
- /// A simpler method that returns all imports with declared content type that matches content type of subject buffers available at the given location
+ /// A simpler method that returns all imports with declared content type that matches content type of subject buffers available at the given location.
+ /// Must be invoked on UI thread.
/// </summary>
- /// <param name="textView"></param>
- /// <param name="location"></param>
- /// <param name="getImports"></param>
- /// <param name="contentTypeComparer"></param>
- /// <returns></returns>
- internal static IEnumerable<(ITextBuffer buffer, SnapshotPoint point, Lazy<T, TMetadata> import)> GetBuffersAndImports(ITextView textView, SnapshotPoint location, Func<IContentType, ITextViewRoleSet, IReadOnlyList<Lazy<T, TMetadata>>> getImports)
+ internal static IEnumerable<(ITextBuffer buffer, SnapshotPoint point, Lazy<T, TMetadata> import)> GetBuffersAndImports(
+ IBufferGraph bufferGraph,
+ ITextViewRoleSet roles,
+ SnapshotPoint location,
+ Func<IContentType, ITextViewRoleSet, IReadOnlyList<Lazy<T, TMetadata>>> getImports)
{
- var mappedPointsEnumeration = GetPointsOnAvailableBuffers(textView, location);
+ var mappedPointsEnumeration = GetPointsOnAvailableBuffers(bufferGraph, location);
if (!mappedPointsEnumeration.Any())
yield break;
@@ -138,36 +139,23 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
// An array of per-buffer buckets, each containing cached list of matching imports,
// ordered by [Order] and content type specificity
- var importBuckets = new ImportBucket<T, TMetadata>[buffers.Length];
for (int i = 0; i < buffers.Length; i++)
{
- foreach (var import in getImports(buffers[i].ContentType, textView.Roles))
+ foreach (var import in getImports(buffers[i].ContentType, roles))
yield return (buffers[i], mappedPoints[i], import);
}
}
- private static IEnumerable<SnapshotPoint> GetPointsOnAvailableBuffers(ITextView textView, SnapshotPoint location)
- {
- var mappingPoint = textView.BufferGraph.CreateMappingPoint(location, PointTrackingMode.Negative);
- var buffers = textView.BufferGraph.GetTextBuffers(b => mappingPoint.GetPoint(b, PositionAffinity.Predecessor) != null);
- var pointsInBuffers = buffers.Select(b => mappingPoint.GetPoint(b, PositionAffinity.Predecessor).Value);
- return pointsInBuffers;
- }
- }
-
- internal static class CompletionUtilities
- {
/// <summary>
- /// Maps given point to buffers that contain this point. Requires UI thread.
+ /// Maps given <see cref="SnapshotPoint"/> to <see cref="SnapshotPoint"/>s on buffers available at this location.
+ /// Must be invoked on UI thread.
/// </summary>
- /// <param name="textView"></param>
- /// <param name="point"></param>
- /// <returns></returns>
- internal static IEnumerable<ITextBuffer> GetBuffersForPoint(ITextView textView, SnapshotPoint point)
+ private static IEnumerable<SnapshotPoint> GetPointsOnAvailableBuffers(IBufferGraph bufferGraph, SnapshotPoint location)
{
- // We are looking at the buffer to the left of the caret.
- return textView.BufferGraph.GetTextBuffers(n =>
- textView.BufferGraph.MapDownToBuffer(point, PointTrackingMode.Negative, n, PositionAffinity.Predecessor) != null);
+ var mappingPoint = bufferGraph.CreateMappingPoint(location, PointTrackingMode.Negative);
+ var buffers = bufferGraph.GetTextBuffers(b => mappingPoint.GetPoint(b, PositionAffinity.Predecessor) != null);
+ var pointsInBuffers = buffers.Select(b => mappingPoint.GetPoint(b, PositionAffinity.Predecessor).Value);
+ return pointsInBuffers;
}
}
}
diff --git a/src/Language/Impl/Language/AsyncCompletion/ModelComputation.cs b/src/Language/Impl/Language/AsyncCompletion/ModelComputation.cs
new file mode 100644
index 0000000..d553d3e
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/ModelComputation.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Threading;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ /// <summary>
+ /// Facilitates enqueuing tasks to be ran on a worker thread.
+ /// Each task takes an immutable instance of <typeparamref name="TModel"/>
+ /// and outputs an instance of <typeparamref name="TModel"/>.
+ /// The returned instance will serve as input to the next task.
+ /// </summary>
+ /// <typeparam name="TModel">Type that represents a snapshot of feature's state</typeparam>
+ sealed class ModelComputation<TModel> where TModel : class
+ {
+ private readonly JoinableTaskFactory _joinableTaskFactory;
+ private readonly TaskScheduler _computationTaskScheduler;
+ private readonly CancellationToken _token;
+ private readonly IGuardedOperations _guardedOperations;
+ private readonly IModelComputationCallbackHandler<TModel> _callbacks;
+
+ private bool _terminated;
+ private JoinableTask<TModel> _lastJoinableTask;
+ private CancellationTokenSource _uiCancellation;
+
+ internal TModel RecentModel { get; private set; } = default;
+
+ /// <summary>
+ /// Creates an instance of <see cref="ModelComputation{TModel}"/>
+ /// and enqueues an task that will generate the initial state of the <typeparamref name="TModel"/>
+ /// </summary>
+#pragma warning disable CA1068 // CancellationToken should be the last parameter
+ public ModelComputation(
+ TaskScheduler computationTaskScheduler,
+ JoinableTaskContext joinableTaskContext,
+ Func<TModel, CancellationToken, Task<TModel>> initialTransformation,
+ CancellationToken token,
+ IGuardedOperations guardedOperations,
+ IModelComputationCallbackHandler<TModel> callbacks)
+#pragma warning restore CA1068
+ {
+ _joinableTaskFactory = joinableTaskContext.Factory;
+ _computationTaskScheduler = computationTaskScheduler;
+ _token = token;
+ _guardedOperations = guardedOperations;
+ _callbacks = callbacks;
+
+ // Start dummy tasks so that we don't need to check for null on first Enqueue
+ _lastJoinableTask = _joinableTaskFactory.RunAsync(() => Task.FromResult(default(TModel)));
+ _uiCancellation = new CancellationTokenSource();
+
+ // Immediately run the first transformation, to operate on proper TModel.
+ Enqueue(initialTransformation, updateUi: false);
+ }
+
+ /// <summary>
+ /// Schedules work to be done on the background,
+ /// potentially preempted by another piece of work scheduled in the future,
+ /// <paramref name="updateUi" /> indicates whether a single piece of work should occue once all background work is completed.
+ /// </summary>
+ public void Enqueue(Func<TModel, CancellationToken, Task<TModel>> transformation, bool updateUi)
+ {
+ // The integrity of our sequential chain depends on this method not being called concurrently.
+ // So we require the UI thread.
+ if (!_joinableTaskFactory.Context.IsOnMainThread)
+ throw new InvalidOperationException($"This method must be callled on the UI thread.");
+
+ if (_token.IsCancellationRequested || _terminated)
+ return; // Don't enqueue after computation has stopped.
+
+ // Attempt to commit (CommitIfUnique) will cancel the UI updates. If the commit failed, we still want to update the UI.
+ if (_uiCancellation.IsCancellationRequested)
+ _uiCancellation = new CancellationTokenSource();
+
+ var previousTask = _lastJoinableTask;
+ JoinableTask<TModel> currentTask = null;
+ currentTask = _joinableTaskFactory.RunAsync(async () =>
+ {
+ await Task.Yield(); // Yield to guarantee that currentTask is assigned.
+ await _computationTaskScheduler; // Go to the above priority thread. Main thread will return as soon as possible.
+ try
+ {
+ var previousModel = await previousTask;
+ // Previous task finished processing. We are ready to execute next piece of work.
+ if (_token.IsCancellationRequested || _terminated)
+ return previousModel;
+
+ var transformedModel = await transformation(await previousTask, _token).ConfigureAwait(true);
+ RecentModel = transformedModel;
+
+ // TODO: update UI even if updateUi is false but it wasn't updated yet.
+ if (_lastJoinableTask == currentTask && updateUi)
+ {
+ // update UI because we're the latest task
+ if (!_uiCancellation.IsCancellationRequested)
+ _callbacks.UpdateUI(transformedModel, _uiCancellation.Token).Forget();
+ }
+
+ return transformedModel;
+ }
+ catch (Exception ex)
+ {
+ _terminated = true;
+ _guardedOperations.HandleException(this, ex);
+ _callbacks.Dismiss();
+ return await previousTask;
+ }
+ });
+
+ _lastJoinableTask = currentTask;
+ }
+
+ /// <summary>
+ /// Blocks, waiting for all background work to finish.
+ /// </summary>
+ /// <param name="cancelUi">Whether UI should be dismissed. If false, this method will return after UI has been rendered</param>
+ /// <param name="dontWaitForUpdatedModel">Returns last available model without block. Used in WYSIWYG mode.</param>
+ /// <param name="token">Token used to cancel the operation, unblock the thread and return null</param>
+ /// <returns></returns>
+ public TModel WaitAndGetResult(bool cancelUi, CancellationToken token)
+ {
+ if (cancelUi)
+ _uiCancellation.Cancel();
+
+ try
+ {
+ return _lastJoinableTask.Join(token);
+ }
+ catch (OperationCanceledException)
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/Language/Impl/Language/Completion/PrioritizedTaskScheduler.cs b/src/Language/Impl/Language/AsyncCompletion/PrioritizedTaskScheduler.cs
index 8dd5394..3c054d9 100644
--- a/src/Language/Impl/Language/Completion/PrioritizedTaskScheduler.cs
+++ b/src/Language/Impl/Language/AsyncCompletion/PrioritizedTaskScheduler.cs
@@ -4,7 +4,7 @@ using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
{
/// <summary>
/// Clone of Roslyn's PrioritizedTaskScheduler
diff --git a/src/Language/Impl/Language/AsyncCompletion/SuggestionModeCompletionItemSource.cs b/src/Language/Impl/Language/AsyncCompletion/SuggestionModeCompletionItemSource.cs
new file mode 100644
index 0000000..97d031b
--- /dev/null
+++ b/src/Language/Impl/Language/AsyncCompletion/SuggestionModeCompletionItemSource.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
+{
+ /// <summary>
+ /// Internal item source used during lifetime of the suggestion mode item.
+ /// </summary>
+ internal class SuggestionModeCompletionItemSource : IAsyncCompletionSource
+ {
+ private SuggestionItemOptions _options;
+
+ internal SuggestionModeCompletionItemSource(SuggestionItemOptions options)
+ {
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+ }
+
+ Task<CompletionContext> IAsyncCompletionSource.GetCompletionContextAsync(InitialTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableToSpan, CancellationToken token)
+ {
+ throw new NotImplementedException("This item source is not meant to be registered. It is used only to provide a tooltip.");
+ }
+
+ Task<object> IAsyncCompletionSource.GetDescriptionAsync(CompletionItem item, CancellationToken token)
+ {
+ return Task.FromResult<object>(_options.ToolTipText);
+ }
+
+ bool IAsyncCompletionSource.TryGetApplicableToSpan(char typedChar, SnapshotPoint triggerLocation, out SnapshotSpan applicableToSpan, CancellationToken token)
+ {
+ applicableToSpan = default;
+ return false;
+ }
+ }
+}
diff --git a/src/Language/Impl/Language/Completion/TextUndoTransactionThatRollsBackProperly.cs b/src/Language/Impl/Language/AsyncCompletion/TextUndoTransactionThatRollsBackProperly.cs
index 54b6778..0fbb876 100644
--- a/src/Language/Impl/Language/Completion/TextUndoTransactionThatRollsBackProperly.cs
+++ b/src/Language/Impl/Language/AsyncCompletion/TextUndoTransactionThatRollsBackProperly.cs
@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.Text.Operations;
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
{
/// <summary>
/// An implementation of <see cref="ITextUndoTransaction" /> that wraps another
diff --git a/src/Language/Impl/Language/Completion/UndoUtilities.cs b/src/Language/Impl/Language/AsyncCompletion/UndoUtilities.cs
index 3eb4d8f..9477d7b 100644
--- a/src/Language/Impl/Language/Completion/UndoUtilities.cs
+++ b/src/Language/Impl/Language/AsyncCompletion/UndoUtilities.cs
@@ -4,12 +4,12 @@ using Microsoft.VisualStudio.Text;
using System.Collections.Generic;
using System.Linq;
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
+namespace Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Implementation
{
/// <summary>
/// Code taken from http://source.roslyn.io/#Microsoft.CodeAnalysis.EditorFeatures/Implementation/IntelliSense/Completion/Controller_Commit.cs
/// </summary>
- static class UndoUtilities
+ internal static class UndoUtilities
{
internal static void RollbackToBeforeTypeChar(ITextSnapshot initialTextSnapshot, ITextBuffer subjectBuffer)
{
diff --git a/src/Language/Impl/Language/Completion/AsyncCompletionBroker.cs b/src/Language/Impl/Language/Completion/AsyncCompletionBroker.cs
deleted file mode 100644
index 8ee9e35..0000000
--- a/src/Language/Impl/Language/Completion/AsyncCompletionBroker.cs
+++ /dev/null
@@ -1,382 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.ComponentModel.Composition;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.VisualStudio.Text;
-using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.Text.Operations;
-using Microsoft.VisualStudio.Text.Utilities;
-using Microsoft.VisualStudio.Threading;
-using Microsoft.VisualStudio.Utilities;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
-{
- [Export(typeof(IAsyncCompletionBroker))]
- internal class AsyncCompletionBroker : IAsyncCompletionBroker
- {
- [Import(AllowDefault = true)]
- private ILoggingServiceInternal Logger;
-
- [Import]
- private IGuardedOperations GuardedOperations;
-
- [Import]
- private JoinableTaskContext JoinableTaskContext;
-
- [Import]
- private IContentTypeRegistryService ContentTypeRegistryService;
-
- // Used exclusively for legacy telemetry
- [Import(AllowDefault = true)]
- private ITextDocumentFactoryService TextDocumentFactoryService;
-
- [ImportMany]
- private IEnumerable<Lazy<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> UnorderedPresenterProviders;
-
- [ImportMany]
- private IEnumerable<Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> UnorderedCompletionItemSourceProviders;
-
- [ImportMany]
- private IEnumerable<Lazy<IAsyncCompletionServiceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> UnorderedCompletionServiceProviders;
-
- private IList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> _orderedPresenterProviders;
- private IList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> OrderedPresenterProviders
- => _orderedPresenterProviders ?? (_orderedPresenterProviders = Orderer.Order(UnorderedPresenterProviders));
-
- private IList<Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> _orderedCompletionItemSourceProviders;
- private IList<Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> OrderedCompletionItemSourceProviders
- => _orderedCompletionItemSourceProviders ?? (_orderedCompletionItemSourceProviders = Orderer.Order(UnorderedCompletionItemSourceProviders));
-
- private IList<Lazy<IAsyncCompletionServiceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> _orderedCompletionServiceProviders;
- private IList<Lazy<IAsyncCompletionServiceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> OrderedCompletionServiceProviders
- => _orderedCompletionServiceProviders ?? (_orderedCompletionServiceProviders = Orderer.Order(UnorderedCompletionServiceProviders));
-
- private ImmutableDictionary<IContentType, ImmutableSortedSet<char>> _commitCharacters = ImmutableDictionary<IContentType, ImmutableSortedSet<char>>.Empty;
- private ImmutableDictionary<IContentType, ImmutableArray<IAsyncCompletionItemSourceProvider>> _cachedCompletionItemSourceProviders = ImmutableDictionary<IContentType, ImmutableArray<IAsyncCompletionItemSourceProvider>>.Empty;
- private ImmutableDictionary<IContentType, ImmutableArray<IAsyncCompletionServiceProvider>> _cachedCompletionServiceProviders = ImmutableDictionary<IContentType, ImmutableArray<IAsyncCompletionServiceProvider>>.Empty;
- private ImmutableDictionary<IContentType, ICompletionPresenterProvider> _cachedPresenterProviders = ImmutableDictionary<IContentType, ICompletionPresenterProvider>.Empty;
- private bool firstRun = true; // used only for diagnostics
- private bool _firstInvocationReported; // used for "time to code"
- private StableContentTypeComparer _contentTypeComparer;
- private const string IsCompletionAvailableProperty = "IsCompletionAvailable";
-
- private Dictionary<IContentType, bool> FeatureAvailabilityByContentType = new Dictionary<IContentType, bool>();
-
- bool IAsyncCompletionBroker.IsCompletionSupported(IContentType contentType)
- {
- bool featureIsAvailable;
- if (FeatureAvailabilityByContentType.TryGetValue(contentType, out featureIsAvailable))
- {
- return featureIsAvailable;
- }
-
- featureIsAvailable = UnorderedCompletionItemSourceProviders
- .Any(n => n.Metadata.ContentTypes.Any(ct => contentType.IsOfType(ct)));
- featureIsAvailable &= UnorderedCompletionServiceProviders
- .Any(n => n.Metadata.ContentTypes.Any(ct => contentType.IsOfType(ct)));
-
- FeatureAvailabilityByContentType[contentType] = featureIsAvailable;
- return featureIsAvailable;
- }
-
- IAsyncCompletionSession IAsyncCompletionBroker.TriggerCompletion(ITextView textView, SnapshotPoint triggerLocation, char typedChar)
- {
- var session = GetSession(textView);
- if (session != null)
- {
- return session;
- }
-
- var sourcesWithData = MetadataUtilities<IAsyncCompletionItemSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>.GetBuffersAndImports(textView, triggerLocation, GetCompletionItemSourceProviders);
-
- // We will pass this to the next TriggerCompletion. This feels hacky, refactor the way parts are collected and built
- var cachedData = new CompletionSourcesWithData(sourcesWithData);
-
- SnapshotSpan? applicableSpan = null;
- foreach (var sourceWithData in sourcesWithData)
- {
- var sourceProvider = GuardedOperations.InstantiateExtension(this, sourceWithData.import); // TODO: consider caching this
- var source = sourceProvider.GetOrCreate(textView);
- var candidateSpan = GuardedOperations.CallExtensionPoint(
- errorSource: source,
- call: () => source.ShouldTriggerCompletion(typedChar, sourceWithData.point),
- valueOnThrow: null
- );
-
- // Assume that sources are ordered. If this source is the first one to provide span, map it to the view's top buffer and use it for completion,
- if (applicableSpan == null && candidateSpan.HasValue)
- {
- var mappingSpan = textView.BufferGraph.CreateMappingSpan(candidateSpan.Value, SpanTrackingMode.EdgeInclusive);
- applicableSpan = mappingSpan.GetSpans(textView.TextBuffer)[0];
- }
- }
- return applicableSpan.HasValue
- ? TriggerCompletion(textView, triggerLocation, applicableSpan.Value, cachedData)
- : null;
- }
-
- private IAsyncCompletionSession TriggerCompletion(ITextView textView, SnapshotPoint triggerLocation, SnapshotSpan applicableSpan, CompletionSourcesWithData sources)
- {
- var session = GetSession(textView);
- if (session != null)
- {
- return session;
- }
-
- if (!sources.Data.Any())
- {
- // There is no completion source available for this buffer
- return null;
- }
-
- var potentialCommitCharsBuilder = ImmutableArray.CreateBuilder<char>();
- var sourcesWithLocations = new List<(IAsyncCompletionItemSource, SnapshotPoint)>();
- foreach (var sourceWithData in sources.Data)
- {
- var sourceProvider = GuardedOperations.InstantiateExtension(this, sourceWithData.import); // TODO: consider caching this
- GuardedOperations.CallExtensionPoint(
- errorSource: sourceProvider,
- call: () =>
- {
- var source = sourceProvider.GetOrCreate(textView);
- potentialCommitCharsBuilder.AddRange(source.GetPotentialCommitCharacters());
- sourcesWithLocations.Add((source, sourceWithData.point));
- });
- }
-
- if (_contentTypeComparer == null)
- _contentTypeComparer = new StableContentTypeComparer(ContentTypeRegistryService);
-
- var servicesWithLocations = MetadataUtilities<IAsyncCompletionServiceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>.GetOrderedBuffersAndImports(textView, triggerLocation, GetServiceProviders, _contentTypeComparer);
- var bestServiceWithData = servicesWithLocations.FirstOrDefault();
- var serviceProvider = GuardedOperations.InstantiateExtension(this, bestServiceWithData.import); // TODO: consider caching this
- var service = GuardedOperations.CallExtensionPoint(serviceProvider, () => serviceProvider.GetOrCreate(textView), null);
- if (service == null)
- {
- // This should never happen because we provide a default and IsCompletionFeatureAvailable would have returned false
- throw new InvalidOperationException("No completion services not found. Completion will be unavailable.");
- }
-
- var presentationProvidersWithLocations = MetadataUtilities<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>.GetOrderedBuffersAndImports(textView, triggerLocation, GetPresenters, _contentTypeComparer);
- var bestPresentationProviderWithLocation = presentationProvidersWithLocations.FirstOrDefault();
- var presenterProvider = GuardedOperations.InstantiateExtension(this, bestPresentationProviderWithLocation.import); // TODO: consider caching this
-
- if (firstRun)
- {
- System.Diagnostics.Debug.Assert(presenterProvider != null, $"No instance of {nameof(ICompletionPresenterProvider)} is loaded. Completion will work without the UI.");
- firstRun = false;
- }
- var telemetry = GetOrCreateTelemetry(textView);
-
- session = new AsyncCompletionSession(applicableSpan, potentialCommitCharsBuilder.ToImmutable(), JoinableTaskContext.Factory, presenterProvider, sourcesWithLocations, service, this, textView, telemetry, GuardedOperations);
- textView.Properties.AddProperty(typeof(IAsyncCompletionSession), session);
- textView.Closed += TextView_Closed;
-
- // Additionally, emulate the legacy completion telemetry
- EmulateLegacyCompletionTelemetry(bestServiceWithData.buffer?.ContentType, textView);
-
- return session;
- }
-
- private IReadOnlyList<Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> GetCompletionItemSourceProviders(IContentType contentType, ITextViewRoleSet textViewRoles)
- {
- return OrderedCompletionItemSourceProviders.Where(n => n.Metadata.ContentTypes.Any(c => contentType.IsOfType(c)) && (n.Metadata.TextViewRoles == null || textViewRoles.ContainsAny(n.Metadata.TextViewRoles))).ToList();
- }
- private IReadOnlyList<Lazy<IAsyncCompletionServiceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> GetServiceProviders(IContentType contentType, ITextViewRoleSet textViewRoles)
- {
- return OrderedCompletionServiceProviders.Where(n => n.Metadata.ContentTypes.Any(c => contentType.IsOfType(c)) && (n.Metadata.TextViewRoles == null || textViewRoles.ContainsAny(n.Metadata.TextViewRoles))).OrderBy(n => n.Metadata.ContentTypes, _contentTypeComparer).ToList();
- }
- private IReadOnlyList<Lazy<ICompletionPresenterProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata>> GetPresenters(IContentType contentType, ITextViewRoleSet textViewRoles)
- {
- return OrderedPresenterProviders.Where(n => n.Metadata.ContentTypes.Any(c => contentType.IsOfType(c)) && (n.Metadata.TextViewRoles == null || textViewRoles.ContainsAny(n.Metadata.TextViewRoles))).OrderBy(n => n.Metadata.ContentTypes, _contentTypeComparer).ToList();
- }
-
- // TODO: Evaluate the methods below and clean them up. We should have one reliable way to get parts in correct order
- private ImmutableArray<IAsyncCompletionItemSourceProvider> GetCompletionItemSourceProviders(IContentType contentType)
- {
- if (_cachedCompletionItemSourceProviders.TryGetValue(contentType, out var cachedSourceProviders))
- {
- return cachedSourceProviders;
- }
-
- var providers = GuardedOperations.InvokeMatchingFactories(
- lazyFactories: OrderedCompletionItemSourceProviders,
- getter: n => n,
- dataContentType: contentType,
- errorSource: this);
-
- var result = providers.ToImmutableArray();
- _cachedCompletionItemSourceProviders = _cachedCompletionItemSourceProviders.Add(contentType, result);
- return result;
- }
-
- private ImmutableArray<IAsyncCompletionServiceProvider> GetCompletionServiceProviders(IContentType contentType)
- {
- if (_cachedCompletionServiceProviders.TryGetValue(contentType, out var serviceProvider))
- {
- return serviceProvider;
- }
-
- var providers = GuardedOperations.InvokeMatchingFactories(
- lazyFactories: OrderedCompletionServiceProviders,
- getter: n => n,
- dataContentType: contentType,
- errorSource: this);
-
- var result = providers.ToImmutableArray();
- _cachedCompletionServiceProviders = _cachedCompletionServiceProviders.Add(contentType, result);
- return result;
- }
-
- private ICompletionPresenterProvider GetUiFactory(IContentType contentType)
- {
- if (_cachedPresenterProviders.TryGetValue(contentType, out var factory))
- {
- return factory;
- }
-
- ICompletionPresenterProvider bestFactory = GuardedOperations.InvokeBestMatchingFactory(
- providerHandles: OrderedPresenterProviders,
- getter: n => n,
- dataContentType: contentType,
- contentTypeRegistryService: ContentTypeRegistryService,
- errorSource: this);
-
- _cachedPresenterProviders = _cachedPresenterProviders.Add(contentType, bestFactory);
- return bestFactory;
- }
-
- internal bool TryGetKnownCommitCharacters(IContentType contentType, ITextView view, out ImmutableSortedSet<char> commitChars)
- {
- if (_commitCharacters.TryGetValue(contentType, out commitChars))
- {
- return commitChars.Any();
- }
- var allCommitChars = new List<char>();
- foreach (var source in
- GetCompletionItemSourceProviders(contentType)
- .Select(n => n.GetOrCreate(view)))
- {
- GuardedOperations.CallExtensionPoint(
- errorSource: source,
- call: () => allCommitChars.AddRange(source.GetPotentialCommitCharacters())
- );
- }
- commitChars = ImmutableSortedSet.CreateRange(allCommitChars);
- _commitCharacters = _commitCharacters.Add(contentType, commitChars);
- return commitChars.Any();
- }
-
- private void TextView_Closed(object sender, EventArgs e)
- {
- var view = (ITextView)sender;
- view.Closed -= TextView_Closed;
- GetSession(view)?.Dismiss();
- try
- {
- SendTelemetry(view);
- }
- catch (Exception ex)
- {
- GuardedOperations.HandleException(this, ex);
- }
- }
-
- bool IAsyncCompletionBroker.IsCompletionActive(ITextView textView)
- {
- return textView.Properties.ContainsProperty(typeof(IAsyncCompletionSession));
- }
-
- public IAsyncCompletionSession GetSession(ITextView textView)
- {
- if (textView.Properties.TryGetProperty(typeof(IAsyncCompletionSession), out IAsyncCompletionSession session))
- {
- return session;
- }
- return null;
- }
-
- /// <summary>
- /// This method is used by <see cref="IAsyncCompletionSession"/> to inform the broker that it should forget about the session. Used when dismissing.
- /// This method does not dismiss the session!
- /// </summary>
- /// <param name="session">Session being dismissed</param>
- internal void ForgetSession(IAsyncCompletionSession session)
- {
- session.TextView.Properties.RemoveProperty(typeof(IAsyncCompletionSession));
- }
-
- /// <summary>
- /// Wrapper around complex parameters. This is a candidate for refactoring.
- /// </summary>
- private struct CompletionSourcesWithData
- {
- internal IEnumerable<(ITextBuffer buffer, SnapshotPoint point, Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata> import)> Data;
-
- public CompletionSourcesWithData(IEnumerable<(ITextBuffer buffer, SnapshotPoint point, Lazy<IAsyncCompletionItemSourceProvider, IOrderableContentTypeAndOptionalTextViewRoleMetadata> import)> data)
- {
- Data = data;
- }
- }
-
- // Helper methods for telemetry:
- private CompletionTelemetryHost GetOrCreateTelemetry(ITextView textView)
- {
- if (textView.Properties.TryGetProperty(typeof(CompletionTelemetryHost), out CompletionTelemetryHost telemetry))
- {
- return telemetry;
- }
- else
- {
- var newTelemetry = new CompletionTelemetryHost(Logger, this);
- textView.Properties.AddProperty(typeof(CompletionTelemetryHost), newTelemetry);
- return newTelemetry;
- }
- }
-
- private void SendTelemetry(ITextView textView)
- {
- if (textView.Properties.TryGetProperty(typeof(CompletionTelemetryHost), out CompletionTelemetryHost telemetry))
- {
- telemetry.Send();
- textView.Properties.RemoveProperty(typeof(CompletionTelemetryHost));
- }
- }
-
- internal string GetItemSourceName(IAsyncCompletionItemSource source) => OrderedCompletionItemSourceProviders.FirstOrDefault(n => n.IsValueCreated && n.Value == source)?.Metadata.Name ?? string.Empty;
- internal string GetCompletionServiceName(IAsyncCompletionService service) => OrderedCompletionServiceProviders.FirstOrDefault(n => n.IsValueCreated && n.Value == service)?.Metadata.Name ?? string.Empty;
- internal string GetCompletionPresenterProviderName(ICompletionPresenterProvider provider) => OrderedPresenterProviders.FirstOrDefault(n => n.IsValueCreated && n.Value == provider)?.Metadata.Name ?? string.Empty;
-
- // Parity with legacy telemetry
- private void EmulateLegacyCompletionTelemetry(IContentType contentType, ITextView textView)
- {
- if (Logger == null || _firstInvocationReported)
- return;
-
- string GetFileExtension(ITextBuffer buffer)
- {
- var documentFactoryService = TextDocumentFactoryService;
- if (buffer != null && documentFactoryService != null)
- {
- ITextDocument currentDocument = null;
- documentFactoryService.TryGetTextDocument(buffer, out currentDocument);
- if (currentDocument != null && currentDocument.FilePath != null)
- {
- return System.IO.Path.GetExtension(currentDocument.FilePath);
- }
- }
- return null;
- }
- var fileExtension = GetFileExtension(textView.TextBuffer) ?? "Unknown";
- var reportedContentType = contentType?.ToString() ?? "Unknown";
-
- _firstInvocationReported = true;
- Logger.PostEvent(TelemetryEventType.Operation, "VS/Editor/IntellisenseFirstRun/Opened", TelemetryResult.Success,
- ("VS.Editor.IntellisenseFirstRun.Opened.ContentType", reportedContentType),
- ("VS.Editor.IntellisenseFirstRun.Opened.FileExtension", fileExtension));
- }
- }
-}
diff --git a/src/Language/Impl/Language/Completion/AsyncCompletionSession.cs b/src/Language/Impl/Language/Completion/AsyncCompletionSession.cs
deleted file mode 100644
index bda6905..0000000
--- a/src/Language/Impl/Language/Completion/AsyncCompletionSession.cs
+++ /dev/null
@@ -1,776 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.VisualStudio.Language.Intellisense;
-using Microsoft.VisualStudio.Text;
-using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.Text.Operations;
-using Microsoft.VisualStudio.Text.Utilities;
-using Microsoft.VisualStudio.Threading;
-using Microsoft.VisualStudio.Utilities;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
-{
- /// <summary>
- /// Holds a state of the session
- /// and a reference to the UI element
- /// </summary>
- internal class AsyncCompletionSession : IAsyncCompletionSession, ICompletionComputationCallbackHandler<CompletionModel>, IPropertyOwner
- {
- // Available data and services
- private readonly IList<(IAsyncCompletionItemSource Source, SnapshotPoint Point)> _completionSources;
- private readonly IDictionary<IAsyncCompletionItemSource, SnapshotPoint> _completionSourcesWhoGaveItems;
- private readonly IAsyncCompletionService _completionService;
- private readonly SnapshotSpan _initialApplicableSpan;
- private readonly JoinableTaskFactory _jtf;
- private readonly ICompletionPresenterProvider _presenterProvider;
- private readonly AsyncCompletionBroker _broker;
- private readonly ITextView _textView;
- private readonly IGuardedOperations _guardedOperations;
- private readonly ImmutableArray<char> _potentialCommitChars;
-
- // Presentation:
- ICompletionPresenter _gui; // Must be accessed from GUI thread
- const int FirstIndex = 0;
- readonly int PageStepSize;
-
- // Computation state machine
- private ModelComputation<CompletionModel> _computation;
- private readonly CancellationTokenSource _computationCancellation = new CancellationTokenSource();
- int _lastFilteringTaskId;
-
- // Telemetry:
-
- private readonly CompletionSessionTelemetry _telemetry;
-
- /// <summary>
- /// Tracks time spent on the worker thread - getting data, filtering and sorting. Used for telemetry.
- /// </summary>
- internal Stopwatch ComputationStopwatch { get; } = new Stopwatch();
-
- /// <summary>
- /// Tracks time spent on the UI thread - either rendering or committing. Used for telemetry.
- /// </summary>
- internal Stopwatch UiStopwatch { get; } = new Stopwatch();
-
- // When set, UI will no longer be updated
- private bool _isDismissed;
-
- // Facilitate experience when there are no items to display
- private bool _selectionModeBeforeNoResultFallback;
- private bool _inNoResultFallback;
- private bool _ignoreCaretMovement;
-
- public event EventHandler<CompletionItemEventArgs> ItemCommitted;
- public event EventHandler Dismissed;
- public event EventHandler<CompletionItemsWithHighlightEventArgs> ItemsUpdated;
-
- public ITextView TextView => _textView;
-
- public bool IsDismissed => _isDismissed;
-
- public PropertyCollection Properties { get; }
-
- public AsyncCompletionSession(SnapshotSpan applicableSpan, ImmutableArray<char> potentialCommitChars, JoinableTaskFactory jtf,
- ICompletionPresenterProvider presenterProvider, IList<(IAsyncCompletionItemSource, SnapshotPoint)> completionSources,
- IAsyncCompletionService completionService, AsyncCompletionBroker broker, ITextView view, CompletionTelemetryHost telemetryHost, IGuardedOperations guardedOperations)
- {
- _initialApplicableSpan = applicableSpan;
- _potentialCommitChars = potentialCommitChars;
- _jtf = jtf;
- _presenterProvider = presenterProvider;
- _broker = broker;
- _completionSources = completionSources; // still prorotype at the momemnt.
- _completionSourcesWhoGaveItems = new Dictionary<IAsyncCompletionItemSource, SnapshotPoint>(); // To be filled in GetInitialModel
- _completionService = completionService;
- _textView = view;
- _guardedOperations = guardedOperations;
- _telemetry = new CompletionSessionTelemetry(telemetryHost, completionService, presenterProvider);
- PageStepSize = presenterProvider?.ResultsPerPage ?? 1;
- _textView.Caret.PositionChanged += OnCaretPositionChanged;
- Properties = new PropertyCollection();
- }
-
- bool IAsyncCompletionSession.CommitIfUnique(CancellationToken token)
- {
- if (_isDismissed)
- return false;
-
- // Note that this will deadlock if OpenOrUpdate wasn't called ahead of time.
- var lastModel = _computation.WaitAndGetResult();
- if (lastModel.UniqueItem != null)
- {
- Commit(default(char), lastModel.UniqueItem, token);
- return true;
- }
- else if (!lastModel.PresentedItems.IsDefaultOrEmpty && lastModel.PresentedItems.Length == 1)
- {
- Commit(default(char), lastModel.PresentedItems[0].CompletionItem, token);
- return true;
- }
- else
- {
- // Show the UI, because waitAndGetResult canceled showing the UI.
- UpdateUiInner(lastModel); // We are on the UI thread, so we may call UpdateUiInner
- return false;
- }
- }
-
- /// <summary>
- /// Entry point for committing. Decides whether to commit or not. The inner commit method calls Dispose to stop this session and hide the UI.
- /// </summary>
- /// <param name="token">Command handler infrastructure provides a token that we should pass to the language service's custom commit method.</param>
- /// <param name="typedChar">It is default(char) when commit was requested by an explcit command (e.g. hitting Tab, Enter or clicking)
- /// and it is not default(char) when commit happens as a result of typing a commit character.</param>
- CommitBehavior IAsyncCompletionSession.Commit(CancellationToken token, char typedChar)
- {
- if (_isDismissed)
- return CommitBehavior.None;
-
- var lastModel = _computation.WaitAndGetResult();
-
- if (lastModel.UseSoftSelection && !typedChar.Equals(default(char)))
- {
- // In soft selection mode, user commits explicitly (click, tab, e.g. not tied to a text change). Otherwise, we dismiss the session
- ((IAsyncCompletionSession)this).Dismiss();
- return CommitBehavior.None;
- }
- else if (lastModel.SelectSuggestionMode && string.IsNullOrWhiteSpace(lastModel.SuggestionModeItem?.InsertText))
- {
- // In suggestion mode, don't commit empty suggestion (suggestion item temporarily shows description of suggestion mode)
- return CommitBehavior.None;
- }
- else if (lastModel.SelectSuggestionMode)
- {
- // Commit the suggestion mode item
- return Commit(typedChar, lastModel.SuggestionModeItem, token);
- }
- else if (lastModel.PresentedItems.IsDefaultOrEmpty)
- {
- // There is nothing to commit
- Dismiss();
- return CommitBehavior.None;
- }
- else
- {
- // Regular commit
- return Commit(typedChar, lastModel.PresentedItems[lastModel.SelectedIndex].CompletionItem, token);
- }
- }
-
- private CommitBehavior Commit(char typedChar, CompletionItem itemToCommit, CancellationToken token)
- {
- CommitBehavior result = CommitBehavior.None;
- if (_isDismissed)
- return result;
-
- var lastModel = _computation.WaitAndGetResult();
-
- if (!_jtf.Context.IsOnMainThread)
- throw new InvalidOperationException($"{nameof(IAsyncCompletionSession)}.{nameof(IAsyncCompletionSession.Commit)} must be callled from UI thread.");
-
- UiStopwatch.Restart();
-
- // Pass appropriate buffer to the item's provider
- var buffer = _completionSourcesWhoGaveItems[itemToCommit.Source].Snapshot.TextBuffer;
- if (itemToCommit.UseCustomCommit)
- {
- result = _guardedOperations.CallExtensionPoint(
- errorSource: itemToCommit.Source,
- call: () => itemToCommit.Source.CustomCommit(_textView, buffer, itemToCommit, lastModel.ApplicableSpan, typedChar, token),
- valueOnThrow: CommitBehavior.None);
- }
- else
- {
- result = _guardedOperations.CallExtensionPoint(
- errorSource: itemToCommit.Source,
- call: () => itemToCommit.Source.GetDefaultCommitBehavior(_textView, buffer, itemToCommit, lastModel.ApplicableSpan, typedChar, token),
- valueOnThrow: CommitBehavior.None);
-
- InsertIntoBuffer(_textView, lastModel, itemToCommit.InsertText, typedChar);
- }
- UiStopwatch.Stop();
- _telemetry.RecordCommitted(UiStopwatch.ElapsedMilliseconds, itemToCommit);
- _guardedOperations.RaiseEvent(this, ItemCommitted, new CompletionItemEventArgs(itemToCommit));
- Dismiss();
- return result;
- }
-
- private void InsertIntoBuffer(ITextView view, CompletionModel model, string insertText, char typeChar)
- {
- var buffer = view.TextBuffer;
- var bufferEdit = buffer.CreateEdit();
-
- // ApplicableSpan already contains the typeChar and brace completion. Replacing this span will cause us to lose this data.
- // The command handler who invoked this code needs to re-play the type char command, such that we get these changes back.
- bufferEdit.Replace(model.ApplicableSpan.GetSpan(buffer.CurrentSnapshot), insertText);
- bufferEdit.Apply();
- }
-
- public void Dismiss()
- {
- if (_isDismissed)
- return;
-
- _isDismissed = true;
- _broker.ForgetSession(this);
- _guardedOperations.RaiseEvent(this, Dismissed);
- _textView.Caret.PositionChanged -= OnCaretPositionChanged;
- _computationCancellation.Cancel();
-
- if (_gui != null)
- {
- var copyOfGui = _gui;
- _guardedOperations.CallExtensionPointAsync(
- errorSource: _gui,
- asyncAction: async () =>
- {
- await _jtf.SwitchToMainThreadAsync();
- copyOfGui.FiltersChanged -= OnFiltersChanged;
- copyOfGui.CommitRequested -= OnCommitRequested;
- copyOfGui.CompletionItemSelected -= OnItemSelected;
- copyOfGui.CompletionClosed -= OnGuiClosed;
- copyOfGui.Close();
- });
- _gui = null;
- }
- }
-
- void IAsyncCompletionSession.OpenOrUpdate(CompletionTrigger trigger, SnapshotPoint triggerLocation)
- {
- if (_isDismissed)
- return;
-
- if (_computation == null)
- {
- _computation = new ModelComputation<CompletionModel>(PrioritizedTaskScheduler.AboveNormalInstance, _computationCancellation.Token, _guardedOperations, this);
- _computation.Enqueue((model, token) => GetInitialModel(_textView, trigger, triggerLocation, token), updateUi: false);
- }
-
- var taskId = Interlocked.Increment(ref _lastFilteringTaskId);
- _computation.Enqueue((model, token) => UpdateSnapshot(model, trigger, FromCompletionTriggerReason(trigger.Reason), triggerLocation, token, taskId), updateUi: true);
- }
-
- internal void InvokeAndCommitIfUnique(CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token)
- {
- if (_isDismissed)
- return;
-
- if (_computation == null)
- {
- // Do not recompute, since this may change the selection.
- ((IAsyncCompletionSession)this).OpenOrUpdate(trigger, triggerLocation);
- }
-
- if (((IAsyncCompletionSession)this).CommitIfUnique(token))
- {
- ((IAsyncCompletionSession)this).Dismiss();
- }
- }
-
- private static CompletionFilterReason FromCompletionTriggerReason(CompletionTriggerReason reason)
- {
- switch (reason)
- {
- case CompletionTriggerReason.Invoke:
- case CompletionTriggerReason.InvokeAndCommitIfUnique:
- return CompletionFilterReason.Initial;
- case CompletionTriggerReason.Insertion:
- return CompletionFilterReason.Insertion;
- case CompletionTriggerReason.Deletion:
- return CompletionFilterReason.Deletion;
- default:
- throw new ArgumentOutOfRangeException(nameof(reason));
- }
- }
-
- #region Internal methods accessed by the command handlers
-
- internal void ToggleSuggestionMode()
- {
- _computation.Enqueue((model, token) => ToggleCompletionModeInner(model, token), updateUi: true);
- }
-
- internal void SelectDown()
- {
- _computation.Enqueue((model, token) => UpdateSelectedItem(model, +1, token), updateUi: true);
- }
-
- internal void SelectPageDown()
- {
- _computation.Enqueue((model, token) => UpdateSelectedItem(model, +PageStepSize, token), updateUi: true);
- }
-
- internal void SelectUp()
- {
- _computation.Enqueue((model, token) => UpdateSelectedItem(model, -1, token), updateUi: true);
- }
-
- internal void SelectPageUp()
- {
- _computation.Enqueue((model, token) => UpdateSelectedItem(model, -PageStepSize, token), updateUi: true);
- }
-
- #endregion
-
- private void OnFiltersChanged(object sender, CompletionFilterChangedEventArgs args)
- {
- var taskId = Interlocked.Increment(ref _lastFilteringTaskId);
- _computation.Enqueue((model, token) => UpdateFilters(model, args.Filters, token, taskId), updateUi: true);
- }
-
- private void OnCommitRequested(object sender, CompletionItemEventArgs args)
- {
- Commit(default(char), args.Item, default(CancellationToken));
- }
-
- private void OnItemSelected(object sender, CompletionItemSelectedEventArgs args)
- {
- // Note 1: Use this only to react to selection changes initiated by user's mouse\touch operation in the UI, since they cancel the soft selection
- // Note 2: we are not enqueuing a call to update the UI, since this would put us in infinite loop, and the UI is already updated
- _computation.Enqueue((model, token) => UpdateSelectedItem(model, args.SelectedItem, args.SuggestionModeSelected, token), updateUi: false);
- }
-
- private void OnGuiClosed(object sender, CompletionClosedEventArgs args)
- {
- Dismiss();
- }
-
- /// <summary>
- /// Determines whether the commit code path should be taken. Since this method is on a typing hot path,
- /// we return quickly if the character is not found in the predefined list of potential commit characters.
- /// Else, we create a mapping point from the top-buffer trigger location to each source's buffer
- /// and return whether any source would like to commit completion.
- /// </summary>
- /// <remarks>This method must run on UI thread because of mapping the point across buffers.</remarks>
- bool IAsyncCompletionSession.ShouldCommit(char typeChar, SnapshotPoint triggerLocation)
- {
- if (!_jtf.Context.IsOnMainThread)
- throw new InvalidOperationException($"This method must be callled on the UI thread.");
-
- if (_potentialCommitChars.Contains(typeChar))
- {
- var mappingPoint = _textView.BufferGraph.CreateMappingPoint(triggerLocation, PointTrackingMode.Negative);
- return _completionSourcesWhoGaveItems
- .Select(p => (p, mappingPoint.GetPoint(p.Value.Snapshot.TextBuffer, PositionAffinity.Predecessor)))
- .Where(n => n.Item2.HasValue)
- .Any(n => _guardedOperations.CallExtensionPoint(
- errorSource: n.Item1.Key,
- call: () => n.Item1.Key.ShouldCommitCompletion(typeChar, n.Item2.Value),
- valueOnThrow: false));
- }
- return false;
- }
-
- /// <summary>
- /// Monitors when user scrolled outside of the applicable span. Note that:
- /// * This event is not raised during regular typing.
- /// * This event is raised by brace completion.
- /// * Typing stretches the applicable span
- /// </summary>
- private void OnCaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
- {
- // http://source.roslyn.io/#Microsoft.CodeAnalysis.EditorFeatures/Implementation/IntelliSense/Completion/Controller_CaretPositionChanged.cs,40
- if (_ignoreCaretMovement)
- return;
-
- _computation.Enqueue((model, token) => HandleCaretPositionChanged(model, e.NewPosition), updateUi: true);
- }
-
- internal void IgnoreCaretMovement(bool ignore)
- {
- _ignoreCaretMovement = ignore;
- if (!ignore)
- {
- // Don't let the session exist in invalid state: ensure that the location of the session is still valid
- _computation?.Enqueue((model, token) => HandleCaretPositionChanged(model, _textView.Caret.Position), updateUi: true);
- }
- }
-
- async Task ICompletionComputationCallbackHandler<CompletionModel>.UpdateUi(CompletionModel model)
- {
- if (_presenterProvider == null) return;
- await _jtf.SwitchToMainThreadAsync();
- UpdateUiInner(model);
- await TaskScheduler.Default;
- }
-
- /// <summary>
- /// Opens or updates the UI. Must be called on UI thread.
- /// </summary>
- /// <param name="model"></param>
- private void UpdateUiInner(CompletionModel model)
- {
- if (_isDismissed)
- return;
- // TODO: Consider building CompletionPresentationViewModel in BG and passing it here
-
- UiStopwatch.Restart();
- if (_gui == null)
- {
- _gui = _guardedOperations.CallExtensionPoint(errorSource: _presenterProvider, call: () => _presenterProvider.GetOrCreate(_textView), valueOnThrow: null);
- if (_gui != null)
- {
- _guardedOperations.CallExtensionPoint(
- errorSource: _gui,
- call: () =>
- {
- _gui = _presenterProvider.GetOrCreate(_textView);
- _gui.Open(new CompletionPresentationViewModel(model.PresentedItems, model.Filters, model.ApplicableSpan, model.UseSoftSelection,
- model.DisplaySuggestionMode, model.SelectSuggestionMode, model.SelectedIndex, model.SuggestionModeItem, model.SuggestionModeDescription));
- _gui.FiltersChanged += OnFiltersChanged;
- _gui.CommitRequested += OnCommitRequested;
- _gui.CompletionItemSelected += OnItemSelected;
- _gui.CompletionClosed += OnGuiClosed;
- });
- }
- }
- else
- {
- _guardedOperations.CallExtensionPoint(
- errorSource: _gui,
- call: () => _gui.Update(new CompletionPresentationViewModel(model.PresentedItems, model.Filters, model.ApplicableSpan, model.UseSoftSelection,
- model.DisplaySuggestionMode, model.SelectSuggestionMode, model.SelectedIndex, model.SuggestionModeItem, model.SuggestionModeDescription)));
- }
- UiStopwatch.Stop();
- _telemetry.RecordRendering(UiStopwatch.ElapsedMilliseconds);
- }
-
- /// <summary>
- /// Creates a new model and populates it with initial data
- /// </summary>
- private async Task<CompletionModel> GetInitialModel(ITextView view, CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token)
- {
- // Map the trigger location to respective view for each completion provider
- var getCompletionTasks = new Task<CompletionContext>[_completionSources.Count];
- for (int i = 0; i < _completionSources.Count; i++)
- {
- var index = i; // Capture current value of i
- getCompletionTasks[i] = Task.Run(async () =>
- {
- var source = _completionSources[index].Source;
- var point = _completionSources[index].Point;
- var context = await _guardedOperations.CallExtensionPointAsync(
- errorSource: _completionSources[index].Source,
- asyncCall: () => _completionSources[index].Source.GetCompletionContextAsync(trigger, point, _initialApplicableSpan, token),
- valueOnThrow: null
- );
- if (context != null && !context.Items.IsDefaultOrEmpty)
- {
- _completionSourcesWhoGaveItems[source] = point;
- }
- return context;
- });
- }
- var nestedResults = await Task.WhenAll(getCompletionTasks);
- var initialCompletionItems = nestedResults.Where(n => n != null && !n.Items.IsDefaultOrEmpty).SelectMany(n => n.Items).ToImmutableArray();
-
- // Do not continue with empty session
- if (initialCompletionItems.IsEmpty)
- {
- ((IAsyncCompletionSession)this).Dismiss();
- return default(CompletionModel);
- }
-
- var availableFilters = initialCompletionItems
- .SelectMany(n => n.Filters)
- .Distinct()
- .Select(n => new CompletionFilterWithState(n, true))
- .ToImmutableArray();
-
- var applicableSpan = triggerLocation.Snapshot.CreateTrackingSpan(_initialApplicableSpan, SpanTrackingMode.EdgeInclusive);
-
- var useSoftSelection = nestedResults.Any(n => n != null && n.UseSoftSelection);
- var useSuggestionMode = nestedResults.Any(n => n != null && n.UseSuggestionMode);
- var suggestionModeDescription = nestedResults.FirstOrDefault(n => !string.IsNullOrEmpty(n?.SuggestionModeDescription))?.SuggestionModeDescription ?? string.Empty;
-
-#if DEBUG
- Debug.WriteLine("Completion session got data.");
- Debug.WriteLine("Sources: " + String.Join(", ", _completionSources.Select(n => n.Source.GetType())));
- Debug.WriteLine("Service: " + _completionService.GetType());
- Debug.WriteLine("Filters: " + String.Join(", ", availableFilters.Select(n => n.Filter.DisplayText)));
- Debug.WriteLine("Span: " + _initialApplicableSpan.GetText());
-#endif
-
- ComputationStopwatch.Restart();
- var sortedList = await _guardedOperations.CallExtensionPointAsync(
- errorSource: _completionService,
- asyncCall: () => _completionService.SortCompletionListAsync(initialCompletionItems, trigger.Reason, triggerLocation.Snapshot, applicableSpan, _textView, token),
- valueOnThrow: initialCompletionItems);
- ComputationStopwatch.Stop();
- _telemetry.RecordProcessing(ComputationStopwatch.ElapsedMilliseconds, initialCompletionItems.Length);
- _telemetry.RecordKeystroke();
-
- return new CompletionModel(initialCompletionItems, sortedList, applicableSpan, trigger.Reason, triggerLocation.Snapshot,
- availableFilters, useSoftSelection, useSuggestionMode, suggestionModeDescription, suggestionModeItem: null);
- }
-
- /// <summary>
- /// User has moved the caret. Ensure that the caret is still within the applicable span. If not, dismiss the session.
- /// </summary>
- /// <returns></returns>
- private Task<CompletionModel> HandleCaretPositionChanged(CompletionModel model, CaretPosition caretPosition)
- {
- if (!(model.ApplicableSpan.GetSpan(caretPosition.VirtualBufferPosition.Position.Snapshot).IntersectsWith(new SnapshotSpan(caretPosition.VirtualBufferPosition.Position, 0))))
- {
- ((IAsyncCompletionSession)this).Dismiss();
- }
- return Task.FromResult(model);
- }
-
- /// <summary>
- /// TODO what is this?
- /// </summary>
- /// <returns></returns>
- private Task<CompletionModel> ToggleCompletionModeInner(CompletionModel model, CancellationToken token)
- {
- return Task.FromResult(model.WithSuggestionModeActive(!model.DisplaySuggestionMode));
- }
-
- /// <summary>
- /// User has typed. Update the known snapshot, filter the items and update the model.
- /// </summary>
- private async Task<CompletionModel> UpdateSnapshot(CompletionModel model, CompletionTrigger trigger, CompletionFilterReason filterReason, SnapshotPoint triggerLocation, CancellationToken token, int thisId)
- {
- // Always record keystrokes, even if filtering is preempted
- _telemetry.RecordKeystroke();
-
- // Completion got cancelled
- if (token.IsCancellationRequested || model == null)
- return default(CompletionModel);
-
- // Dismiss if we are outside of the applicable span
- var currentlyApplicableSpan = model.ApplicableSpan.GetSpan(triggerLocation.Snapshot);
- if (triggerLocation < currentlyApplicableSpan.Start
- || triggerLocation > currentlyApplicableSpan.End)
- {
- ((IAsyncCompletionSession)this).Dismiss();
- return model;
- }
- // Record the first time the span is empty. If it is empty the second time we're here, and user is deleting, then dismiss
- if (currentlyApplicableSpan.IsEmpty && model.ApplicableSpanWasEmpty && trigger.Reason == CompletionTriggerReason.Deletion)
- {
- ((IAsyncCompletionSession)this).Dismiss();
- return model;
- }
- model = model.WithApplicableSpanEmptyRecord(currentlyApplicableSpan.IsEmpty);
-
- // Filtering got preempted, so store the most recent snapshot for the next time we filter
- if (thisId != _lastFilteringTaskId)
- return model.WithSnapshot(triggerLocation.Snapshot);
-
- ComputationStopwatch.Restart();
-
- var filteredCompletion = await _guardedOperations.CallExtensionPointAsync(
- errorSource: _completionService,
- asyncCall: () => _completionService.UpdateCompletionListAsync(
- model.InitialItems,
- model.InitialTriggerReason,
- filterReason,
- triggerLocation.Snapshot, // used exclusively to resolve applicable span
- model.ApplicableSpan,
- model.Filters,
- _textView, // Roslyn doesn't need it, and likely, can't use it
- token),
- valueOnThrow: null);
-
- // Handle error cases by logging the issue and dismissing the session.
- if (filteredCompletion == null)
- {
- ((IAsyncCompletionSession)this).Dismiss();
- return model;
- }
-
- // Special experience when there are no more selected items:
- ImmutableArray<CompletionItemWithHighlight> returnedItems;
- int selectedIndex = filteredCompletion.SelectedItemIndex;
- if (filteredCompletion.Items.IsDefault)
- {
- // Prevent null references when service returns default(ImmutableArray)
- returnedItems = ImmutableArray<CompletionItemWithHighlight>.Empty;
- }
- else if (filteredCompletion.Items.IsEmpty)
- {
- // If there are no results now, show previously visible results, but without highlighting
- if (model.PresentedItems.IsDefaultOrEmpty)
- {
- returnedItems = ImmutableArray<CompletionItemWithHighlight>.Empty;
- }
- else
- {
- returnedItems = model.PresentedItems.Select(n => new CompletionItemWithHighlight(n.CompletionItem)).ToImmutableArray();
- _selectionModeBeforeNoResultFallback = model.UseSoftSelection;
- selectedIndex = model.SelectedIndex;
- _inNoResultFallback = true;
- model = model.WithSoftSelection(true);
- }
- }
- else
- {
- if (_inNoResultFallback)
- {
- model = model.WithSoftSelection(_selectionModeBeforeNoResultFallback);
- _inNoResultFallback = false;
- }
- returnedItems = filteredCompletion.Items;
- }
-
- ComputationStopwatch.Stop();
- _telemetry.RecordProcessing(ComputationStopwatch.ElapsedMilliseconds, returnedItems.Length);
-
- if (filteredCompletion.SelectionMode == CompletionItemSelection.SoftSelected)
- model = model.WithSoftSelection(true);
- else if (filteredCompletion.SelectionMode == CompletionItemSelection.Selected)
- model = model.WithSoftSelection(false);
-
- // Prepare the suggestionModeItem if we ever change the mode
- var enteredText = currentlyApplicableSpan.GetText();
- var suggestionModeItem = new CompletionItem(enteredText, SuggestionModeCompletionItemSource.Instance);
-
- _guardedOperations.RaiseEvent(this, ItemsUpdated, new CompletionItemsWithHighlightEventArgs(returnedItems));
-
- return model.WithSnapshotAndItems(triggerLocation.Snapshot, returnedItems, selectedIndex, filteredCompletion.UniqueItem, suggestionModeItem);
- }
-
- /// <summary>
- /// Reacts to user toggling a filter
- /// </summary>
- /// <param name="newFilters">Filters with updated Selected state, as indicated by the user.</param>
- private async Task<CompletionModel> UpdateFilters(CompletionModel model, ImmutableArray<CompletionFilterWithState> newFilters, CancellationToken token, int thisId)
- {
- _telemetry.RecordChangingFilters();
- _telemetry.RecordKeystroke();
-
- // Filtering got preempted, so store the most updated filters for the next time we filter
- if (token.IsCancellationRequested || thisId != _lastFilteringTaskId)
- return model.WithFilters(newFilters);
-
- var filteredCompletion = await _guardedOperations.CallExtensionPointAsync(
- errorSource: _completionService,
- asyncCall: () => _completionService.UpdateCompletionListAsync(
- model.InitialItems,
- model.InitialTriggerReason,
- CompletionFilterReason.FilterChange,
- model.Snapshot,
- model.ApplicableSpan,
- newFilters,
- _textView,
- token),
- valueOnThrow: null);
-
- // Handle error cases by logging the issue and discarding the request to filter
- if (filteredCompletion == null)
- return model;
- if (filteredCompletion.Filters.Length != newFilters.Length)
- {
- _guardedOperations.HandleException(
- errorSource: _completionService,
- e: new InvalidOperationException("Completion service returned incorrect set of filters."));
- return model;
- }
-
- return model.WithFilters(filteredCompletion.Filters).WithPresentedItems(filteredCompletion.Items, filteredCompletion.SelectedItemIndex);
- }
-
-#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
- /// <summary>
- /// Reacts to user scrolling the list using keyboard
- /// </summary>
- private async Task<CompletionModel> UpdateSelectedItem(CompletionModel model, int offset, CancellationToken token)
-#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
- {
- _telemetry.RecordScrolling();
- _telemetry.RecordKeystroke();
-
- if (!model.PresentedItems.Any())
- {
- // No-op if there are no items
- if (model.DisplaySuggestionMode)
- {
- // Unless there is a suggestion mode item. Select it.
- return model.WithSuggestionItemSelected();
- }
- return model;
- }
-
- var lastIndex = model.PresentedItems.Count() - 1;
- var currentIndex = model.SelectSuggestionMode ? -1 : model.SelectedIndex;
-
- if (offset > 0) // Scrolling down. Stop at last index then go to first index.
- {
- if (currentIndex == lastIndex)
- {
- if (model.DisplaySuggestionMode)
- return model.WithSuggestionItemSelected();
- else
- return model.WithSelectedIndex(FirstIndex);
- }
- var newIndex = currentIndex + offset;
- return model.WithSelectedIndex(Math.Min(newIndex, lastIndex));
- }
- else // Scrolling up. Stop at first index then go to last index.
- {
- if (currentIndex < FirstIndex)
- {
- // Suggestion mode item is selected. Go to the last item.
- return model.WithSelectedIndex(lastIndex);
- }
- else if (currentIndex == FirstIndex)
- {
- // The first item is selected. If there is a suggestion, select it.
- if (model.DisplaySuggestionMode)
- return model.WithSuggestionItemSelected();
- else
- return model.WithSelectedIndex(lastIndex);
- }
- var newIndex = currentIndex + offset;
- return model.WithSelectedIndex(Math.Max(newIndex, FirstIndex));
- }
- }
-
-#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
- /// <summary>
- /// Reacts to user selecting a specific item in the list
- /// </summary>
- private async Task<CompletionModel> UpdateSelectedItem(CompletionModel model, CompletionItem selectedItem, bool suggestionModeSelected, CancellationToken token)
-#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
- {
- _telemetry.RecordScrolling();
- if (suggestionModeSelected)
- {
- return model.WithSuggestionItemSelected();
- }
- else
- {
- for (int i = 0; i < model.PresentedItems.Length; i++)
- {
- if (model.PresentedItems[i].CompletionItem == selectedItem)
- {
- return model.WithSelectedIndex(i);
- }
- }
- // This item is not in the model
- return model;
- }
- }
-
- public ImmutableArray<CompletionItem> GetVisibleItems(CancellationToken token)
- {
- // TODO: Consider returning array of CompletionItemWithHighlight so that we don't do linq here
- return _computation.RecentModel.PresentedItems.Select(n => n.CompletionItem).ToImmutableArray();
- }
-
- public CompletionItem GetSelectedItem(CancellationToken token)
- {
- var model = _computation.RecentModel;
- if (model.SelectSuggestionMode)
- return model.SuggestionModeItem;
- else
- return model.PresentedItems[model.SelectedIndex].CompletionItem;
- }
- }
-}
diff --git a/src/Language/Impl/Language/Completion/CompletionCommandHandlers.cs b/src/Language/Impl/Language/Completion/CompletionCommandHandlers.cs
deleted file mode 100644
index cdd8b07..0000000
--- a/src/Language/Impl/Language/Completion/CompletionCommandHandlers.cs
+++ /dev/null
@@ -1,431 +0,0 @@
-using System;
-using System.ComponentModel.Composition;
-using Microsoft.VisualStudio.Commanding;
-using Microsoft.VisualStudio.Text;
-using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
-using Microsoft.VisualStudio.Text.Operations;
-using Microsoft.VisualStudio.Text.Utilities;
-using Microsoft.VisualStudio.Utilities;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
-{
- /// <summary>
- /// Reacts to the down arrow command and attempts to scroll the completion list.
- /// </summary>
- [Name(KnownCompletionNames.CompletionCommandHandlers)]
- [ContentType("text")]
- [Export(typeof(ICommandHandler))]
- internal sealed class CompletionCommandHandlers :
- ICommandHandler<DownKeyCommandArgs>,
- ICommandHandler<PageDownKeyCommandArgs>,
- ICommandHandler<PageUpKeyCommandArgs>,
- ICommandHandler<UpKeyCommandArgs>,
- IChainedCommandHandler<BackspaceKeyCommandArgs>,
- ICommandHandler<EscapeKeyCommandArgs>,
- ICommandHandler<InvokeCompletionListCommandArgs>,
- ICommandHandler<CommitUniqueCompletionListItemCommandArgs>,
- ICommandHandler<InsertSnippetCommandArgs>,
- ICommandHandler<ToggleCompletionModeCommandArgs>,
- IChainedCommandHandler<DeleteKeyCommandArgs>,
- ICommandHandler<WordDeleteToEndCommandArgs>,
- ICommandHandler<WordDeleteToStartCommandArgs>,
- ICommandHandler<ReturnKeyCommandArgs>,
- ICommandHandler<RedoCommandArgs>,
- ICommandHandler<TabKeyCommandArgs>,
- IChainedCommandHandler<TypeCharCommandArgs>,
- ICommandHandler<UndoCommandArgs>
- {
- [Import]
- IAsyncCompletionBroker Broker;
-
- [Import]
- IExperimentationServiceInternal ExperimentationService;
-
- [Import]
- ITextUndoHistoryRegistry UndoHistoryRegistry;
-
- [Import]
- IEditorOperationsFactoryService EditorOperationsFactoryService;
-
- string INamed.DisplayName => Strings.CompletionCommandHandlerName;
-
- /// <summary>
- /// Helper method that returns command state for commands
- /// that are always available - unless the completion feature is available.
- /// </summary>
- /// <param name="view"></param>
- /// <returns></returns>
- private CommandState Available(IContentType contentType)
- {
- return ModernCompletionFeature.GetFeatureState(ExperimentationService)
- && Broker.IsCompletionSupported(contentType)
- ? CommandState.Available
- : CommandState.Unspecified;
- }
-
- /// <summary>
- /// Helper method that returns command state for commands
- /// that are available when completion is active.
- /// </summary>
- /// <remarks>
- /// Completion might be active only if the feature is available, so we're skipping other checks.
- /// </remarks>
- private CommandState AvailableIfCompletionIsUp(ITextView view)
- {
- return Broker.IsCompletionActive(view)
- ? CommandState.Available
- : CommandState.Unspecified;
- }
-
- CommandState IChainedCommandHandler<BackspaceKeyCommandArgs>.GetCommandState(BackspaceKeyCommandArgs args, Func<CommandState> nextCommandHandler)
- => AvailableIfCompletionIsUp(args.TextView);
-
- void IChainedCommandHandler<BackspaceKeyCommandArgs>.ExecuteCommand(BackspaceKeyCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
- {
- // Execute other commands in the chain to see the change in the buffer.
- nextCommandHandler();
-
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- var trigger = new CompletionTrigger(CompletionTriggerReason.Deletion);
- var location = args.TextView.Caret.Position.BufferPosition;
- session.OpenOrUpdate(trigger, location);
- }
- }
-
- CommandState ICommandHandler<EscapeKeyCommandArgs>.GetCommandState(EscapeKeyCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<EscapeKeyCommandArgs>.ExecuteCommand(EscapeKeyCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- session.Dismiss();
- return true;
- }
- return false;
- }
-
- CommandState ICommandHandler<InvokeCompletionListCommandArgs>.GetCommandState(InvokeCompletionListCommandArgs args)
- => Available(args.SubjectBuffer.ContentType);
-
- bool ICommandHandler<InvokeCompletionListCommandArgs>.ExecuteCommand(InvokeCompletionListCommandArgs args, CommandExecutionContext executionContext)
- {
- // If the caret is buried in virtual space, we should realize this virtual space before triggering the session.
- if (args.TextView.Caret.InVirtualSpace)
- {
- IEditorOperations editorOperations = EditorOperationsFactoryService.GetEditorOperations(args.TextView);
- // We can realize virtual space by inserting nothing through the editor operations.
- editorOperations?.InsertText("");
- }
-
- var trigger = new CompletionTrigger(CompletionTriggerReason.Invoke);
- var location = args.TextView.Caret.Position.BufferPosition;
- var session = Broker.TriggerCompletion(args.TextView, location, default(char));
- if (session != null)
- {
- session.OpenOrUpdate(trigger, location);
- return true;
- }
- return false;
- }
-
- CommandState ICommandHandler<CommitUniqueCompletionListItemCommandArgs>.GetCommandState(CommitUniqueCompletionListItemCommandArgs args)
- => Available(args.SubjectBuffer.ContentType);
-
- bool ICommandHandler<CommitUniqueCompletionListItemCommandArgs>.ExecuteCommand(CommitUniqueCompletionListItemCommandArgs args, CommandExecutionContext executionContext)
- {
- // If the caret is buried in virtual space, we should realize this virtual space before triggering the session.
- if (args.TextView.Caret.InVirtualSpace)
- {
- IEditorOperations editorOperations = EditorOperationsFactoryService.GetEditorOperations(args.TextView);
- // We can realize virtual space by inserting nothing through the editor operations.
- editorOperations?.InsertText("");
- }
-
- var trigger = new CompletionTrigger(CompletionTriggerReason.InvokeAndCommitIfUnique);
- var location = args.TextView.Caret.Position.BufferPosition;
- var session = Broker.TriggerCompletion(args.TextView, location, default(char));
- if (session != null)
- {
- var sessionInternal = session as AsyncCompletionSession;
- sessionInternal?.InvokeAndCommitIfUnique(trigger, location, executionContext.OperationContext.UserCancellationToken);
- return true;
- }
- return false;
- }
-
- CommandState ICommandHandler<InsertSnippetCommandArgs>.GetCommandState(InsertSnippetCommandArgs args)
- => Available(args.SubjectBuffer.ContentType);
-
- bool ICommandHandler<InsertSnippetCommandArgs>.ExecuteCommand(InsertSnippetCommandArgs args, CommandExecutionContext executionContext)
- {
- System.Diagnostics.Debug.WriteLine("!!!! InsertSnippetCommandArgs");
- return false;
- }
-
- CommandState ICommandHandler<ToggleCompletionModeCommandArgs>.GetCommandState(ToggleCompletionModeCommandArgs args)
- => Available(args.SubjectBuffer.ContentType);
-
- bool ICommandHandler<ToggleCompletionModeCommandArgs>.ExecuteCommand(ToggleCompletionModeCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView) as AsyncCompletionSession; // we are accessing an internal method
- if (session != null)
- {
- session.ToggleSuggestionMode();
- return true; // TODO: Investigate. If we return false, we get called again. No matter what we return, the button in the UI does not update.
- }
- return false;
- }
-
- CommandState IChainedCommandHandler<DeleteKeyCommandArgs>.GetCommandState(DeleteKeyCommandArgs args, Func<CommandState> nextCommandHandler)
- => AvailableIfCompletionIsUp(args.TextView);
-
- void IChainedCommandHandler<DeleteKeyCommandArgs>.ExecuteCommand(DeleteKeyCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
- {
- // Execute other commands in the chain to see the change in the buffer.
- nextCommandHandler();
-
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- var trigger = new CompletionTrigger(CompletionTriggerReason.Deletion);
- var location = args.TextView.Caret.Position.BufferPosition;
- session.OpenOrUpdate(trigger, location);
- }
- }
-
- CommandState ICommandHandler<WordDeleteToEndCommandArgs>.GetCommandState(WordDeleteToEndCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<WordDeleteToEndCommandArgs>.ExecuteCommand(WordDeleteToEndCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- session.Dismiss();
- return false; // return false so that the editor can handle this event
- }
- return false;
- }
-
- CommandState ICommandHandler<WordDeleteToStartCommandArgs>.GetCommandState(WordDeleteToStartCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<WordDeleteToStartCommandArgs>.ExecuteCommand(WordDeleteToStartCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- session.Dismiss();
- return false; // return false so that the editor can handle this event
- }
- return false;
- }
-
- CommandState ICommandHandler<RedoCommandArgs>.GetCommandState(RedoCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<RedoCommandArgs>.ExecuteCommand(RedoCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- session.Dismiss();
- return false; // return false so that the editor can handle this event
- }
- return false;
- }
-
- CommandState ICommandHandler<ReturnKeyCommandArgs>.GetCommandState(ReturnKeyCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<ReturnKeyCommandArgs>.ExecuteCommand(ReturnKeyCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- var commitBehavior = session.Commit(executionContext.OperationContext.UserCancellationToken, '\n');
- session.Dismiss();
-
- // Mark this command as handled (return true), unless extender set the RaiseFurtherCommandHandlers flag.
- if ((commitBehavior & CommitBehavior.RaiseFurtherCommandHandlers) == 0)
- return true;
- }
- return false;
- }
-
- CommandState ICommandHandler<TabKeyCommandArgs>.GetCommandState(TabKeyCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<TabKeyCommandArgs>.ExecuteCommand(TabKeyCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- var commitBehavior = session.Commit(executionContext.OperationContext.UserCancellationToken, '\t');
- session.Dismiss();
-
- // Mark this command as handled (return true), unless extender set the RaiseFurtherCommandHandlers flag.
- if ((commitBehavior & CommitBehavior.RaiseFurtherCommandHandlers) == 0)
- return true;
- }
- return false;
- }
-
- CommandState IChainedCommandHandler<TypeCharCommandArgs>.GetCommandState(TypeCharCommandArgs args, Func<CommandState> nextCommandHandler)
- => Available(args.SubjectBuffer.ContentType);
-
- void IChainedCommandHandler<TypeCharCommandArgs>.ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
- {
- var initialTextSnapshot = args.TextView.TextSnapshot;
- var view = args.TextView;
- var location = view.Caret.Position.BufferPosition;
-
- // Note regarding undo: When completion and brace completion happen together, completion should be first on the undo stack.
- // Effectively, we want to first undo the completion, leaving brace completion intact. Second undo should undo brace completion.
- // To achieve this, we create a transaction in which we commit and reapply brace completion (via nextCommandHandler).
- // Please read "Note regarding undo" comments in this method that explain the implementation choices.
- // Hopefully an upcoming upgrade of the undo mechanism will allow us to undo out of order and vastly simplify this method.
-
- // Note regarding undo: In a corner case of typing closing brace over existing closing brace,
- // Roslyn brace completion does not perform an edit. It moves the caret outside of session's applicable span,
- // which dismisses the session. Put the session in a state where it will not dismiss when caret leaves the applicable span.
- /*
- // Unfortunately, preserving this session means that ultimately replaying the key will insert second brace instead of overtyping.
- // Actually, even obtaining session before nextCommandHandler messes this up. I don't understand this yet and for now I will keep this code commented out. Completing over closing brace is now a known bug.
- if (sessionToCommit != null && args.TextView.TextBuffer == args.SubjectBuffer)
- {
- ((AsyncCompletionSession)sessionToCommit).IgnoreCaretMovement(ignore: true);
- }
- */
- // Execute other commands in the chain to see the change in the buffer. This includes brace completion.
- // Note regarding undo: This will be undone second
- nextCommandHandler();
-
- // Pass location from before calling nextCommandHandler so that extenders
- // get the same view of the buffer in both ShouldCommit and Commit
- var sessionToCommit = Broker.GetSession(args.TextView);
- if (sessionToCommit?.ShouldCommit(args.TypedChar, location) == true)
- {
- // Buffer has changed, update the snapshot
- location = view.Caret.Position.BufferPosition;
-
- // Note regarding undo: this transaction will be undone first
- using (var undoTransaction = new CaretPreservingEditTransaction("Completion", view, UndoHistoryRegistry, EditorOperationsFactoryService))
- {
- UndoUtilities.RollbackToBeforeTypeChar(initialTextSnapshot, args.SubjectBuffer);
- // Now the buffer doesn't have the commit character nor the matching brace, if any
-
- var commitBehavior = sessionToCommit.Commit(executionContext.OperationContext.UserCancellationToken, args.TypedChar);
-
- if ((commitBehavior & CommitBehavior.SuppressFurtherCommandHandlers) == 0)
- nextCommandHandler(); // Replay the key, so that we get brace completion.
-
- // Complete the transaction before stopping it.
- undoTransaction.Complete();
- }
- }
- /*
- if (sessionToCommit != null)
- {
- ((AsyncCompletionSession)sessionToCommit).IgnoreCaretMovement(ignore: false);
- }
- */
- // Buffer might have changed. Update it for when we try to trigger new session.
- location = view.Caret.Position.BufferPosition;
-
- var trigger = new CompletionTrigger(CompletionTriggerReason.Insertion, args.TypedChar);
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- session.OpenOrUpdate(trigger, location);
- }
- else
- {
- var newSession = Broker.TriggerCompletion(args.TextView, location, args.TypedChar);
- if (newSession != null)
- {
- newSession?.OpenOrUpdate(trigger, location);
- }
- }
- }
-
- CommandState ICommandHandler<UndoCommandArgs>.GetCommandState(UndoCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<UndoCommandArgs>.ExecuteCommand(UndoCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView);
- if (session != null)
- {
- session.Dismiss();
- return false; // return false so that the editor can handle this event
- }
- return false;
- }
-
- CommandState ICommandHandler<DownKeyCommandArgs>.GetCommandState(DownKeyCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<DownKeyCommandArgs>.ExecuteCommand(DownKeyCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView) as AsyncCompletionSession; // we are accessing an internal method
- if (session != null)
- {
- session.SelectDown();
- System.Diagnostics.Debug.WriteLine("Completions's DownKey command handler returns true (handled)");
- return true;
- }
- System.Diagnostics.Debug.WriteLine("Completions's DownKey command handler returns false (unhandled)");
- return false;
- }
-
- CommandState ICommandHandler<PageDownKeyCommandArgs>.GetCommandState(PageDownKeyCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<PageDownKeyCommandArgs>.ExecuteCommand(PageDownKeyCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView) as AsyncCompletionSession; // we are accessing an internal method
- if (session != null)
- {
- session.SelectPageDown();
- return true;
- }
- return false;
- }
-
- CommandState ICommandHandler<PageUpKeyCommandArgs>.GetCommandState(PageUpKeyCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<PageUpKeyCommandArgs>.ExecuteCommand(PageUpKeyCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView) as AsyncCompletionSession; // we are accessing an internal method
- if (session != null)
- {
- session.SelectPageUp();
- return true;
- }
- return false;
- }
-
- CommandState ICommandHandler<UpKeyCommandArgs>.GetCommandState(UpKeyCommandArgs args)
- => AvailableIfCompletionIsUp(args.TextView);
-
- bool ICommandHandler<UpKeyCommandArgs>.ExecuteCommand(UpKeyCommandArgs args, CommandExecutionContext executionContext)
- {
- var session = Broker.GetSession(args.TextView) as AsyncCompletionSession; // we are accessing an internal method
- if (session != null)
- {
- session.SelectUp();
- System.Diagnostics.Debug.WriteLine("Completions's UpKey command handler returns true (handled)");
- return true;
- }
- System.Diagnostics.Debug.WriteLine("Completions's UpKey command handler returns false (unhandled)");
- return false;
- }
- }
-}
diff --git a/src/Language/Impl/Language/Completion/CompletionModel.cs b/src/Language/Impl/Language/Completion/CompletionModel.cs
deleted file mode 100644
index 84d18f3..0000000
--- a/src/Language/Impl/Language/Completion/CompletionModel.cs
+++ /dev/null
@@ -1,461 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.VisualStudio.Language.Intellisense;
-using Microsoft.VisualStudio.Text;
-using Microsoft.VisualStudio.Utilities;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
-{
- sealed class CompletionModel
- {
- /// <summary>
- /// All items, as provided by completion item sources.
- /// </summary>
- public readonly ImmutableArray<CompletionItem> InitialItems;
-
- /// <summary>
- /// Sorted array of all items, as provided by the completion service.
- /// </summary>
- public readonly ImmutableArray<CompletionItem> SortedItems;
-
- /// <summary>
- /// Span pertinent to this completion model.
- /// </summary>
- public readonly ITrackingSpan ApplicableSpan;
-
- /// <summary>
- /// Snapshot pertinent to this completion model.
- /// </summary>
- public readonly ITextSnapshot Snapshot;
-
- /// <summary>
- /// Stores the initial reason this session was triggererd.
- /// </summary>
- public readonly CompletionTriggerReason InitialTriggerReason;
-
- /// <summary>
- /// Filters involved in this completion model, including their availability and selection state.
- /// </summary>
- public readonly ImmutableArray<CompletionFilterWithState> Filters;
-
- /// <summary>
- /// Items to be displayed in the UI.
- /// </summary>
- public readonly ImmutableArray<CompletionItemWithHighlight> PresentedItems;
-
- /// <summary>
- /// Index of item to select. Use -1 to select nothing, when suggestion mode item should be selected.
- /// </summary>
- public readonly int SelectedIndex;
-
- /// <summary>
- /// Whether selection should be displayed as soft selection.
- /// </summary>
- public readonly bool UseSoftSelection;
-
- /// <summary>
- /// Whether suggestion mode item should be visible.
- /// </summary>
- public readonly bool DisplaySuggestionMode;
-
- /// <summary>
- /// Whether suggestion mode item should be selected.
- /// </summary>
- public readonly bool SelectSuggestionMode;
-
- /// <summary>
- /// <see cref="CompletionItem"/> which contains user-entered text.
- /// Used to display and commit the suggestion mode item
- /// </summary>
- public readonly CompletionItem SuggestionModeItem;
-
- /// <summary>
- /// Text to display in place of suggestion mode when filtered text is empty.
- /// </summary>
- public readonly string SuggestionModeDescription;
-
- /// <summary>
- /// <see cref="CompletionItem"/> which overrides regular unique item selection.
- /// When this is null, the single item from <see cref="PresentedItems"/> is used as unique item.
- /// </summary>
- public readonly CompletionItem UniqueItem;
-
- /// <summary>
- /// When completion starts, its <see cref="ApplicableSpan"/> may be empty. Initially, don't dismiss.
- /// Further, the span is empty if user removes characters. We would like to dismiss then.
- /// </summary>
- public readonly bool ApplicableSpanWasEmpty;
-
- /// <summary>
- /// Constructor for the initial model
- /// </summary>
- public CompletionModel(ImmutableArray<CompletionItem> initialItems, ImmutableArray<CompletionItem> sortedItems,
- ITrackingSpan applicableSpan, CompletionTriggerReason initialTriggerReason, ITextSnapshot snapshot,
- ImmutableArray<CompletionFilterWithState> filters, bool useSoftSelection, bool useSuggestionMode, string suggestionModeDescription, CompletionItem suggestionModeItem)
- {
- InitialItems = initialItems;
- SortedItems = sortedItems;
- ApplicableSpan = applicableSpan;
- InitialTriggerReason = initialTriggerReason;
- Snapshot = snapshot;
- Filters = filters;
- SelectedIndex = 0;
- UseSoftSelection = useSoftSelection;
- DisplaySuggestionMode = useSuggestionMode;
- SelectSuggestionMode = useSuggestionMode;
- SuggestionModeDescription = suggestionModeDescription;
- SuggestionModeItem = suggestionModeItem;
- UniqueItem = null;
- ApplicableSpanWasEmpty = false;
- }
-
- /// <summary>
- /// Private constructor for the With* methods
- /// </summary>
- private CompletionModel(ImmutableArray<CompletionItem> initialItems, ImmutableArray<CompletionItem> sortedItems, ITrackingSpan applicableSpan, CompletionTriggerReason initialTriggerReason,
- ITextSnapshot snapshot, ImmutableArray<CompletionFilterWithState> filters, ImmutableArray<CompletionItemWithHighlight> presentedItems, bool useSoftSelection, bool useSuggestionMode,
- string suggestionModeDescription, int selectedIndex, bool selectSuggestionMode, CompletionItem suggestionModeItem, CompletionItem uniqueItem, bool applicableSpanWasEmpty)
- {
- InitialItems = initialItems;
- SortedItems = sortedItems;
- ApplicableSpan = applicableSpan;
- InitialTriggerReason = initialTriggerReason;
- Snapshot = snapshot;
- Filters = filters;
- PresentedItems = presentedItems;
- SelectedIndex = selectedIndex;
- UseSoftSelection = useSoftSelection;
- DisplaySuggestionMode = useSuggestionMode;
- SelectSuggestionMode = selectSuggestionMode;
- SuggestionModeDescription = suggestionModeDescription;
- SuggestionModeItem = suggestionModeItem;
- ApplicableSpanWasEmpty = applicableSpanWasEmpty;
- }
-
- public CompletionModel WithPresentedItems(ImmutableArray<CompletionItemWithHighlight> newPresentedItems, int newSelectedIndex)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: Snapshot,
- filters: Filters,
- presentedItems: newPresentedItems, // Updated
- useSoftSelection: UseSoftSelection,
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: newSelectedIndex, // Updated
- selectSuggestionMode: SelectSuggestionMode,
- suggestionModeItem: SuggestionModeItem,
- uniqueItem: UniqueItem,
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- public CompletionModel WithSnapshot(ITextSnapshot newSnapshot)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: newSnapshot, // Updated
- filters: Filters,
- presentedItems: PresentedItems,
- useSoftSelection: UseSoftSelection,
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: SelectedIndex,
- selectSuggestionMode: SelectSuggestionMode,
- suggestionModeItem: SuggestionModeItem,
- uniqueItem: UniqueItem,
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- public CompletionModel WithFilters(ImmutableArray<CompletionFilterWithState> newFilters)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: Snapshot,
- filters: newFilters, // Updated
- presentedItems: PresentedItems,
- useSoftSelection: UseSoftSelection,
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: SelectedIndex,
- selectSuggestionMode: SelectSuggestionMode,
- suggestionModeItem: SuggestionModeItem,
- uniqueItem: UniqueItem,
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- public CompletionModel WithSelectedIndex(int newIndex)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: Snapshot,
- filters: Filters,
- presentedItems: PresentedItems,
- useSoftSelection: false, // Explicit selection and soft selection are mutually exclusive
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: newIndex, // Updated
- selectSuggestionMode: false, // Explicit selection of regular item
- suggestionModeItem: SuggestionModeItem,
- uniqueItem: UniqueItem,
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- public CompletionModel WithSuggestionItemSelected()
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: Snapshot,
- filters: Filters,
- presentedItems: PresentedItems,
- useSoftSelection: false, // Explicit selection and soft selection are mutually exclusive
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: -1, // Deselect regular item
- selectSuggestionMode: true, // Explicit selection of suggestion item
- suggestionModeItem: SuggestionModeItem,
- uniqueItem: UniqueItem,
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- public CompletionModel WithSuggestionModeActive(bool newUseSuggestionMode)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: Snapshot,
- filters: Filters,
- presentedItems: PresentedItems,
- useSoftSelection: UseSoftSelection | newUseSuggestionMode, // Enabling suggestion mode also enables soft selection
- useSuggestionMode: newUseSuggestionMode, // Updated
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: SelectedIndex,
- selectSuggestionMode: SelectSuggestionMode,
- suggestionModeItem: SuggestionModeItem,
- uniqueItem: UniqueItem,
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- /// <summary>
- /// </summary>
- /// <param name="newSuggestionModeItem">It is ok to pass in null when there is no suggestion. UI will display SuggestsionModeDescription instead.</param>
- internal CompletionModel WithSuggestionModeItem(CompletionItem newSuggestionModeItem)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: Snapshot,
- filters: Filters,
- presentedItems: PresentedItems,
- useSoftSelection: UseSoftSelection,
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: SelectedIndex,
- selectSuggestionMode: SelectSuggestionMode,
- suggestionModeItem: newSuggestionModeItem,
- uniqueItem: UniqueItem,
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- /// <summary>
- /// </summary>
- /// <param name="newUniqueItem">Overrides typical unique item selection.
- /// Pass in null to use regular behavior: treating single <see cref="PresentedItems"/> item as the unique item.</param>
- internal CompletionModel WithUniqueItem(CompletionItem newUniqueItem)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: Snapshot,
- filters: Filters,
- presentedItems: PresentedItems,
- useSoftSelection: UseSoftSelection,
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: SelectedIndex,
- selectSuggestionMode: SelectSuggestionMode,
- suggestionModeItem: SuggestionModeItem,
- uniqueItem: newUniqueItem,
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- internal CompletionModel WithSoftSelection(bool newSoftSelection)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: Snapshot,
- filters: Filters,
- presentedItems: PresentedItems,
- useSoftSelection: newSoftSelection, // Updated
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: SelectedIndex,
- selectSuggestionMode: SelectSuggestionMode,
- suggestionModeItem: SuggestionModeItem,
- uniqueItem: UniqueItem,
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- internal CompletionModel WithSnapshotAndItems(ITextSnapshot snapshot, ImmutableArray<CompletionItemWithHighlight> presentedItems, int selectedIndex, CompletionItem uniqueItem, CompletionItem suggestionModeItem)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: snapshot, // Updated
- filters: Filters,
- presentedItems: presentedItems, // Updated
- useSoftSelection: UseSoftSelection,
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: selectedIndex, // Updated
- selectSuggestionMode: SelectSuggestionMode,
- suggestionModeItem: suggestionModeItem, // Updated
- uniqueItem: uniqueItem, // Updated
- applicableSpanWasEmpty: ApplicableSpanWasEmpty
- );
- }
-
- internal CompletionModel WithApplicableSpanEmptyRecord(bool applicableSpanIsEmpty)
- {
- return new CompletionModel(
- initialItems: InitialItems,
- sortedItems: SortedItems,
- applicableSpan: ApplicableSpan,
- initialTriggerReason: InitialTriggerReason,
- snapshot: Snapshot,
- filters: Filters,
- presentedItems: PresentedItems,
- useSoftSelection: UseSoftSelection,
- useSuggestionMode: DisplaySuggestionMode,
- suggestionModeDescription: SuggestionModeDescription,
- selectedIndex: SelectedIndex,
- selectSuggestionMode: SelectSuggestionMode,
- suggestionModeItem: SuggestionModeItem,
- uniqueItem: UniqueItem,
- applicableSpanWasEmpty: applicableSpanIsEmpty // Updated
- );
- }
- }
-
- sealed class ModelComputation<TModel>
- {
- private Task<TModel> _lastTask = Task.FromResult(default(TModel));
- private Task _notifyUITask = Task.CompletedTask;
- private readonly TaskScheduler _computationTaskScheduler;
- private readonly CancellationToken _token;
- private readonly IGuardedOperations _guardedOperations;
- private CancellationTokenSource _uiCancellation;
- private readonly ICompletionComputationCallbackHandler<TModel> _callbacks;
- internal TModel RecentModel { get; private set; } = default(TModel);
-
- public ModelComputation(TaskScheduler computationTaskScheduler, CancellationToken token, IGuardedOperations guardedOperations, ICompletionComputationCallbackHandler<TModel> callbacks)
- {
- _computationTaskScheduler = computationTaskScheduler;
- _token = token;
- _guardedOperations = guardedOperations;
- _uiCancellation = new CancellationTokenSource();
- _callbacks = callbacks;
- }
-
- private Task<TModel> SafelyInvoke(Func<TModel, CancellationToken, Task<TModel>> transformation, Task<TModel> previousTask, CancellationToken token)
- {
- var transformedTask = transformation(previousTask.Result, token);
- if (transformedTask.IsFaulted)
- {
- _guardedOperations.HandleException(this, transformedTask.Exception);
- _callbacks.Dismiss();
- return previousTask;
- }
- return transformedTask;
- }
-
- /// <summary>
- /// Schedules work to be done on the background,
- /// potentially preempted by another piece of work scheduled in the future,
- /// <paramref name="updateUi" /> indicates whether a single piece of work should occue once all background work is completed.
- /// </summary>
- public void Enqueue(Func<TModel, CancellationToken, Task<TModel>> transformation, bool updateUi)
- {
- // This method is based on Roslyn's ModelComputation.ChainTaskAndNotifyControllerWhenFinished
- var nextTask = _lastTask.ContinueWith(t => SafelyInvoke(transformation, t, _token), _computationTaskScheduler).Unwrap();
- _lastTask = nextTask;
-
- // If the _notifyUITask is canceled, refresh it
- if (_notifyUITask.IsCanceled || _uiCancellation.IsCancellationRequested)
- {
- _notifyUITask = Task.CompletedTask;
- _uiCancellation = new CancellationTokenSource();
- }
-
- _notifyUITask = Task.Factory.ContinueWhenAll(
- new[] { _notifyUITask, nextTask },
- async existingTasks =>
- {
- if (existingTasks.All(t => t.Status == TaskStatus.RanToCompletion))
- {
- OnModelUpdated(nextTask.Result);
- if (updateUi && nextTask == _lastTask)
- {
- await _callbacks.UpdateUi(nextTask.Result);
- }
- }
- },
- _uiCancellation.Token
- );
- }
-
- private void OnModelUpdated(TModel result)
- {
- RecentModel = result;
- }
-
- /// <summary>
- /// Blocks, waiting for all background work to finish.
- /// Ignores the last piece of work a.k.a. "updateUI"
- /// </summary>
- public TModel WaitAndGetResult()
- {
- _uiCancellation.Cancel();
- _lastTask.Wait();
- return _lastTask.Result;
- }
- }
-}
diff --git a/src/Language/Impl/Language/Completion/CompletionTelemetry.cs b/src/Language/Impl/Language/Completion/CompletionTelemetry.cs
deleted file mode 100644
index 35915a3..0000000
--- a/src/Language/Impl/Language/Completion/CompletionTelemetry.cs
+++ /dev/null
@@ -1,239 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.VisualStudio.Text.Utilities;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
-{
- internal class CompletionSessionTelemetry
- {
- private readonly CompletionTelemetryHost _telemetryHost;
-
- // Collected data
- internal string CompletionService { get; private set; }
- internal string CompletionPresenterProvider { get; private set; }
- internal string CompletionSource { get; private set; }
-
- internal long InitialProcessingDuration { get; private set; }
- internal long TotalProcessingDuration { get; private set; }
- internal int TotalProcessingCount { get; private set; }
-
- internal long InitialRenderingDuration { get; private set; }
- internal long TotalRenderingDuration { get; private set; }
- internal int TotalRenderingCount { get; private set; }
-
- internal long CommitDuration { get; private set; }
-
- internal bool UserEverScrolled { get; private set; }
- internal bool UserEverSetFilters { get; private set; }
- internal int FinalItemCount { get; private set; }
- internal int NumberOfKeystrokes { get; private set; }
-
- public CompletionSessionTelemetry(CompletionTelemetryHost telemetryHost, IAsyncCompletionService completionService, ICompletionPresenterProvider presenterProvider)
- {
- _telemetryHost = telemetryHost;
- CompletionService = _telemetryHost.GetCompletionServiceName(completionService);
- CompletionPresenterProvider = _telemetryHost.GetCompletionPresenterProviderName(presenterProvider);
- }
-
- internal void RecordProcessing(long processingTime, int itemCount)
- {
- if (TotalProcessingCount == 0)
- {
- InitialProcessingDuration = processingTime;
- }
- else
- {
- TotalProcessingDuration += processingTime;
- FinalItemCount = itemCount;
- }
- TotalProcessingCount++;
- }
-
- internal void RecordRendering(long processingTime)
- {
- if (TotalRenderingCount == 0)
- InitialRenderingDuration = processingTime;
- TotalRenderingCount++;
- TotalRenderingDuration += processingTime;
- }
-
- internal void RecordScrolling()
- {
- UserEverScrolled = true;
- }
-
- internal void RecordChangingFilters()
- {
- UserEverSetFilters = true;
- }
-
- internal void RecordKeystroke()
- {
- NumberOfKeystrokes++;
- }
-
- internal void RecordCommitted(long commitDuration, CompletionItem committedItem)
- {
- CompletionSource = committedItem.UseCustomCommit ? _telemetryHost.GetItemSourceName(committedItem.Source) : String.Empty;
- CommitDuration = commitDuration;
- _telemetryHost.Add(this);
- }
- }
-
- internal class CompletionTelemetryHost
- {
- private class AggregateSourceData
- {
- internal long TotalCommitTime;
- internal long CommitCount;
- }
-
- private class AggregateServiceData
- {
- internal long TotalProcessTime;
- internal long InitialProcessTime;
- internal int ProcessCount;
- internal int TotalKeystrokes;
- internal int UserEverScrolled;
- internal int UserEverSetFilters;
- internal int FinalItemCount;
- internal int DataCount;
- }
-
- private class AggregatePresenterData
- {
- internal long TotalRenderTime;
- internal long InitialRenderTime;
- internal int RenderCount;
- }
-
- Dictionary<string, AggregateSourceData> SourceData = new Dictionary<string, AggregateSourceData>(4);
- Dictionary<string, AggregateServiceData> ServiceData = new Dictionary<string, AggregateServiceData>(4);
- Dictionary<string, AggregatePresenterData> PresenterData = new Dictionary<string, AggregatePresenterData>(4);
-
- private readonly ILoggingServiceInternal _logger;
- private readonly AsyncCompletionBroker _broker;
-
- public CompletionTelemetryHost(ILoggingServiceInternal logger, AsyncCompletionBroker broker)
- {
- _logger = logger;
- _broker = broker;
- }
-
- internal string GetItemSourceName(IAsyncCompletionItemSource source) => _broker.GetItemSourceName(source);
- internal string GetCompletionServiceName(IAsyncCompletionService service) => _broker.GetCompletionServiceName(service);
- internal string GetCompletionPresenterProviderName(ICompletionPresenterProvider provider) => _broker.GetCompletionPresenterProviderName(provider);
-
- /// <summary>
- /// Adds data from <see cref="CompletionSessionTelemetry" /> to appropriate buckets.
- /// </summary>
- /// <param name=""></param>
- internal void Add(CompletionSessionTelemetry telemetry)
- {
- if (_logger == null)
- return;
-
- var presenterKey = telemetry.CompletionPresenterProvider;
- if (!PresenterData.ContainsKey(presenterKey))
- PresenterData[presenterKey] = new AggregatePresenterData();
- var aggregatePresenterData = PresenterData[presenterKey];
-
- var serviceKey = telemetry.CompletionService;
- if (!ServiceData.ContainsKey(serviceKey))
- ServiceData[serviceKey] = new AggregateServiceData();
- var aggregateServiceData = ServiceData[serviceKey];
-
- var sourceKey = telemetry.CompletionSource;
- if (!SourceData.ContainsKey(sourceKey))
- SourceData[sourceKey] = new AggregateSourceData();
- var aggregateSourceData = SourceData[sourceKey];
-
- aggregatePresenterData.InitialRenderTime += telemetry.InitialRenderingDuration;
- aggregatePresenterData.TotalRenderTime += telemetry.TotalRenderingDuration;
- aggregatePresenterData.RenderCount += telemetry.TotalRenderingCount;
-
- aggregateServiceData.DataCount++;
- aggregateServiceData.FinalItemCount += telemetry.FinalItemCount;
- aggregateServiceData.InitialProcessTime += telemetry.InitialProcessingDuration;
- aggregateServiceData.ProcessCount += telemetry.TotalProcessingCount;
- aggregateServiceData.TotalProcessTime += telemetry.TotalProcessingDuration;
- aggregateServiceData.TotalKeystrokes += telemetry.NumberOfKeystrokes;
- aggregateServiceData.TotalProcessTime += telemetry.TotalProcessingDuration;
- aggregateServiceData.UserEverScrolled += telemetry.UserEverScrolled ? 1 : 0;
- aggregateServiceData.UserEverSetFilters += telemetry.UserEverSetFilters ? 1 : 0;
-
- aggregateSourceData.TotalCommitTime += telemetry.CommitDuration;
- aggregateSourceData.CommitCount++;
- }
-
- /// <summary>
- /// Sends batch of collected data.
- /// </summary>
- internal void Send()
- {
- if (_logger == null)
- return;
-
- foreach (var data in PresenterData)
- {
- if (data.Value.RenderCount == 0)
- continue;
-
- _logger.PostEvent(PresenterEventName,
- (PresenterName, data.Key),
- (PresenterAverageInitialRendering, data.Value.InitialRenderTime / data.Value.RenderCount),
- (PresenterAverageRendering, data.Value.TotalRenderTime / data.Value.RenderCount)
- );
- }
-
- foreach (var data in ServiceData)
- {
- if (data.Value.DataCount == 0)
- continue;
-
- _logger.PostEvent(ServiceEventName,
- (ServiceName, data.Key),
- (ServiceAverageFinalItemCount, data.Value.FinalItemCount / data.Value.DataCount),
- (ServiceAverageInitialProcessTime, data.Value.InitialProcessTime / data.Value.DataCount),
- (ServiceAverageFilterTime, data.Value.TotalProcessTime / data.Value.ProcessCount),
- (ServiceAverageKeystrokeCount, data.Value.TotalKeystrokes / data.Value.DataCount),
- (ServiceAverageScrolled, data.Value.UserEverScrolled / data.Value.DataCount),
- (ServiceAverageSetFilters, data.Value.UserEverSetFilters / data.Value.DataCount)
- );
- }
-
- foreach (var data in SourceData)
- {
- if (data.Value.CommitCount == 0)
- continue;
-
- _logger.PostEvent(SourceEventName,
- (SourceName, data.Key),
- (SourceAverageCommit, data.Value.TotalCommitTime / data.Value.CommitCount)
- );
- }
- }
-
- // Property and event names
- internal const string PresenterEventName = "VS/Editor/Completion/PresenterData";
- internal const string PresenterName = "Property.Rendering.Name";
- internal const string PresenterAverageInitialRendering = "Property.Rendering.InitialDuration";
- internal const string PresenterAverageRendering = "Property.Rendering.AnyDuration";
-
- internal const string ServiceEventName = "VS/Editor/Completion/ServiceData";
- internal const string ServiceName = "Property.Service.Name";
- internal const string ServiceAverageFinalItemCount = "Property.Service.Name";
- internal const string ServiceAverageInitialProcessTime = "Property.Service.Name";
- internal const string ServiceAverageFilterTime = "Property.Service.Name";
- internal const string ServiceAverageKeystrokeCount = "Property.Service.Name";
- internal const string ServiceAverageScrolled = "Property.Service.Name";
- internal const string ServiceAverageSetFilters = "Property.Service.Name";
-
- internal const string SourceEventName = "VS/Editor/Completion/SourceData";
- internal const string SourceName = "Property.Commit.Name";
- internal const string SourceAverageCommit = "Property.Commit.Duration";
- }
-}
diff --git a/src/Language/Impl/Language/Completion/DefaultCompletionService.cs b/src/Language/Impl/Language/Completion/DefaultCompletionService.cs
deleted file mode 100644
index 3cd3479..0000000
--- a/src/Language/Impl/Language/Completion/DefaultCompletionService.cs
+++ /dev/null
@@ -1,283 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.Immutable;
-using System.ComponentModel.Composition;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.VisualStudio.Core.Imaging;
-using Microsoft.VisualStudio.Text;
-using Microsoft.VisualStudio.Text.Editor;
-using Microsoft.VisualStudio.Text.PatternMatching;
-using Microsoft.VisualStudio.Utilities;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
-{
- [Export(typeof(IAsyncCompletionServiceProvider))]
- [Name(KnownCompletionNames.DefaultCompletionService)]
- [ContentType("text")]
- internal class DefaultCompletionServiceProvider : IAsyncCompletionServiceProvider
- {
- [Import]
- public IPatternMatcherFactory PatternMatcherFactory { get; set; }
-
- DefaultCompletionService _instance;
-
- IAsyncCompletionService IAsyncCompletionServiceProvider.GetOrCreate(ITextView textView)
- {
- if (_instance == null)
- _instance = new DefaultCompletionService(PatternMatcherFactory);
- return _instance;
- }
- }
-
- internal class DefaultCompletionService : IAsyncCompletionService
- {
- readonly IPatternMatcherFactory _patternMatcherFactory;
-
- internal DefaultCompletionService(IPatternMatcherFactory patternMatcherFactory)
- {
- _patternMatcherFactory = patternMatcherFactory;
- }
-
- Task<FilteredCompletionModel> IAsyncCompletionService.UpdateCompletionListAsync(
- ImmutableArray<CompletionItem> sortedList, CompletionTriggerReason triggerReason, CompletionFilterReason filterReason,
- ITextSnapshot snapshot, ITrackingSpan applicableSpan, ImmutableArray<CompletionFilterWithState> filters, ITextView view, CancellationToken token)
- {
- // Filter by text
- var filterText = applicableSpan.GetText(snapshot);
- if (string.IsNullOrWhiteSpace(filterText))
- {
- // There is no text filtering. Just apply user filters, sort alphabetically and return.
- IEnumerable<CompletionItem> listFiltered = sortedList;
- if (filters.Any(n => n.IsSelected))
- {
- listFiltered = sortedList.Where(n => ShouldBeInCompletionList(n, filters));
- }
- var listSorted = listFiltered.OrderBy(n => n.SortText);
- var listHighlighted = listSorted.Select(n => new CompletionItemWithHighlight(n)).ToImmutableArray();
- return Task.FromResult(new FilteredCompletionModel(listHighlighted, 0, filters));
- }
-
- // Pattern matcher not only filters, but also provides a way to order the results by their match quality.
- // The relevant CompletionItem is match.Item1, its PatternMatch is match.Item2
- var patternMatcher = _patternMatcherFactory.CreatePatternMatcher(
- filterText,
- new PatternMatcherCreationOptions(System.Globalization.CultureInfo.CurrentCulture, PatternMatcherCreationFlags.IncludeMatchedSpans));
-
- var matches = sortedList
- // Perform pattern matching
- .Select(completionItem => (completionItem, patternMatcher.TryMatch(completionItem.FilterText)))
- // Pick only items that were matched, unless length of filter text is 1
- .Where(n => (filterText.Length == 1 || n.Item2.HasValue));
-
- // See which filters might be enabled based on the typed code
- var textFilteredFilters = matches.SelectMany(n => n.Item1.Filters).Distinct();
-
- // When no items are available for a given filter, it becomes unavailable
- var updatedFilters = ImmutableArray.CreateRange(filters.Select(n => n.WithAvailability(textFilteredFilters.Contains(n.Filter))));
-
- // Filter by user-selected filters. The value on availableFiltersWithSelectionState conveys whether the filter is selected.
- var filterFilteredList = matches;
- if (filters.Any(n => n.IsSelected))
- {
- filterFilteredList = matches.Where(n => ShouldBeInCompletionList(n.Item1, filters));
- }
-
- var bestMatch = filterFilteredList.OrderByDescending(n => n.Item2.HasValue).ThenBy(n => n.Item2).FirstOrDefault();
- var listWithHighlights = filterFilteredList.Select(n => n.Item2.HasValue ? new CompletionItemWithHighlight(n.Item1, n.Item2.Value.MatchedSpans) : new CompletionItemWithHighlight(n.Item1)).ToImmutableArray();
-
- int selectedItemIndex = 0;
- for (int i = 0; i < listWithHighlights.Length; i++)
- {
- if (listWithHighlights[i].CompletionItem == bestMatch.Item1)
- {
- selectedItemIndex = i;
- break;
- }
- }
-
- return Task.FromResult(new FilteredCompletionModel(listWithHighlights, selectedItemIndex, updatedFilters));
- }
-
- Task<ImmutableArray<CompletionItem>> IAsyncCompletionService.SortCompletionListAsync(
- ImmutableArray<CompletionItem> initialList, CompletionTriggerReason triggerReason, ITextSnapshot snapshot,
- ITrackingSpan applicableToSpan, ITextView view, CancellationToken token)
- {
- return Task.FromResult(initialList.OrderBy(n => n.SortText).ToImmutableArray());
- }
-
- #region Filtering
-
- private static bool ShouldBeInCompletionList(
- CompletionItem item,
- ImmutableArray<CompletionFilterWithState> filtersWithState)
- {
- foreach (var filterWithState in filtersWithState.Where(n => n.IsSelected))
- {
- if (item.Filters.Any(n => n == filterWithState.Filter))
- {
- return true;
- }
- }
- return false;
- }
-
- #endregion
- }
-
-#if DEBUG && false
- [Export(typeof(IAsyncCompletionItemSourceProvider))]
- [Name("Debug completion item source")]
- [Order(After = "default")]
- [ContentType("any")]
- public class DebugCompletionItemSourceProvider : IAsyncCompletionItemSourceProvider
- {
- DebugCompletionItemSource _instance;
-
- IAsyncCompletionItemSource IAsyncCompletionItemSourceProvider.GetOrCreate(ITextView textView)
- {
- if (_instance == null)
- _instance = new DebugCompletionItemSource();
- return _instance;
- }
- }
-
- public class DebugCompletionItemSource : IAsyncCompletionItemSource
- {
- private static readonly AccessibleImageId Icon1 = new AccessibleImageId(new Guid("{ae27a6b0-e345-4288-96df-5eaf394ee369}"), 666, "Icon description");
- private static readonly CompletionFilter Filter1 = new CompletionFilter("Diagnostic", "d", Icon1);
- private static readonly AccessibleImageId Icon2 = new AccessibleImageId(new Guid("{ae27a6b0-e345-4288-96df-5eaf394ee369}"), 2852, "Icon description");
- private static readonly CompletionFilter Filter2 = new CompletionFilter("Snippets", "s", Icon2);
- private static readonly AccessibleImageId Icon3 = new AccessibleImageId(new Guid("{ae27a6b0-e345-4288-96df-5eaf394ee369}"), 473, "Icon description");
- private static readonly CompletionFilter Filter3 = new CompletionFilter("Class", "c", Icon3);
- private static readonly ImmutableArray<CompletionFilter> FilterCollection1 = ImmutableArray.Create(Filter1);
- private static readonly ImmutableArray<CompletionFilter> FilterCollection2 = ImmutableArray.Create(Filter2);
- private static readonly ImmutableArray<CompletionFilter> FilterCollection3 = ImmutableArray.Create(Filter3);
- private static readonly ImmutableArray<char> commitCharacters = ImmutableArray.Create(' ', ';', '.', '<', '(', '[');
-
- CustomCommitBehavior IAsyncCompletionItemSource.CustomCommit(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token)
- {
- return CustomCommitBehavior.None;
- }
-
- CommitBehavior IAsyncCompletionItemSource.GetDefaultCommitBehavior(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token)
- {
- return CommitBehavior.None;
- }
-
- async Task<CompletionContext> IAsyncCompletionItemSource.GetCompletionContextAsync(CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan triggerLocation, CancellationToken token)
- {
- return await Task.FromResult(new CompletionContext(
- ImmutableArray.Create(
- new CompletionItem("SampleItem<>", this, Icon3, FilterCollection3, string.Empty, false, "SampleItem", "SampleItem<>", "SampleItem", ImmutableArray<AccessibleImageId>.Empty),
- new CompletionItem("AnotherItem🐱‍👤", this, Icon3, FilterCollection3, string.Empty, false, "AnotherItem", "AnotherItem", "AnotherItem", ImmutableArray.Create(Icon3)),
- new CompletionItem("Sampling", this, Icon1, FilterCollection1),
- new CompletionItem("Sampler", this, Icon1, FilterCollection1),
- new CompletionItem("Sapling", this, Icon2, FilterCollection2, "Sapling is a young tree"),
- new CompletionItem("OverSampling", this, Icon1, FilterCollection1, "overload"),
- new CompletionItem("AnotherSample", this, Icon2, FilterCollection2),
- new CompletionItem("AnotherSampling", this, Icon2, FilterCollection2),
- new CompletionItem("Simple", this, Icon3, FilterCollection3, "KISS"),
- new CompletionItem("Simpler", this, Icon3, FilterCollection3, "KISS")
- )));//, true, true, "Suggestion mode description!"));
- }
-
- async Task<object> IAsyncCompletionItemSource.GetDescriptionAsync(CompletionItem item, CancellationToken token)
- {
- return await Task.FromResult("This is a tooltip for " + item.DisplayText);
- }
-
- ImmutableArray<char> IAsyncCompletionItemSource.GetPotentialCommitCharacters() => commitCharacters;
-
- bool IAsyncCompletionItemSource.ShouldCommitCompletion(char typeChar, SnapshotPoint location)
- {
- return true;
- }
-
- SnapshotSpan? IAsyncCompletionItemSource.ShouldTriggerCompletion(char typeChar, SnapshotPoint triggerLocation)
- {
- var charBeforeCaret = triggerLocation.Subtract(1).GetChar();
- if (commitCharacters.Contains(charBeforeCaret) || triggerLocation.Position == 0)
- {
- // skip the typed character. the applicable span starts at the caret
- return new SnapshotSpan(triggerLocation, 0);
- }
- else
- {
- // include the typed character.
- return new SnapshotSpan(triggerLocation - 1, 1);
- }
- }
- }
-
-#endif
-#if DEBUG && true
-
- [Export(typeof(IAsyncCompletionItemSourceProvider))]
- [Name("Debug HTML completion item source")]
- [Order(After = "default")]
- [ContentType("RazorCSharp")]
- public class DebugHtmlCompletionItemSourceProvider : IAsyncCompletionItemSourceProvider
- {
- DebugHtmlCompletionItemSource _instance;
-
- IAsyncCompletionItemSource IAsyncCompletionItemSourceProvider.GetOrCreate(ITextView textView)
- {
- if (_instance == null)
- _instance = new DebugHtmlCompletionItemSource();
- return _instance;
- }
- }
-
- public class DebugHtmlCompletionItemSource : IAsyncCompletionItemSource
- {
- private static readonly ImmutableArray<char> commitCharacters = ImmutableArray.Create(' ', '>', '=');
-
- CommitBehavior IAsyncCompletionItemSource.CustomCommit(Text.Editor.ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token)
- {
- return CommitBehavior.None;
- }
-
- CommitBehavior IAsyncCompletionItemSource.GetDefaultCommitBehavior(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token)
- {
- return CommitBehavior.None;
- }
-
- async Task<CompletionContext> IAsyncCompletionItemSource.GetCompletionContextAsync(CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableSpan, CancellationToken token)
- {
- return await Task.FromResult(new CompletionContext(ImmutableArray.Create(new CompletionItem("html", this), new CompletionItem("head", this), new CompletionItem("body", this), new CompletionItem("header", this))));
- }
-
- async Task<object> IAsyncCompletionItemSource.GetDescriptionAsync(CompletionItem item, CancellationToken token)
- {
- return await Task.FromResult(item.DisplayText);
- }
-
- ImmutableArray<char> IAsyncCompletionItemSource.GetPotentialCommitCharacters()
- {
- return commitCharacters;
- }
-
- bool IAsyncCompletionItemSource.ShouldCommitCompletion(char typeChar, SnapshotPoint location)
- {
- return true;
- }
-
- SnapshotSpan? IAsyncCompletionItemSource.ShouldTriggerCompletion(char typeChar, SnapshotPoint triggerLocation)
- {
- var charBeforeCaret = triggerLocation.Subtract(1).GetChar();
- if (commitCharacters.Contains(charBeforeCaret) || triggerLocation.Position == 0)
- {
- // skip the typed character. the applicable span starts at the caret
- return new SnapshotSpan(triggerLocation, 0);
- }
- else
- {
- // include the typed character.
- return new SnapshotSpan(triggerLocation - 1, 1);
- }
- }
- }
-#endif
-}
diff --git a/src/Language/Impl/Language/Completion/ICompletionComputationCallbackHandler.cs b/src/Language/Impl/Language/Completion/ICompletionComputationCallbackHandler.cs
deleted file mode 100644
index a19f138..0000000
--- a/src/Language/Impl/Language/Completion/ICompletionComputationCallbackHandler.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
-{
- internal interface ICompletionComputationCallbackHandler<TModel>
- {
- Task UpdateUi(TModel model);
- void Dismiss();
- }
-}
diff --git a/src/Language/Impl/Language/Completion/ModernCompletionFeature.cs b/src/Language/Impl/Language/Completion/ModernCompletionFeature.cs
deleted file mode 100644
index a5ed8cf..0000000
--- a/src/Language/Impl/Language/Completion/ModernCompletionFeature.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Diagnostics;
-using Microsoft.VisualStudio.Text.Utilities;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
-{
- /// <summary>
- /// Provides information whether modern completion should be enabled, given the buffer's content type.
- /// </summary>
- internal static class ModernCompletionFeature
- {
- private const string TreatmentFlightName = "CompletionAPI";
- private static bool _treatmentFlightEnabled;
- private static bool _initialized;
-
- /// <summary>
- /// Returns whether or not modern completion should be enabled.
- /// </summary>
- /// <returns>true if experiment is enabled.</returns>
- public static bool GetFeatureState(IExperimentationServiceInternal experimentationService)
- {
- if (_initialized)
- {
- return _treatmentFlightEnabled;
- }
-
-#if DEBUG
- _treatmentFlightEnabled = true;
-#else
- _treatmentFlightEnabled = experimentationService.IsCachedFlightEnabled(TreatmentFlightName);
-#endif
- _initialized = true;
- return _treatmentFlightEnabled;
- }
- }
-}
diff --git a/src/Language/Impl/Language/Completion/SuggestionModeCompletionItemSource.cs b/src/Language/Impl/Language/Completion/SuggestionModeCompletionItemSource.cs
deleted file mode 100644
index c86589c..0000000
--- a/src/Language/Impl/Language/Completion/SuggestionModeCompletionItemSource.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Immutable;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.VisualStudio.Text;
-using Microsoft.VisualStudio.Text.Editor;
-
-namespace Microsoft.VisualStudio.Language.Intellisense.Implementation
-{
- /// <summary>
- /// Internal item source used during lifetime of the suggestion mode item.
- /// </summary>
- internal class SuggestionModeCompletionItemSource : IAsyncCompletionItemSource
- {
- static IAsyncCompletionItemSource _instance;
- internal static IAsyncCompletionItemSource Instance
- {
- get
- {
- if (_instance == null)
- _instance = new SuggestionModeCompletionItemSource();
- return _instance;
- }
- }
-
- CommitBehavior IAsyncCompletionItemSource.CustomCommit(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token)
- {
- return CommitBehavior.None;
- }
-
- CommitBehavior IAsyncCompletionItemSource.GetDefaultCommitBehavior(ITextView view, ITextBuffer buffer, CompletionItem item, ITrackingSpan applicableSpan, char typeChar, CancellationToken token)
- {
- return CommitBehavior.None;
- }
-
- Task<CompletionContext> IAsyncCompletionItemSource.GetCompletionContextAsync(CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableSpan, CancellationToken token)
- {
- throw new NotImplementedException("This item source is not meant to be registered. It is used only to provide tooltip.");
- }
-
- Task<object> IAsyncCompletionItemSource.GetDescriptionAsync(CompletionItem item, CancellationToken token)
- {
- return Task.FromResult<object>(string.Empty);
- }
-
- ImmutableArray<char> IAsyncCompletionItemSource.GetPotentialCommitCharacters()
- {
- throw new NotImplementedException("This item source is not meant to be registered. It is used only to provide tooltip.");
- }
-
- bool IAsyncCompletionItemSource.ShouldCommitCompletion(char typeChar, SnapshotPoint location)
- {
- return false; // Typing should not commit the suggestion mode item.
- }
-
- SnapshotSpan? IAsyncCompletionItemSource.ShouldTriggerCompletion(char typeChar, SnapshotPoint location)
- {
- return null;
- }
- }
-}
diff --git a/src/Language/Impl/Language/Strings.Designer.cs b/src/Language/Impl/Language/Strings.Designer.cs
index e4e63cc..d1a0428 100644
--- a/src/Language/Impl/Language/Strings.Designer.cs
+++ b/src/Language/Impl/Language/Strings.Designer.cs
@@ -68,5 +68,14 @@ namespace Microsoft.VisualStudio.Language.Intellisense.Implementation {
return ResourceManager.GetString("CompletionCommandHandlerName", resourceCulture);
}
}
+
+ /// <summary>
+ /// Looks up a localized string similar to Suggestion mode. Allows typing delimeters without auto completing..
+ /// </summary>
+ public static string SuggestionModeDefaultTooltip {
+ get {
+ return ResourceManager.GetString("SuggestionModeDefaultTooltip", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Language/Impl/Language/Strings.resx b/src/Language/Impl/Language/Strings.resx
index 3870d03..c391c70 100644
--- a/src/Language/Impl/Language/Strings.resx
+++ b/src/Language/Impl/Language/Strings.resx
@@ -120,4 +120,8 @@
<data name="CompletionCommandHandlerName" xml:space="preserve">
<value>Completion command handler</value>
</data>
+ <data name="SuggestionModeDefaultTooltip" xml:space="preserve">
+ <value>Suggestion mode. Allows typing delimeters without auto completing.</value>
+ <comment>Tooltip on suggestion mode completion item (visible when suggestion mode is enabled, through Ctrl+Alt+Space)</comment>
+ </data>
</root> \ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.Text.Implementation.csproj b/src/Microsoft.VisualStudio.Text.Implementation.csproj
index 2757a7c..0644a58 100644
--- a/src/Microsoft.VisualStudio.Text.Implementation.csproj
+++ b/src/Microsoft.VisualStudio.Text.Implementation.csproj
@@ -5,10 +5,11 @@
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>key.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
- <Version>15.0.25-pre</Version>
+ <Version>15.0.30-pre</Version>
<AssemblyVersion>15.0.0.0</AssemblyVersion>
- <NuGetVersionEditor>15.7.153-preview-g7d0635149a</NuGetVersionEditor>
- <NuGetVersionLanguage>15.7.153-preview-g7d0635149a</NuGetVersionLanguage>
+ <NuGetVersionEditor>15.8.519</NuGetVersionEditor>
+ <NuGetVersionLanguage>15.8.519</NuGetVersionLanguage>
+ <LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup>
diff --git a/src/Text/Def/Internal/TextData/ExtensionMethods.cs b/src/Text/Def/Internal/TextData/ExtensionMethods.cs
index dd50540..64fab7d 100644
--- a/src/Text/Def/Internal/TextData/ExtensionMethods.cs
+++ b/src/Text/Def/Internal/TextData/ExtensionMethods.cs
@@ -25,7 +25,7 @@ namespace Microsoft.VisualStudio.Text
{
if (startIndex < 0)
{
- throw new ArgumentOutOfRangeException("start");
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
}
// Take advantage of performant [] operators on the ITextSnapshot
diff --git a/src/Text/Def/Internal/TextData/IStructureSpanningTreeManager.cs b/src/Text/Def/Internal/TextData/IStructureSpanningTreeManager.cs
deleted file mode 100644
index afe6de3..0000000
--- a/src/Text/Def/Internal/TextData/IStructureSpanningTreeManager.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-//
-// 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 internal APIs that are subject to change without notice.
-// Use at your own risk.
-//
-namespace Microsoft.VisualStudio.Text.Structure
-{
- using System;
- using System.Collections.Generic;
- using Microsoft.VisualStudio.Text.Editor;
- using Microsoft.VisualStudio.Text.UI.Adornments;
-
- /// <summary>
- /// Defines the interface for the <see cref="IStructureSpanningTreeManager"/> which
- /// provides information about the structural hierarchy of code in an <see cref="ITextView"/>.
- /// </summary>
- /// <remarks>
- /// You can obtain an instance of this class via the <see cref="IStructureSpanningTreeService"/>.
- /// </remarks>
- public interface IStructureSpanningTreeManager
- {
- /// <summary>
- /// Event that indicates that <see cref="SpanningTreeSnapshot"/> has been updated,
- /// and that any method calls on this service will now return more up to date results.
- /// </summary>
- event EventHandler SpanningTreeChanged;
-
- /// <summary>
- /// Gets an immutable instance of the most up to date current code structure.
- /// </summary>
- IStructureElement SpanningTreeSnapshot { get; }
-
- /// <summary>
- /// Gets an enumerable of <see cref="IStructureElement"/>s that encapsulate the given <see cref="SnapshotPoint"/>.
- /// </summary>
- /// <remarks>
- /// This method is intended as a projection-aware means to obtain language-service provided
- /// structural context for a location such as the caret position, or a structure guide line.
- /// </remarks>
- /// <param name="point">A <see cref="SnapshotPoint"/> indicating the position of interest.</param>
- /// <returns>
- /// The elements within which <paramref name="point"/> is nested, in order, from outermost to innermost.
- /// </returns>
- IEnumerable<IStructureElement> GetElementsEncapsulatingPoint(SnapshotPoint point);
-
- /// <summary>
- /// Gets an enumerable of <see cref="IStructureElement"/>s that intersect with the given
- /// <see cref="SnapshotSpan"/>.
- /// </summary>
- /// <remarks>
- /// This method is intended as a projection-aware means to obtain language-service provided
- /// structural context for a span.
- /// </remarks>
- /// <param name="spans">The spans to collect elements from.</param>
- /// <returns>The elements that intersect with the given span.</returns>
- IEnumerable<IStructureElement> GetElementsIntersectingSpans(NormalizedSnapshotSpanCollection spans);
- }
-}
diff --git a/src/Text/Def/Internal/TextData/IStructureSpanningTreeService.cs b/src/Text/Def/Internal/TextData/IStructureSpanningTreeService.cs
deleted file mode 100644
index 1ad9c28..0000000
--- a/src/Text/Def/Internal/TextData/IStructureSpanningTreeService.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-//
-// 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 internal APIs that are subject to change without notice.
-// Use at your own risk.
-//
-namespace Microsoft.VisualStudio.Text.Structure
-{
- using System;
- using Microsoft.VisualStudio.Text.Editor;
-
- /// <summary>
- /// Defines the interface for the <see cref="IStructureSpanningTreeService"/> which can be
- /// used to obtain instances of the <see cref="IStructureSpanningTreeManager"/>, which
- /// provides information about the structural hierarchy of code in an <see cref="ITextView"/>.
- /// </summary>
- /// <remarks>
- /// This interface is a MEF component part and can be imported with a MEF import attribute.
- /// <code>
- /// [Import]
- /// internal IStructureSpanningTreeService StructureSpanningTreeService { get; }
- /// </code>
- /// </remarks>
- public interface IStructureSpanningTreeService
- {
- /// <summary>
- /// Gets the singleton <see cref="IStructureSpanningTreeManager"/> for the specified view.
- /// </summary>
- /// <param name="textView">The view to get the structure manager for.</param>
- /// <exception cref="InvalidOperationException">Throw if not called from the UI thread.</exception>
- /// <returns>The singleton instance of <see cref="IStructureSpanningTreeManager"/> for the view.</returns>
- IStructureSpanningTreeManager GetManager(ITextView textView);
- }
-}
diff --git a/src/Text/Def/Internal/TextData/JoinableTaskHelper.cs b/src/Text/Def/Internal/TextData/JoinableTaskHelper.cs
index 64c0c0d..3521c28 100644
--- a/src/Text/Def/Internal/TextData/JoinableTaskHelper.cs
+++ b/src/Text/Def/Internal/TextData/JoinableTaskHelper.cs
@@ -6,6 +6,7 @@
// Use at your own risk.
//
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;
@@ -16,16 +17,20 @@ namespace Microsoft.VisualStudio.Utilities
/// </summary>
public class JoinableTaskHelper
{
+#pragma warning disable CA1051 // Do not declare visible instance fields
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "By design")]
public readonly JoinableTaskContext Context;
+
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "By design")]
public readonly JoinableTaskCollection Collection;
+
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "By design")]
public readonly JoinableTaskFactory Factory;
+#pragma warning restore CA1051 // Do not declare visible instance fields
public JoinableTaskHelper(JoinableTaskContext context)
{
- if (context == null)
- throw new ArgumentNullException(nameof(context));
-
- this.Context = context;
+ this.Context = context ?? throw new ArgumentNullException(nameof(context));
this.Collection = context.CreateCollection();
this.Factory = context.CreateFactory(this.Collection);
}
@@ -64,15 +69,17 @@ namespace Microsoft.VisualStudio.Utilities
}
}
- public async Task DisposeAsync()
+ public Task DisposeAsync()
{
- await this.Collection.JoinTillEmptyAsync();
+ return this.Collection.JoinTillEmptyAsync();
}
public void Dispose()
{
- this.Context.Factory.Run(async delegate { // Not this.Factory
- await this.DisposeAsync();
+ this.Context.Factory.Run(async delegate
+ {
+ // Not this.Factory
+ await this.DisposeAsync().ConfigureAwait(false);
});
}
}
diff --git a/src/Text/Def/Internal/TextData/LazyObservableCollection.cs b/src/Text/Def/Internal/TextData/LazyObservableCollection.cs
index 2c4849f..1c2992b 100644
--- a/src/Text/Def/Internal/TextData/LazyObservableCollection.cs
+++ b/src/Text/Def/Internal/TextData/LazyObservableCollection.cs
@@ -157,7 +157,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
TWrapper wrapperObj = value as TWrapper;
if ((value != null) && (wrapperObj == null))
{
- throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "value is not of type {0}", typeof(TWrapper).FullName), "value");
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "value is not of type {0}", typeof(TWrapper).FullName), nameof(value));
}
return this.Contains(wrapperObj);
@@ -173,7 +173,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
TWrapper wrapperObj = value as TWrapper;
if ((value != null) && (wrapperObj == null))
{
- throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "value is not of type {0}", typeof(TWrapper).FullName), "value");
+ throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "value is not of type {0}", typeof(TWrapper).FullName), nameof(value));
}
return this.IndexOf(wrapperObj);
@@ -234,7 +234,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if ((array.Length - index) < this.Count)
{
- throw new ArgumentException("Array not big enough", "array");
+ throw new ArgumentException("Array not big enough", nameof(array));
}
int i = index;
@@ -395,10 +395,12 @@ namespace Microsoft.VisualStudio.Text.Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#region IDisposable Members
+#pragma warning disable CA1063 // Implement IDisposable Correctly
/// <summary>
/// Disposes and releases all wrappers created. Also releases all references to the underlying object
/// </summary>
public void Dispose()
+#pragma warning restore CA1063 // Implement IDisposable Correctly
{
if (_disposed) return;
_disposed = true;
@@ -560,7 +562,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
// Also notify consumers of the change to the 'Count' property.
- this.RaisePropertyChanged("Count");
+ this.RaisePropertyChanged(nameof(Count));
}
private void RaisePropertyChanged(string propertyName)
diff --git a/src/Text/Def/Internal/TextData/TextBufferOperationHelpers.cs b/src/Text/Def/Internal/TextData/TextBufferOperationHelpers.cs
index 4cb900c..26309eb 100644
--- a/src/Text/Def/Internal/TextData/TextBufferOperationHelpers.cs
+++ b/src/Text/Def/Internal/TextData/TextBufferOperationHelpers.cs
@@ -11,7 +11,7 @@ using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
namespace Microsoft.VisualStudio.Text
{
- public class TextBufferOperationHelpers
+ public static class TextBufferOperationHelpers
{
/// <summary>
/// Checks if the given <see cref="ITextSnapshotLine"/> has any non-whitespace characters
diff --git a/src/Text/Def/Internal/TextData/TrackingSpanTree.cs b/src/Text/Def/Internal/TextData/TrackingSpanTree.cs
index 8a7b195..acf8082 100644
--- a/src/Text/Def/Internal/TextData/TrackingSpanTree.cs
+++ b/src/Text/Def/Internal/TextData/TrackingSpanTree.cs
@@ -50,7 +50,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public TrackingSpanTree(ITextBuffer buffer, bool keepTrackingCurrent)
{
if (buffer == null)
- throw new ArgumentNullException("buffer");
+ throw new ArgumentNullException(nameof(buffer));
Buffer = buffer;
Count = 0;
@@ -77,10 +77,10 @@ namespace Microsoft.VisualStudio.Text.Utilities
public TrackingSpanNode<T> TryAddItem(T item, ITrackingSpan trackingSpan)
{
if (trackingSpan == null)
- throw new ArgumentNullException("trackingSpan");
+ throw new ArgumentNullException(nameof(trackingSpan));
if (trackingSpan.TrackingMode != SpanTrackingMode.EdgeExclusive)
- throw new ArgumentException("The tracking mode of the given tracking span must be SpanTrackingMode.EdgeExclusive", "trackingSpan");
+ throw new ArgumentException("The tracking mode of the given tracking span must be SpanTrackingMode.EdgeExclusive", nameof(trackingSpan));
SnapshotSpan spanToAdd = trackingSpan.GetSpan(Buffer.CurrentSnapshot);
TrackingSpanNode<T> node = new TrackingSpanNode<T>(item, trackingSpan);
@@ -101,7 +101,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public bool RemoveItem(T item, ITrackingSpan trackingSpan)
{
if (trackingSpan == null)
- throw new ArgumentNullException("trackingSpan");
+ throw new ArgumentNullException(nameof(trackingSpan));
SnapshotSpan spanToRemove = trackingSpan.GetSpan(Buffer.CurrentSnapshot);
@@ -228,7 +228,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (toVersion == null)
{
- throw new ArgumentNullException("toVersion");
+ throw new ArgumentNullException(nameof(toVersion));
}
if (toVersion.VersionNumber > this.advanceVersion)
diff --git a/src/Text/Def/Internal/TextData/UnicodeWordExtent.cs b/src/Text/Def/Internal/TextData/UnicodeWordExtent.cs
index 6ed9e0a..fc355c2 100644
--- a/src/Text/Def/Internal/TextData/UnicodeWordExtent.cs
+++ b/src/Text/Def/Internal/TextData/UnicodeWordExtent.cs
@@ -17,7 +17,9 @@ namespace Microsoft.VisualStudio.Text.Utilities
/// </summary>
public class LineBuffer
{
+#pragma warning disable CA2211 // Non-constant fields should not be visible
public static int BufferSize = 1024; // this is non-constant so unit tests can change it to better test LineBuffer logic
+#pragma warning restore CA2211 // Non-constant fields should not be visible
private readonly ITextSnapshotLine line;
@@ -63,7 +65,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
}
- public class UnicodeWordExtent
+ public static class UnicodeWordExtent
{
static public bool FindCurrentToken(SnapshotPoint currentPosition, out SnapshotSpan span)
{
diff --git a/src/Text/Def/Internal/TextLogic/IBypassUndoEditTag.cs b/src/Text/Def/Internal/TextLogic/IBypassUndoEditTag.cs
new file mode 100644
index 0000000..33d07c8
--- /dev/null
+++ b/src/Text/Def/Internal/TextLogic/IBypassUndoEditTag.cs
@@ -0,0 +1,22 @@
+//
+// 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 internal APIs that are subject to change without notice.
+// Use at your own risk.
+//
+namespace Microsoft.VisualStudio.Text
+{
+ /// <summary>
+ /// Edit tag indicating that the edit should be ignored by the undo system.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// Yes this is as dangerous as it sounds. Using it will corrupt the undo stack so
+ /// do not use unless you are prepared to do the appropriate clean-up.
+ /// </para>
+ /// </remarks>
+ public interface IBypassUndoEditTag : IUndoEditTag
+ {
+ }
+}
diff --git a/src/Text/Def/Internal/TextLogic/IEditOnlyTextUndoPrimitive.cs b/src/Text/Def/Internal/TextLogic/IEditOnlyTextUndoPrimitive.cs
new file mode 100644
index 0000000..74dec93
--- /dev/null
+++ b/src/Text/Def/Internal/TextLogic/IEditOnlyTextUndoPrimitive.cs
@@ -0,0 +1,20 @@
+//
+// 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 internal APIs that are subject to change without notice.
+// Use at your own risk.
+//
+namespace Microsoft.VisualStudio.Text.Operations
+{
+ /// <summary>
+ /// Represents undo primitive that consists only of text changes.
+ /// </summary>
+ public interface IEditOnlyTextUndoPrimitive : ITextUndoPrimitive
+ {
+ INormalizedTextChangeCollection Changes { get; }
+
+ int? BeforeReiteratedVersionNumber { get; }
+ int? AfterReiteratedVersionNumber { get; }
+ }
+}
diff --git a/src/Text/Def/Internal/TextLogic/IElisionTag.cs b/src/Text/Def/Internal/TextLogic/IElisionTag.cs
index eaa4502..154e5da 100644
--- a/src/Text/Def/Internal/TextLogic/IElisionTag.cs
+++ b/src/Text/Def/Internal/TextLogic/IElisionTag.cs
@@ -7,13 +7,15 @@
//
namespace Microsoft.VisualStudio.Text.Tagging
{
+ using Microsoft.VisualStudio.Text.Editor;
+
/// <summary>
/// Tag indicating spans of text to be excluded from a view.
/// </summary>
/// <remarks>
/// <para>
/// IViewTaggerProviders are querried by the editor implementation with this tag type for views having
- /// the <see cref="F:Microsoft.VisualStudio.Text.Editor.PredefinedTextViewRoles.Structured"/> view role.
+ /// the <see cref="PredefinedTextViewRoles.Structured"/> view role.
/// </para>
/// <para>
/// These tags cause text to be hidden but do not result in any outlining UI.
diff --git a/src/Text/Def/Internal/TextLogic/ILoggingServiceInternal.cs b/src/Text/Def/Internal/TextLogic/ILoggingServiceInternal.cs
index e78084b..dad1d94 100644
--- a/src/Text/Def/Internal/TextLogic/ILoggingServiceInternal.cs
+++ b/src/Text/Def/Internal/TextLogic/ILoggingServiceInternal.cs
@@ -12,7 +12,7 @@ using System;
namespace Microsoft.VisualStudio.Text.Utilities
{
/// <summary>
- /// Allows code in src/Platform to log events.
+ /// Allows code in VS-Platform to log events.
/// </summary>
/// <remarks>
/// For example, the VS Provider of this inserts data points into the telemetry data stream.
diff --git a/src/Text/Def/Internal/TextLogic/ITextSearchNavigator2.cs b/src/Text/Def/Internal/TextLogic/ITextSearchNavigator2.cs
index 57676ee..c560c27 100644
--- a/src/Text/Def/Internal/TextLogic/ITextSearchNavigator2.cs
+++ b/src/Text/Def/Internal/TextLogic/ITextSearchNavigator2.cs
@@ -13,6 +13,7 @@ namespace Microsoft.VisualStudio.Text.Operations
/// </summary>
public interface ITextSearchNavigator2 : ITextSearchNavigator
{
+#pragma warning disable CA2227 // Collection properties should be read only
/// <summary>
/// Indicates the ranges that should be searched (if any).
/// </summary>
@@ -20,5 +21,6 @@ namespace Microsoft.VisualStudio.Text.Operations
/// If this value to a non-null value will effectively override the ITextSearchNavigator.SearchSpan property.
/// </remarks>
NormalizedSnapshotSpanCollection SearchSpans { get; set; }
+#pragma warning restore CA2227 // Collection properties should be read only
}
}
diff --git a/src/Text/Def/Internal/TextLogic/ITextSearchTagger.cs b/src/Text/Def/Internal/TextLogic/ITextSearchTagger.cs
index f351d11..4051ae7 100644
--- a/src/Text/Def/Internal/TextLogic/ITextSearchTagger.cs
+++ b/src/Text/Def/Internal/TextLogic/ITextSearchTagger.cs
@@ -60,6 +60,7 @@ namespace Microsoft.VisualStudio.Text.Operations
/// </remarks>
public interface ITextSearchTagger<T> : ITagger<T> where T : ITag
{
+#pragma warning disable CA2227 // Collection properties should be read only
/// <summary>
/// Limits the scope of the tagger to the provided <see cref="NormalizedSnapshotSpanCollection"/>.
/// </summary>
@@ -67,6 +68,7 @@ namespace Microsoft.VisualStudio.Text.Operations
/// If the value is set to <c>null</c> the entire range of the buffer will be searched.
/// </remarks>
NormalizedSnapshotSpanCollection SearchSpans { get; set; }
+#pragma warning restore CA2227 // Collection properties should be read only
/// <summary>
/// Starts tagging occurences of the <paramref name="searchTerm"/>.
diff --git a/src/Text/Def/Internal/TextLogic/ITextUndoHistory2.cs b/src/Text/Def/Internal/TextLogic/ITextUndoHistory2.cs
new file mode 100644
index 0000000..0d2d3e3
--- /dev/null
+++ b/src/Text/Def/Internal/TextLogic/ITextUndoHistory2.cs
@@ -0,0 +1,31 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.Utilities;
+
+namespace Microsoft.VisualStudio.Text.Operations
+{
+ /// <summary>
+ /// Contains undo transactions.
+ /// </summary>
+ /// <remarks>
+ /// Typically only one undo transaction history at a time is availbble to the user.
+ /// </remarks>
+ public interface ITextUndoHistory2 : ITextUndoHistory
+ {
+ /// <summary>
+ /// Creates a new transaction, invisible, nests it in the previously current transaction, and marks it current.
+ /// </summary>
+ /// <param name="description">The description of the transaction.</param>
+ /// <returns>The new transaction.</returns>
+ /// <remarks>
+ /// <para>Invisible transactions are like normal undo transactions except that they are effectively invisible to the end user. They won't be displayed
+ /// in the undo stack and if the user does an "undo" then all the invisible transactions leading up to the 1st non-invisible transaction are "skipped".</para>
+ /// <para>Invisible transactions can only contain simple text edits (other types of undo actions will be lost and potentially corrupt the undo stack).</para>
+ /// </remarks>
+ ITextUndoTransaction CreateInvisibleTransaction(string description);
+ }
+}
diff --git a/src/Text/Def/Internal/TextLogic/TagAggregatorOptions2.cs b/src/Text/Def/Internal/TextLogic/TagAggregatorOptions2.cs
index 77476d3..80d4351 100644
--- a/src/Text/Def/Internal/TextLogic/TagAggregatorOptions2.cs
+++ b/src/Text/Def/Internal/TextLogic/TagAggregatorOptions2.cs
@@ -14,7 +14,9 @@ namespace Microsoft.VisualStudio.Text.Tagging
/// Tag Aggregator options.
/// </summary>
[Flags]
+#pragma warning disable CA1714 // Flags enums should have plural names
public enum TagAggregatorOptions2
+#pragma warning restore CA1714 // Flags enums should have plural names
{
/// <summary>
/// Default behavior. The tag aggregator will map up and down through all projection buffers.
@@ -56,4 +58,4 @@ namespace Microsoft.VisualStudio.Text.Tagging
/// </remarks>
NoProjection = 0x04
}
-} \ No newline at end of file
+}
diff --git a/src/Text/Def/Internal/TextLogic/TelemetryComplexProperty.cs b/src/Text/Def/Internal/TextLogic/TelemetryComplexProperty.cs
new file mode 100644
index 0000000..fe35096
--- /dev/null
+++ b/src/Text/Def/Internal/TextLogic/TelemetryComplexProperty.cs
@@ -0,0 +1,23 @@
+//
+// 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 internal APIs that are subject to change without notice.
+// Use at your own risk.
+//
+
+namespace Microsoft.VisualStudio.Text.Utilities
+{
+ /// <summary>
+ /// Allows code in VS-Platform to use complex telemetry properties, which reduce boilerplate code.
+ /// </summary>
+ public class TelemetryComplexProperty
+ {
+ public object Property { get; }
+
+ public TelemetryComplexProperty(object property)
+ {
+ Property = property;
+ }
+ }
+}
diff --git a/src/Text/Def/Internal/TextUI/DisplayTextRange.cs b/src/Text/Def/Internal/TextUI/DisplayTextRange.cs
index 9becda5..87323fb 100644
--- a/src/Text/Def/Internal/TextUI/DisplayTextRange.cs
+++ b/src/Text/Def/Internal/TextUI/DisplayTextRange.cs
@@ -11,10 +11,12 @@ using Microsoft.VisualStudio.Text.Formatting;
namespace Microsoft.VisualStudio.Text.Editor
{
+#pragma warning disable CA1710 // Identifiers should have correct suffix
/// <summary>
/// Represents a range in the <see cref="TextBuffer"/> that behaves relative to the view in which it lives.
/// </summary>
public abstract class DisplayTextRange : TextRange, IEnumerable<DisplayTextPoint>
+#pragma warning restore CA1710 // Identifiers should have correct suffix
{
/// <summary>
/// When implemented in a derived class, gets the <see cref="TextView"/> of this range.
diff --git a/src/Text/Def/Internal/TextUI/IViewPrimitives.cs b/src/Text/Def/Internal/TextUI/IViewPrimitives.cs
index 4130715..bf47488 100644
--- a/src/Text/Def/Internal/TextUI/IViewPrimitives.cs
+++ b/src/Text/Def/Internal/TextUI/IViewPrimitives.cs
@@ -21,7 +21,7 @@ namespace Microsoft.VisualStudio.Text.Editor
/// <summary>
/// Gets the <see cref="Selection"/> primitive used for selection manipulation.
/// </summary>
- Selection Selection { get; }
+ LegacySelection Selection { get; }
/// <summary>
/// Gets the <see cref="Caret"/> primitive used for caret movement.
diff --git a/src/Text/Def/Internal/TextUI/IViewPrimitivesFactoryService.cs b/src/Text/Def/Internal/TextUI/IViewPrimitivesFactoryService.cs
index 2408671..bed9ced 100644
--- a/src/Text/Def/Internal/TextUI/IViewPrimitivesFactoryService.cs
+++ b/src/Text/Def/Internal/TextUI/IViewPrimitivesFactoryService.cs
@@ -52,16 +52,16 @@ namespace Microsoft.VisualStudio.Text.Editor
DisplayTextRange CreateDisplayTextRange(TextView textView, TextRange textRange);
/// <summary>
- /// Creates a <see cref="Selection"/> primitive.
+ /// Creates a <see cref="LegacySelection"/> primitive.
/// </summary>
/// <param name="textView">The <see cref="ITextView"/> on which to base this primitive.</param>
- /// <returns>The <see cref="Selection"/> primitive for the given <see cref="ITextView"/>.</returns>
+ /// <returns>The <see cref="LegacySelection"/> primitive for the given <see cref="ITextView"/>.</returns>
/// <remarks>
/// <para>
/// This method always returns the same object if the same <see cref="ITextView"/> is passed in.
/// </para>
/// </remarks>
- Selection CreateSelection(TextView textView);
+ LegacySelection CreateSelection(TextView textView);
/// <summary>
/// Creates a <see cref="Caret"/> primitive.
diff --git a/src/Text/Def/Internal/TextUI/Selection.cs b/src/Text/Def/Internal/TextUI/LegacySelection.cs
index 59d27eb..ee3b9cc 100644
--- a/src/Text/Def/Internal/TextUI/Selection.cs
+++ b/src/Text/Def/Internal/TextUI/LegacySelection.cs
@@ -9,10 +9,12 @@ namespace Microsoft.VisualStudio.Text.Editor
{
using System;
+#pragma warning disable CA1710 // Identifiers should have correct suffix
/// <summary>
/// Represents the selection on the screen.
/// </summary>
- public abstract class Selection : DisplayTextRange
+ public abstract class LegacySelection : DisplayTextRange
+#pragma warning restore CA1710 // Identifiers should have correct suffix
{
/// <summary>
/// When implemented in a derived class, selects the given text range.
diff --git a/src/Text/Def/Internal/TextUI/OverviewFormatDefinitions.cs b/src/Text/Def/Internal/TextUI/OverviewFormatDefinitions.cs
deleted file mode 100644
index 3cff2ac..0000000
--- a/src/Text/Def/Internal/TextUI/OverviewFormatDefinitions.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-//
-// 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 internal APIs that are subject to change without notice.
-// Use at your own risk.
-//
-namespace Microsoft.VisualStudio.Text.OverviewMargin
-{
- public static class OverviewFormatDefinitions
- {
- public const string ElisionColorName = "OverviewMarginCollapsedRegion";
- public const string OffScreenColorName = "OverviewMarginBackground";
- public const string VisibleColorName = "OverviewMarginVisible";
- public const string CaretColorName = "OverviewMarginCaret";
- }
-}
diff --git a/src/Text/Def/Internal/TextUI/TextRange.cs b/src/Text/Def/Internal/TextUI/TextRange.cs
index 3b114ad..354f4d0 100644
--- a/src/Text/Def/Internal/TextUI/TextRange.cs
+++ b/src/Text/Def/Internal/TextUI/TextRange.cs
@@ -14,6 +14,7 @@ using Microsoft.VisualStudio.Text.Operations;
namespace Microsoft.VisualStudio.Text.Editor
{
+#pragma warning disable CA1710 // Identifiers should have correct suffix
/// <summary>
/// Represents a range of text in the buffer.
/// </summary>
@@ -24,6 +25,7 @@ namespace Microsoft.VisualStudio.Text.Editor
/// </para>
/// </remarks>
public abstract class TextRange : IEnumerable<TextPoint>
+#pragma warning restore CA1710 // Identifiers should have correct suffix
{
/// <summary>
/// When implemented in a derived class, gets the start point of this text range.
diff --git a/src/Text/Def/Internal/TextUI/TextView.cs b/src/Text/Def/Internal/TextUI/TextView.cs
index e1a3fa6..26f4446 100644
--- a/src/Text/Def/Internal/TextUI/TextView.cs
+++ b/src/Text/Def/Internal/TextUI/TextView.cs
@@ -149,7 +149,7 @@ namespace Microsoft.VisualStudio.Text.Editor
/// <summary>
/// When implemented in a derived class, gets the <see cref="Selection"/>. of this view.
/// </summary>
- public abstract Selection Selection
+ public abstract LegacySelection Selection
{
get;
}
diff --git a/src/Text/Def/Internal/TextUI/ViewRelativePosition2.cs b/src/Text/Def/Internal/TextUI/ViewRelativePosition2.cs
index adbb0ca..2152d22 100644
--- a/src/Text/Def/Internal/TextUI/ViewRelativePosition2.cs
+++ b/src/Text/Def/Internal/TextUI/ViewRelativePosition2.cs
@@ -40,6 +40,15 @@ namespace Microsoft.VisualStudio.Text.Editor
/// <summary>
/// The offset with respect to the bottom of the view to the bottom of the text on the line.
/// </summary>
- TextBottom
+ TextBottom,
+
+ /// <summary>
+ /// The offset is with respect to the BaseLine of the line containing bufferPosition.
+ /// </summary>
+ /// <remarks>
+ /// If this positioning mode is used (and only this positioning mode), then bufferPosition can be default(SnapshotPoint).
+ /// If a default(SnapshotPoint) is used or one is given but that line is not visible, then the view will pick an appropriate line to use.
+ /// </remarks>
+ Baseline
}
-} \ No newline at end of file
+}
diff --git a/src/Text/Def/TextData/Differencing/ITokenizedStringList.cs b/src/Text/Def/TextData/Differencing/ITokenizedStringList.cs
index 3c7dca7..d92839d 100644
--- a/src/Text/Def/TextData/Differencing/ITokenizedStringList.cs
+++ b/src/Text/Def/TextData/Differencing/ITokenizedStringList.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
namespace Microsoft.VisualStudio.Text.Differencing
{
+#pragma warning disable CA1710 // Identifiers should have correct suffix
/// <summary>
/// A tokenized representation of a string into abutting and non-overlapping segments.
/// </summary>
@@ -17,6 +18,7 @@ namespace Microsoft.VisualStudio.Text.Differencing
/// as ILists.</para>
/// </remarks>
public interface ITokenizedStringList : IList<string>
+#pragma warning restore CA1710 // Identifiers should have correct suffix
{
/// <summary>
/// The original string that was tokenized.
@@ -40,4 +42,4 @@ namespace Microsoft.VisualStudio.Text.Differencing
/// <returns>The span mapped onto the original list.</returns>
Span GetSpanInOriginal(Span span);
}
-} \ No newline at end of file
+}
diff --git a/src/Text/Def/TextData/Differencing/Match.cs b/src/Text/Def/TextData/Differencing/Match.cs
index dedd673..82476e0 100644
--- a/src/Text/Def/TextData/Differencing/Match.cs
+++ b/src/Text/Def/TextData/Differencing/Match.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
namespace Microsoft.VisualStudio.Text.Differencing
{
+#pragma warning disable CA1710 // Identifiers should have correct suffix
/// <summary>
/// Represents a range of matches between two sequences as a pair of spans of equal length.
/// </summary>
@@ -20,6 +21,7 @@ namespace Microsoft.VisualStudio.Text.Differencing
/// (0, 0, 2) and (4, 4, 1)
///</remarks>
public class Match : IEnumerable<Tuple<int, int>>
+#pragma warning restore CA1710 // Identifiers should have correct suffix
{
private Span left;
private Span right;
diff --git a/src/Text/Def/TextData/Differencing/StringDifferenceOptions.cs b/src/Text/Def/TextData/Differencing/StringDifferenceOptions.cs
index 02e9e88..d5c95c9 100644
--- a/src/Text/Def/TextData/Differencing/StringDifferenceOptions.cs
+++ b/src/Text/Def/TextData/Differencing/StringDifferenceOptions.cs
@@ -7,29 +7,22 @@ using System.Globalization;
namespace Microsoft.VisualStudio.Text.Differencing
{
-// Ignore the warnings about deprecated properties
+ // Ignore the warnings about deprecated properties
#pragma warning disable 0618
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Options to use in computing string differences.
/// </summary>
public struct StringDifferenceOptions
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
- // These need to be fields and specifically in this order in order for the COM-friendly
- // interfaces to be generated correctly.
- private StringDifferenceTypes differenceType;
- private int locality;
- private bool ignoreTrimWhiteSpace;
/// <summary>
/// The type of string differencing to do, as a combination
/// of line, word, and character differencing.
/// </summary>
- public StringDifferenceTypes DifferenceType
- {
- get { return differenceType; }
- set { differenceType = value; }
- }
+ public StringDifferenceTypes DifferenceType { get; set; }
/// <summary>
/// The greatest distance a differencing element (line, span, or character) can move
@@ -47,20 +40,12 @@ namespace Microsoft.VisualStudio.Text.Differencing
/// </para>
/// </remarks>
[Obsolete("This value is no longer used and will be ignored.")]
- public int Locality
- {
- get { return locality; }
- set { locality = value; }
- }
+ public int Locality { get; set; }
/// <summary>
/// Gets or sets whether to ignore white space.
/// </summary>
- public bool IgnoreTrimWhiteSpace
- {
- get { return ignoreTrimWhiteSpace; }
- set { ignoreTrimWhiteSpace = value; }
- }
+ public bool IgnoreTrimWhiteSpace { get; set; }
/// <summary>
/// The behavior to use when splitting words, if word differencing is requested
@@ -91,9 +76,9 @@ namespace Microsoft.VisualStudio.Text.Differencing
/// <param name="ignoreTrimWhiteSpace">Determines whether whitespace should be ignored.</param>
public StringDifferenceOptions(StringDifferenceTypes differenceType, int locality, bool ignoreTrimWhiteSpace) : this()
{
- this.differenceType = differenceType;
- this.locality = locality;
- this.ignoreTrimWhiteSpace = ignoreTrimWhiteSpace;
+ this.DifferenceType = differenceType;
+ this.Locality = locality;
+ this.IgnoreTrimWhiteSpace = ignoreTrimWhiteSpace;
}
/// <summary>
@@ -102,9 +87,9 @@ namespace Microsoft.VisualStudio.Text.Differencing
/// <param name="other">The <see cref="StringDifferenceOptions"/> to use in constructing a new <see cref="StringDifferenceOptions"/>.</param>
public StringDifferenceOptions(StringDifferenceOptions other) : this()
{
- this.differenceType = other.DifferenceType;
- this.locality = other.Locality;
- this.ignoreTrimWhiteSpace = other.IgnoreTrimWhiteSpace;
+ this.DifferenceType = other.DifferenceType;
+ this.Locality = other.Locality;
+ this.IgnoreTrimWhiteSpace = other.IgnoreTrimWhiteSpace;
this.WordSplitBehavior = other.WordSplitBehavior;
this.DetermineLocalityCallback = other.DetermineLocalityCallback;
this.ContinueProcessingPredicate = other.ContinueProcessingPredicate;
@@ -119,7 +104,7 @@ namespace Microsoft.VisualStudio.Text.Differencing
{
return string.Format(CultureInfo.InvariantCulture,
"Type: {0}, Locality: {1}, IgnoreTrimWhiteSpace: {2}, WordSplitBehavior: {3}, DetermineLocalityCallback: {4}, ContinueProcessingPredicate: {5}",
- DifferenceType, Locality, IgnoreTrimWhiteSpace, WordSplitBehavior, DetermineLocalityCallback, ContinueProcessingPredicate);
+ this.DifferenceType, this.Locality, this.IgnoreTrimWhiteSpace, this.WordSplitBehavior, this.DetermineLocalityCallback, this.ContinueProcessingPredicate);
}
/// <summary>
@@ -127,9 +112,9 @@ namespace Microsoft.VisualStudio.Text.Differencing
/// </summary>
public override int GetHashCode()
{
- int callbackHashCode = (DetermineLocalityCallback != null) ? DetermineLocalityCallback.GetHashCode() : 0;
- int predicateHashCode = (ContinueProcessingPredicate != null)? ContinueProcessingPredicate.GetHashCode() : 0;
- return (DifferenceType.GetHashCode() ^ Locality.GetHashCode() ^ IgnoreTrimWhiteSpace.GetHashCode() ^ WordSplitBehavior.GetHashCode() ^ callbackHashCode ^ predicateHashCode);
+ int callbackHashCode = (this.DetermineLocalityCallback != null) ? this.DetermineLocalityCallback.GetHashCode() : 0;
+ int predicateHashCode = (this.ContinueProcessingPredicate != null)? this.ContinueProcessingPredicate.GetHashCode() : 0;
+ return (this.DifferenceType.GetHashCode() ^ this.Locality.GetHashCode() ^ this.IgnoreTrimWhiteSpace.GetHashCode() ^ this.WordSplitBehavior.GetHashCode() ^ callbackHashCode ^ predicateHashCode);
}
/// <summary>
@@ -149,7 +134,7 @@ namespace Microsoft.VisualStudio.Text.Differencing
/// </summary>
public static bool operator ==(StringDifferenceOptions left, StringDifferenceOptions right)
{
- if (object.ReferenceEquals(left, right))
+ if (ReferenceEquals(left, right))
return true;
if ((object)left == null || (object)right == null)
diff --git a/src/Text/Def/TextData/Document/FileUtilities.cs b/src/Text/Def/TextData/Document/FileUtilities.cs
deleted file mode 100644
index c176a73..0000000
--- a/src/Text/Def/TextData/Document/FileUtilities.cs
+++ /dev/null
@@ -1,253 +0,0 @@
-//
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-//
-using System.Diagnostics;
-using System.IO;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace Microsoft.VisualStudio.Text
-{
- internal class FileUtilities
- {
- public static void SaveSnapshot(ITextSnapshot snapshot,
- FileMode fileMode,
- Encoding encoding,
- string filePath)
- {
- Debug.Assert((fileMode == FileMode.Create) || (fileMode == FileMode.CreateNew));
-
- //Save the contents of the text buffer to disk.
-
- string temporaryFilePath = null;
- try
- {
- FileStream originalFileStream = null;
- FileStream temporaryFileStream = FileUtilities.CreateFileStream(filePath, fileMode, out temporaryFilePath, out originalFileStream);
- if (originalFileStream == null)
- {
- //The "normal" scenario: save the snapshot directly to disk. Either:
- // there are no hard links to the target file so we can write the snapshot to the temporary and use File.Replace.
- // we're creating a new file (in which case, temporaryFileStream is a misnomer: it is the stream for the file we are creating).
- try
- {
- using (StreamWriter streamWriter = new StreamWriter(temporaryFileStream, encoding))
- {
- snapshot.Write(streamWriter);
- }
- }
- finally
- {
- //This is somewhat redundant: disposing of streamWriter had the side-effect of disposing of temporaryFileStream
- temporaryFileStream.Dispose();
- temporaryFileStream = null;
- }
-
- if (temporaryFilePath != null)
- {
- //We were saving to the original file and already have a copy of the file on disk.
- int remainingAttempts = 3;
- do
- {
- try
- {
- //Replace the contents of filePath with the contents of the temporary using File.Replace to
- //preserve the various attributes of the original file.
- File.Replace(temporaryFilePath, filePath, null, true);
- temporaryFilePath = null;
-
- return;
- }
- catch (FileNotFoundException)
- {
- // The target file doesn't exist (someone deleted it after we detected it earlier).
- // This is an acceptable condition so don't throw.
- File.Move(temporaryFilePath, filePath);
- temporaryFilePath = null;
-
- return;
- }
- catch (IOException)
- {
- //There was some other exception when trying to replace the contents of the file
- //(probably because some other process had the file locked).
- //Wait a few ms and try again.
- System.Threading.Thread.Sleep(5);
- }
- }
- while (--remainingAttempts > 0);
-
- //We're giving up on replacing the file. Try overwriting it directly (this is essentially the old Dev11 behavior).
- //Do not try approach we are using for hard links (copying the original & restoring it if there is a failure) since
- //getting here implies something strange is going on with the file system (Git or the like locking files) so we
- //want the simplest possible fallback.
-
- //Failing here causes the exception to be passed to the calling code.
- using (FileStream stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read))
- {
- using (StreamWriter streamWriter = new StreamWriter(stream, encoding))
- {
- snapshot.Write(streamWriter);
- }
- }
- }
- }
- else
- {
- //filePath has hard links so we need to use a different approach to save the file:
- // copy the original file to the temporary
- // write directly to the original
- // restore the original in the event of errors (which could be encoding errors and not disk issues) if there's a problem.
- try
- {
- // Copy the contents of the original file to the temporary.
- originalFileStream.CopyTo(temporaryFileStream);
-
- //We've got a clean copy, try writing the snapshot directly to the original file
- try
- {
- originalFileStream.Seek(0, SeekOrigin.Begin);
- originalFileStream.SetLength(0);
-
- //Make sure the StreamWriter is flagged leaveOpen == true. Otherwise disposing of the StreamWriter will dispose of originalFileStream and we need to
- //leave originalFileStream open so we can use it to restore the original from the temporary copy we made.
- using (var streamWriter = new StreamWriter(originalFileStream, encoding, bufferSize: 1024, leaveOpen: true)) //1024 == the default buffer size for a StreamWriter.
- {
- snapshot.Write(streamWriter);
- }
- }
- catch
- {
- //Restore the original from the temporary copy we made (but rethrow the original exception since we didn't save the file).
- temporaryFileStream.Seek(0, SeekOrigin.Begin);
-
- originalFileStream.Seek(0, SeekOrigin.Begin);
- originalFileStream.SetLength(0);
-
- temporaryFileStream.CopyTo(originalFileStream);
-
- throw;
- }
- }
- finally
- {
- originalFileStream.Dispose();
- originalFileStream = null;
-
- temporaryFileStream.Dispose();
- temporaryFileStream = null;
- }
- }
- }
- finally
- {
- if (temporaryFilePath != null)
- {
- try
- {
- //We do not need the temporary any longer.
- if (File.Exists(temporaryFilePath))
- {
- File.Delete(temporaryFilePath);
- }
- }
- catch
- {
- //Failing to clean up the temporary is an ignorable exception.
- }
- }
- }
- }
-
- private static FileStream CreateFileStream(string filePath, FileMode fileMode, out string temporaryPath, out FileStream originalFileStream)
- {
- originalFileStream = null;
-
- if (File.Exists(filePath))
- {
- // We're writing to a file that already exists. This is an error if we're trying to do a CreateNew.
- if (fileMode == FileMode.CreateNew)
- {
- throw new IOException(filePath + " exists");
- }
-
- try
- {
- originalFileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
-
- //Even thoug SafeFileHandle is an IDisposable, we don't dispose of it since that closes the strem.
- var safeHandle = originalFileStream.SafeFileHandle;
- if (!(safeHandle.IsClosed || safeHandle.IsInvalid))
- {
- BY_HANDLE_FILE_INFORMATION fi;
- if (GetFileInformationByHandle(safeHandle, out fi))
- {
- if (fi.NumberOfLinks <= 1)
- {
- // The file we're trying to write to doesn't have any hard links ... clear out the originalFileStream
- // as a clue.
- originalFileStream.Dispose();
- originalFileStream = null;
- }
- }
- }
- }
- catch
- {
- if (originalFileStream != null)
- {
- originalFileStream.Dispose();
- originalFileStream = null;
- }
-
- //We were not able to determine whether or not the file had hard links so throw here (aborting the save)
- //since we don't know how to do it safely.
- throw;
- }
-
- string root = Path.GetDirectoryName(filePath);
-
- int count = 0;
- while (++count < 20)
- {
- try
- {
- temporaryPath = Path.Combine(root, Path.GetRandomFileName() + "~"); //The ~ suffix hides the temporary file from GIT.
- return new FileStream(temporaryPath, FileMode.CreateNew, (originalFileStream != null) ? FileAccess.ReadWrite : FileAccess.Write, FileShare.None);
- }
- catch (IOException)
- {
- //Ignore IOExceptions ... GetRandomFileName() came up with a duplicate so we need to try again.
- }
- }
-
- Debug.Fail("Unable to create a temporary file");
- }
-
- 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/Def/TextData/Document/TextDocumentFileActionEventArgs.cs b/src/Text/Def/TextData/Document/TextDocumentFileActionEventArgs.cs
index 9e4732b..e94e9ae 100644
--- a/src/Text/Def/TextData/Document/TextDocumentFileActionEventArgs.cs
+++ b/src/Text/Def/TextData/Document/TextDocumentFileActionEventArgs.cs
@@ -52,7 +52,7 @@ namespace Microsoft.VisualStudio.Text
{
if (filePath == null)
{
- throw new ArgumentNullException("filePath");
+ throw new ArgumentNullException(nameof(filePath));
}
_filePath = filePath;
diff --git a/src/Text/Def/TextData/Model/ContentTypeChangedEventArgs.cs b/src/Text/Def/TextData/Model/ContentTypeChangedEventArgs.cs
index fccc545..a18c86d 100644
--- a/src/Text/Def/TextData/Model/ContentTypeChangedEventArgs.cs
+++ b/src/Text/Def/TextData/Model/ContentTypeChangedEventArgs.cs
@@ -12,10 +12,8 @@ namespace Microsoft.VisualStudio.Text
/// </summary>
public class ContentTypeChangedEventArgs : TextSnapshotChangedEventArgs
{
- #region Private Members
- IContentType _beforeContentType;
- IContentType _afterContentType;
+ #region Private Members
#endregion
@@ -38,40 +36,18 @@ namespace Microsoft.VisualStudio.Text
object editTag)
: base(beforeSnapshot, afterSnapshot, editTag)
{
- if (beforeContentType == null)
- {
- throw new ArgumentNullException("beforeContentType");
- }
-
- if (afterContentType == null)
- {
- throw new ArgumentNullException("afterContentType");
- }
-
- _beforeContentType = beforeContentType;
- _afterContentType = afterContentType;
+ BeforeContentType = beforeContentType ?? throw new ArgumentNullException(nameof(beforeContentType));
+ AfterContentType = afterContentType ?? throw new ArgumentNullException(nameof(afterContentType));
}
/// <summary>
/// The <see cref="IContentType"/> before the change occurred.
/// </summary>
- public IContentType BeforeContentType
- {
- get
- {
- return _beforeContentType;
- }
- }
+ public IContentType BeforeContentType { get; }
/// <summary>
/// The <see cref="IContentType"/> after the change occurred.
/// </summary>
- public IContentType AfterContentType
- {
- get
- {
- return _afterContentType;
- }
- }
+ public IContentType AfterContentType { get; }
}
}
diff --git a/src/Text/Def/TextData/Model/EditOptions.cs b/src/Text/Def/TextData/Model/EditOptions.cs
index 71568ad..d8699ec 100644
--- a/src/Text/Def/TextData/Model/EditOptions.cs
+++ b/src/Text/Def/TextData/Model/EditOptions.cs
@@ -6,13 +6,13 @@ namespace Microsoft.VisualStudio.Text
{
using Microsoft.VisualStudio.Text.Differencing;
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Options applicable to text editing transactions.
/// </summary>
public struct EditOptions
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
- private bool computeMinimalChange;
- private StringDifferenceOptions differenceOptions;
#region Common EditOptions values
@@ -35,8 +35,8 @@ namespace Microsoft.VisualStudio.Text
/// </summary>
public EditOptions(StringDifferenceOptions differenceOptions)
{
- this.computeMinimalChange = true;
- this.differenceOptions = differenceOptions;
+ this.ComputeMinimalChange = true;
+ this.DifferenceOptions = differenceOptions;
}
/// <summary>
@@ -44,17 +44,14 @@ namespace Microsoft.VisualStudio.Text
/// </summary>
public EditOptions(bool computeMinimalChange, StringDifferenceOptions differenceOptions)
{
- this.computeMinimalChange = computeMinimalChange;
- this.differenceOptions = differenceOptions;
+ this.ComputeMinimalChange = computeMinimalChange;
+ this.DifferenceOptions = differenceOptions;
}
/// <summary>
/// True if this edit computes minimal change using the differencing option <see cref="StringDifferenceOptions"/>, false otherwise.
/// </summary>
- public bool ComputeMinimalChange
- {
- get { return this.computeMinimalChange; }
- }
+ public bool ComputeMinimalChange { get; }
/// <summary>
/// The differencing options for this edit, if <see cref="ComputeMinimalChange" /> is true.
@@ -63,10 +60,7 @@ namespace Microsoft.VisualStudio.Text
/// <see cref="StringDifferenceOptions.IgnoreTrimWhiteSpace" /> will be
/// ignored.
/// </remarks>
- public StringDifferenceOptions DifferenceOptions
- {
- get { return differenceOptions; }
- }
+ public StringDifferenceOptions DifferenceOptions { get; }
#region Overridden methods and operators
@@ -81,7 +75,7 @@ namespace Microsoft.VisualStudio.Text
}
else
{
- return differenceOptions.ToString();
+ return DifferenceOptions.ToString();
}
}
@@ -96,7 +90,7 @@ namespace Microsoft.VisualStudio.Text
}
else
{
- return differenceOptions.GetHashCode();
+ return DifferenceOptions.GetHashCode();
}
}
@@ -114,7 +108,7 @@ namespace Microsoft.VisualStudio.Text
if (!this.ComputeMinimalChange)
return true;
- return other.differenceOptions == this.differenceOptions;
+ return other.DifferenceOptions == this.DifferenceOptions;
}
else
{
diff --git a/src/Text/Def/TextData/Model/EditTags.cs b/src/Text/Def/TextData/Model/EditTags.cs
new file mode 100644
index 0000000..20cec5b
--- /dev/null
+++ b/src/Text/Def/TextData/Model/EditTags.cs
@@ -0,0 +1,66 @@
+namespace Microsoft.VisualStudio.Text
+{
+ /// <summary>
+ /// Interface that can be used for the <see cref="ITextBuffer.CreateEdit(EditOptions, int?, object)"/> editTag parameter.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This interface, by itself, does nothing. The derived interfaces, however, can provide some context on the nature of the edit.
+ /// For example, the tags for edits associated with the user doing an "undo" should derive from <see cref="IUndoEditTag"/> and
+ /// <see cref="IUserEditTag"/>.
+ /// </para>
+ /// </remarks>
+ public interface IEditTag { }
+
+ /// <summary>
+ /// Indicates a constraint that no additional edits should be performed in the buffer's <see cref="ITextBuffer.Changed"/> event
+ /// handlers called in response to this edit.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This constraint is not currently enforced but that may happen in the future.
+ /// </para>
+ /// </remarks>
+ public interface IInviolableEditTag : IEditTag { }
+
+ /// <summary>
+ /// Indicates that this edit will create an invisible undo transaction.
+ /// </summary>
+ public interface IInvisibleEditTag : IEditTag { }
+
+ /// <summary>
+ /// Indicates that the edit is part of an undo or redo.
+ /// </summary>
+ public interface IUndoEditTag : IInviolableEditTag { }
+
+ /// <summary>
+ /// Indicates that the edit is part of automatic formatting.
+ /// </summary>
+ public interface IFormattingEditTag : IInviolableEditTag { }
+
+ /// <summary>
+ /// Indicates that the edit is from a remote collaborator.
+ /// </summary>
+ public interface IRemoteEditTag : IInviolableEditTag, IInvisibleEditTag { }
+
+ /// <summary>
+ /// Indicates that the edit is a direct result of a user action (e.g. typing) as opposed to a side-effect (e.g. the
+ /// automatic formatting after the user types a semicolon).
+ /// </summary>
+ public interface IUserEditTag : IEditTag { }
+
+ /// <summary>
+ /// Indicates that the edit is something like a "paste" where the modified text should be formatted.
+ /// </summary>
+ public interface IFormattingNeededEditTag : IEditTag { }
+
+ /// <summary>
+ /// Indicates that the edit is the result of the user typing a character.
+ /// </summary>
+ public interface ITypingEditTag : IUserEditTag { }
+
+ /// <summary>
+ /// Indicates that the edit is the result of the user typing hitting a backspace or delete.
+ /// </summary>
+ public interface IDeleteEditTag : IUserEditTag { }
+}
diff --git a/src/Text/Def/TextData/Model/NormalizedSnapshotSpanCollection.cs b/src/Text/Def/TextData/Model/NormalizedSnapshotSpanCollection.cs
index 095fb71..42b60ed 100644
--- a/src/Text/Def/TextData/Model/NormalizedSnapshotSpanCollection.cs
+++ b/src/Text/Def/TextData/Model/NormalizedSnapshotSpanCollection.cs
@@ -7,6 +7,7 @@ namespace Microsoft.VisualStudio.Text
using System;
using System.Collections;
using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
/// <summary>
/// A read-only collection of <see cref="SnapshotSpan"/> objects, all from the same snapshot.
@@ -28,10 +29,11 @@ namespace Microsoft.VisualStudio.Text
// over 95% of the instances of this class). If this.spans is nonnull, the collection is size two or greater.
// We can't do this by subclassing because of backward compatibility with the concrete constructor.
- private ITextSnapshot snapshot;
- private NormalizedSpanCollection spans;
- private Span span;
+ private readonly ITextSnapshot snapshot;
+ private readonly NormalizedSpanCollection spans;
+ private readonly Span span;
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "Type is immutable")]
public readonly static NormalizedSnapshotSpanCollection Empty = new NormalizedSnapshotSpanCollection();
/// <summary>
@@ -68,11 +70,11 @@ namespace Microsoft.VisualStudio.Text
{
if (snapshot == null)
{
- throw new ArgumentNullException("snapshot");
+ throw new ArgumentNullException(nameof(snapshot));
}
if (spans == null)
{
- throw new ArgumentNullException("spans");
+ throw new ArgumentNullException(nameof(spans));
}
if (spans.Count > 0 && spans[spans.Count - 1].End > snapshot.Length)
{
@@ -101,11 +103,11 @@ namespace Microsoft.VisualStudio.Text
{
if (snapshot == null)
{
- throw new ArgumentNullException("snapshot");
+ throw new ArgumentNullException(nameof(snapshot));
}
if (spans == null)
{
- throw new ArgumentNullException("spans");
+ throw new ArgumentNullException(nameof(spans));
}
using (IEnumerator<Span> spanEnumerator = spans.GetEnumerator())
@@ -151,11 +153,11 @@ namespace Microsoft.VisualStudio.Text
{
if (snapshot == null)
{
- throw new ArgumentNullException("snapshot");
+ throw new ArgumentNullException(nameof(snapshot));
}
if (spans == null)
{
- throw new ArgumentNullException("spans");
+ throw new ArgumentNullException(nameof(spans));
}
if (spans.Count == 0)
@@ -197,7 +199,7 @@ namespace Microsoft.VisualStudio.Text
{
if (snapshotSpans == null)
{
- throw new ArgumentNullException("snapshotSpans");
+ throw new ArgumentNullException(nameof(snapshotSpans));
}
using (IEnumerator<SnapshotSpan> spanEnumerator = snapshotSpans.GetEnumerator())
@@ -265,7 +267,7 @@ namespace Microsoft.VisualStudio.Text
// TODO: possibly eliminate based on slight usage?
if (snapshotSpans == null)
{
- throw new ArgumentNullException("snapshotSpans");
+ throw new ArgumentNullException(nameof(snapshotSpans));
}
if (snapshotSpans.Count == 0)
@@ -324,7 +326,7 @@ namespace Microsoft.VisualStudio.Text
{
if (snapshot == null)
{
- throw new ArgumentNullException("snapshot");
+ throw new ArgumentNullException(nameof(snapshot));
}
if (span.End > snapshot.Length)
@@ -341,11 +343,11 @@ namespace Microsoft.VisualStudio.Text
{
if (targetSnapshot == null)
{
- throw new ArgumentNullException("targetSnapshot");
+ throw new ArgumentNullException(nameof(targetSnapshot));
}
if (mode < SpanTrackingMode.EdgeExclusive || mode > SpanTrackingMode.Custom)
{
- throw new ArgumentOutOfRangeException("mode");
+ throw new ArgumentOutOfRangeException(nameof(mode));
}
if (this.snapshot == null)
@@ -425,11 +427,11 @@ namespace Microsoft.VisualStudio.Text
{
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.Count == 0)
@@ -465,11 +467,11 @@ namespace Microsoft.VisualStudio.Text
{
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.Count == 0)
@@ -504,11 +506,11 @@ namespace Microsoft.VisualStudio.Text
{
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.Count == 0)
@@ -543,11 +545,11 @@ namespace Microsoft.VisualStudio.Text
{
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.Count == 0)
@@ -581,7 +583,7 @@ namespace Microsoft.VisualStudio.Text
{
if (set == null)
{
- throw new ArgumentNullException("set");
+ throw new ArgumentNullException(nameof(set));
}
else if (set.Count == 0 || this.Count == 0)
{
@@ -636,7 +638,7 @@ namespace Microsoft.VisualStudio.Text
{
if (set == null)
{
- throw new ArgumentNullException("set");
+ throw new ArgumentNullException(nameof(set));
}
else if (set.Count == 0 || this.Count == 0)
{
@@ -742,7 +744,10 @@ namespace Microsoft.VisualStudio.Text
}
else
{
- throw new ArgumentOutOfRangeException("index");
+ // Analyzer has a bug where it is giving a false positive in this location.
+#pragma warning disable CA1065 // Do not raise exceptions in unexpected locations
+ throw new ArgumentOutOfRangeException(nameof(index));
+#pragma warning restore CA1065 // Do not raise exceptions in unexpected locations
}
}
set
@@ -806,11 +811,11 @@ namespace Microsoft.VisualStudio.Text
{
if (array == null)
{
- throw new ArgumentNullException("array");
+ throw new ArgumentNullException(nameof(array));
}
if (arrayIndex < 0 || arrayIndex > array.Length || this.Count > array.Length - arrayIndex)
{
- throw new ArgumentOutOfRangeException("arrayIndex");
+ throw new ArgumentOutOfRangeException(nameof(arrayIndex));
}
if (this.spans != null)
{
@@ -1034,7 +1039,7 @@ namespace Microsoft.VisualStudio.Text
}
else
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
}
set
@@ -1060,11 +1065,11 @@ namespace Microsoft.VisualStudio.Text
{
if (array == null)
{
- throw new ArgumentNullException("array");
+ throw new ArgumentNullException(nameof(array));
}
if (index < 0 || index > array.Length || this.Count > array.Length - index)
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
if (array.Rank != 1)
{
diff --git a/src/Text/Def/TextData/Model/NormalizedSpanCollection.cs b/src/Text/Def/TextData/Model/NormalizedSpanCollection.cs
index aff7bdb..c118d3c 100644
--- a/src/Text/Def/TextData/Model/NormalizedSpanCollection.cs
+++ b/src/Text/Def/TextData/Model/NormalizedSpanCollection.cs
@@ -15,6 +15,7 @@ namespace Microsoft.VisualStudio.Text
/// </summary>
public class NormalizedSpanCollection : ReadOnlyCollection<Span>
{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104", Justification = "Type is readonly")]
public readonly static NormalizedSpanCollection Empty = new NormalizedSpanCollection();
/// <summary>
@@ -59,8 +60,10 @@ namespace Microsoft.VisualStudio.Text
/// <para>This constructor runs in O(N) time, where N = spans.Count.</para></remarks>
/// <exception cref="ArgumentNullException"><paramref name="normalizedSpans"/> is null.</exception>
/// <remarks>This constructor is private so as not to expose the misleading <paramref name="ignored"/> parameter.</remarks>
+#pragma warning disable CA1801 // Parameter ignored is never used
private NormalizedSpanCollection(IList<Span> normalizedSpans, bool ignored)
: base(normalizedSpans)
+#pragma warning restore CA1801 // Parameter ignored is never used
{
}
@@ -93,11 +96,11 @@ namespace Microsoft.VisualStudio.Text
{
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.Count == 0)
@@ -109,7 +112,7 @@ namespace Microsoft.VisualStudio.Text
return left;
}
- List<Span> spans = new List<Span>();
+ var spans = new List<Span>();
int index1 = 0;
int index2 = 0;
@@ -163,11 +166,11 @@ namespace Microsoft.VisualStudio.Text
{
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.Count == 0)
@@ -179,7 +182,7 @@ namespace Microsoft.VisualStudio.Text
return right;
}
- List<Span> spans = new List<Span>();
+ var spans = new List<Span>();
for (int index1 = 0, index2 = 0; (index1 < left.Count) && (index2 < right.Count); )
{
Span span1 = left[index1];
@@ -220,11 +223,11 @@ namespace Microsoft.VisualStudio.Text
{
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.Count == 0)
@@ -236,7 +239,7 @@ namespace Microsoft.VisualStudio.Text
return right;
}
- List<Span> spans = new List<Span>();
+ var spans = new List<Span>();
for (int index1 = 0, index2 = 0; (index1 < left.Count) && (index2 < right.Count) ;)
{
Span span1 = left[index1];
@@ -276,11 +279,11 @@ namespace Microsoft.VisualStudio.Text
{
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.Count == 0)
@@ -292,7 +295,7 @@ namespace Microsoft.VisualStudio.Text
return left;
}
- List<Span> spans = new List<Span>();
+ var spans = new List<Span>();
int index1 = 0;
int index2 = 0;
@@ -362,9 +365,9 @@ namespace Microsoft.VisualStudio.Text
/// <returns><c>true</c> if the two sets are equivalent, otherwise <c>false</c>.</returns>
public static bool operator ==(NormalizedSpanCollection left, NormalizedSpanCollection right)
{
- if (object.ReferenceEquals(left, right))
+ if (ReferenceEquals(left, right))
return true;
- if (object.ReferenceEquals(left, null) || object.ReferenceEquals(right, null))
+ if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
return false;
if (left.Count != right.Count)
@@ -398,7 +401,7 @@ namespace Microsoft.VisualStudio.Text
{
if (set == null)
{
- throw new ArgumentNullException("set");
+ throw new ArgumentNullException(nameof(set));
}
for (int index1 = 0, index2 = 0; (index1 < this.Count) && (index2 < set.Count) ;)
@@ -457,7 +460,7 @@ namespace Microsoft.VisualStudio.Text
{
if (set == null)
{
- throw new ArgumentNullException("set");
+ throw new ArgumentNullException(nameof(set));
}
for (int index1 = 0, index2 = 0; (index1 < this.Count) && (index2 < set.Count); )
@@ -586,10 +589,10 @@ namespace Microsoft.VisualStudio.Text
{
if (spans == null)
{
- throw new ArgumentNullException("spans");
+ throw new ArgumentNullException(nameof(spans));
}
- List<Span> sorted = new List<Span>(spans);
+ var sorted = new List<Span>(spans);
if (sorted.Count <= 1)
{
return sorted;
diff --git a/src/Text/Def/TextData/Model/Projection/ElisionSourceSpansChangedEventArgs.cs b/src/Text/Def/TextData/Model/Projection/ElisionSourceSpansChangedEventArgs.cs
index b7c5136..02a78b3 100644
--- a/src/Text/Def/TextData/Model/Projection/ElisionSourceSpansChangedEventArgs.cs
+++ b/src/Text/Def/TextData/Model/Projection/ElisionSourceSpansChangedEventArgs.cs
@@ -33,11 +33,11 @@ namespace Microsoft.VisualStudio.Text.Projection
{
if (elidedSpans == null)
{
- throw new ArgumentNullException("elidedSpans");
+ throw new ArgumentNullException(nameof(elidedSpans));
}
if (expandedSpans == null)
{
- throw new ArgumentNullException("expandedSpans");
+ throw new ArgumentNullException(nameof(expandedSpans));
}
this.elidedSpans = elidedSpans;
this.expandedSpans = expandedSpans;
diff --git a/src/Text/Def/TextData/Model/Projection/GraphBufferContentTypeChangedEventArgs.cs b/src/Text/Def/TextData/Model/Projection/GraphBufferContentTypeChangedEventArgs.cs
index d977310..b3f2e49 100644
--- a/src/Text/Def/TextData/Model/Projection/GraphBufferContentTypeChangedEventArgs.cs
+++ b/src/Text/Def/TextData/Model/Projection/GraphBufferContentTypeChangedEventArgs.cs
@@ -29,15 +29,15 @@ namespace Microsoft.VisualStudio.Text.Projection
{
if (textBuffer == null)
{
- throw new ArgumentNullException("textBuffer");
+ throw new ArgumentNullException(nameof(textBuffer));
}
if (beforeContentType == null)
{
- throw new ArgumentNullException("beforeContentType");
+ throw new ArgumentNullException(nameof(beforeContentType));
}
if (afterContentType == null)
{
- throw new ArgumentNullException("afterContentType");
+ throw new ArgumentNullException(nameof(afterContentType));
}
this.textBuffer = textBuffer;
this.beforeContentType = beforeContentType;
diff --git a/src/Text/Def/TextData/Model/Projection/GraphBuffersChangedEventArgs.cs b/src/Text/Def/TextData/Model/Projection/GraphBuffersChangedEventArgs.cs
index 98d1ce5..b142651 100644
--- a/src/Text/Def/TextData/Model/Projection/GraphBuffersChangedEventArgs.cs
+++ b/src/Text/Def/TextData/Model/Projection/GraphBuffersChangedEventArgs.cs
@@ -26,11 +26,11 @@ namespace Microsoft.VisualStudio.Text.Projection
{
if (addedBuffers == null)
{
- throw new ArgumentNullException("addedBuffers");
+ throw new ArgumentNullException(nameof(addedBuffers));
}
if (removedBuffers == null)
{
- throw new ArgumentNullException("removedBuffers");
+ throw new ArgumentNullException(nameof(removedBuffers));
}
this.addedBuffers = new ReadOnlyCollection<ITextBuffer>(addedBuffers);
this.removedBuffers = new ReadOnlyCollection<ITextBuffer>(removedBuffers);
diff --git a/src/Text/Def/TextData/Model/Projection/ProjectionSourceBuffersChangedEventArgs.cs b/src/Text/Def/TextData/Model/Projection/ProjectionSourceBuffersChangedEventArgs.cs
index 977ce7e..ea17f1e 100644
--- a/src/Text/Def/TextData/Model/Projection/ProjectionSourceBuffersChangedEventArgs.cs
+++ b/src/Text/Def/TextData/Model/Projection/ProjectionSourceBuffersChangedEventArgs.cs
@@ -44,11 +44,11 @@ namespace Microsoft.VisualStudio.Text.Projection
{
if (addedBuffers == null)
{
- throw new ArgumentNullException("addedBuffers");
+ throw new ArgumentNullException(nameof(addedBuffers));
}
if (removedBuffers == null)
{
- throw new ArgumentNullException("removedBuffers");
+ throw new ArgumentNullException(nameof(removedBuffers));
}
this.addedBuffers = addedBuffers;
this.removedBuffers = removedBuffers;
diff --git a/src/Text/Def/TextData/Model/Projection/ProjectionSourceSpansChangedEventArgs.cs b/src/Text/Def/TextData/Model/Projection/ProjectionSourceSpansChangedEventArgs.cs
index e219725..2497ff7 100644
--- a/src/Text/Def/TextData/Model/Projection/ProjectionSourceSpansChangedEventArgs.cs
+++ b/src/Text/Def/TextData/Model/Projection/ProjectionSourceSpansChangedEventArgs.cs
@@ -40,11 +40,11 @@ namespace Microsoft.VisualStudio.Text.Projection
{
if (insertedSpans == null)
{
- throw new ArgumentNullException("insertedSpans");
+ throw new ArgumentNullException(nameof(insertedSpans));
}
if (deletedSpans == null)
{
- throw new ArgumentNullException("deletedSpans");
+ throw new ArgumentNullException(nameof(deletedSpans));
}
this.insertedSpans = new ReadOnlyCollection<ITrackingSpan>(insertedSpans);
this.deletedSpans = new ReadOnlyCollection<ITrackingSpan>(deletedSpans);
diff --git a/src/Text/Def/TextData/Model/SnapshotPoint.cs b/src/Text/Def/TextData/Model/SnapshotPoint.cs
index 330cc9c..de383a0 100644
--- a/src/Text/Def/TextData/Model/SnapshotPoint.cs
+++ b/src/Text/Def/TextData/Model/SnapshotPoint.cs
@@ -6,14 +6,15 @@ namespace Microsoft.VisualStudio.Text
{
using System;
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
+#pragma warning disable CA1036 // Override methods on comparable types
/// <summary>
/// An immutable text position in a particular text snapshot.
/// </summary>
public struct SnapshotPoint : IComparable<SnapshotPoint>
+#pragma warning restore CA1036 // Override methods on comparable types
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
- // Member must match order in the ctor, otherwise the COM tool gets confused.
- private ITextSnapshot snapshot;
- private int position;
/// <summary>
/// Initializes a new instance of a <see cref="SnapshotPoint"/> with respect to a particular snapshot and position.
@@ -24,32 +25,26 @@ namespace Microsoft.VisualStudio.Text
{
if (snapshot == null)
{
- throw new ArgumentNullException("snapshot");
+ throw new ArgumentNullException(nameof(snapshot));
}
if (position < 0 || position > snapshot.Length)
{
- throw new ArgumentOutOfRangeException("position");
+ throw new ArgumentOutOfRangeException(nameof(position));
}
- this.snapshot = snapshot;
- this.position = position;
+ this.Snapshot = snapshot;
+ this.Position = position;
}
/// <summary>
/// Gets the position of the point.
/// </summary>
/// <value>A non-negative integer less than or equal to the length of the snapshot.</value>
- public int Position
- {
- get { return this.position; }
- }
+ public int Position { get; }
/// <summary>
/// Gets the <see cref="ITextSnapshot"/> to which this snapshot point refers.
/// </summary>
- public ITextSnapshot Snapshot
- {
- get { return this.snapshot; }
- }
+ public ITextSnapshot Snapshot { get; }
/// <summary>
/// Implicitly converts the snapshot point to an integer equal to the position of the snapshot point in the snapshot.
@@ -65,7 +60,7 @@ namespace Microsoft.VisualStudio.Text
/// <returns></returns>
public ITextSnapshotLine GetContainingLine()
{
- return this.snapshot.GetLineFromPosition(this.position);
+ return this.Snapshot.GetLineFromPosition(this.Position);
}
/// <summary>
@@ -75,7 +70,7 @@ namespace Microsoft.VisualStudio.Text
/// <exception cref="ArgumentOutOfRangeException"> if the position of this point is equal to the length of the snapshot.</exception>
public char GetChar()
{
- return this.snapshot[this.position];
+ return this.Snapshot[this.Position];
}
/// <summary>
@@ -88,7 +83,7 @@ namespace Microsoft.VisualStudio.Text
/// <exception cref="ArgumentException"><paramref name="targetSnapshot"/> does not refer to the same <see cref="ITextBuffer"/> as this snapshot point.</exception>
public SnapshotPoint TranslateTo(ITextSnapshot targetSnapshot, PointTrackingMode trackingMode)
{
- if (targetSnapshot == this.snapshot)
+ if (targetSnapshot == this.Snapshot)
{
return this;
}
@@ -96,16 +91,16 @@ namespace Microsoft.VisualStudio.Text
{
if (targetSnapshot == null)
{
- throw new ArgumentNullException("targetSnapshot");
+ throw new ArgumentNullException(nameof(targetSnapshot));
}
- if (targetSnapshot.TextBuffer != this.snapshot.TextBuffer)
+ if (targetSnapshot.TextBuffer != this.Snapshot.TextBuffer)
{
throw new ArgumentException(Strings.InvalidSnapshot);
}
- int targetPosition = targetSnapshot.Version.VersionNumber > this.snapshot.Version.VersionNumber
- ? Tracking.TrackPositionForwardInTime(trackingMode, this.position, this.snapshot.Version, targetSnapshot.Version)
- : Tracking.TrackPositionBackwardInTime(trackingMode, this.position, this.snapshot.Version, targetSnapshot.Version);
+ int targetPosition = targetSnapshot.Version.VersionNumber > this.Snapshot.Version.VersionNumber
+ ? Tracking.TrackPositionForwardInTime(trackingMode, this.Position, this.Snapshot.Version, targetSnapshot.Version)
+ : Tracking.TrackPositionBackwardInTime(trackingMode, this.Position, this.Snapshot.Version, targetSnapshot.Version);
return new SnapshotPoint(targetSnapshot, targetPosition);
}
@@ -116,7 +111,7 @@ namespace Microsoft.VisualStudio.Text
/// </summary>
public override int GetHashCode()
{
- return (this.snapshot != null) ? (this.position.GetHashCode() ^ this.snapshot.GetHashCode()) : 0;
+ return (this.Snapshot != null) ? (this.Position.GetHashCode() ^ this.Snapshot.GetHashCode()) : 0;
}
/// <summary>
@@ -124,19 +119,18 @@ namespace Microsoft.VisualStudio.Text
/// </summary>
public override string ToString()
{
- if (this.snapshot == null)
+ if (this.Snapshot == null)
{
return "uninit";
}
else
{
- string tag;
- this.Snapshot.TextBuffer.Properties.TryGetProperty<string>("tag", out tag);
+ this.Snapshot.TextBuffer.Properties.TryGetProperty("tag", out string tag);
return string.Format(System.Globalization.CultureInfo.CurrentCulture, "{0}_v{1}_{2}_'{3}'",
tag ?? "?",
this.Snapshot.Version.VersionNumber,
- this.position,
- position == this.Snapshot.Length ? "<end>" : this.Snapshot.GetText(position, 1));
+ this.Position,
+ this.Position == this.Snapshot.Length ? "<end>" : this.Snapshot.GetText(this.Position, 1));
}
}
@@ -297,7 +291,7 @@ namespace Microsoft.VisualStudio.Text
throw new ArgumentException(Strings.InvalidSnapshotPoint);
}
- return this.position.CompareTo(other.position);
+ return this.Position.CompareTo(other.Position);
}
#endregion
diff --git a/src/Text/Def/TextData/Model/SnapshotSpan.cs b/src/Text/Def/TextData/Model/SnapshotSpan.cs
index dc71c9e..5d60818 100644
--- a/src/Text/Def/TextData/Model/SnapshotSpan.cs
+++ b/src/Text/Def/TextData/Model/SnapshotSpan.cs
@@ -6,10 +6,12 @@ namespace Microsoft.VisualStudio.Text
{
using System;
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// An immutable text span in a particular text snapshot.
/// </summary>
public struct SnapshotSpan
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
#region Private Members
@@ -30,11 +32,11 @@ namespace Microsoft.VisualStudio.Text
{
if (snapshot == null)
{
- throw new ArgumentNullException("snapshot");
+ throw new ArgumentNullException(nameof(snapshot));
}
if (span.End > snapshot.Length)
{
- throw new ArgumentOutOfRangeException("span");
+ throw new ArgumentOutOfRangeException(nameof(span));
}
this.start = new SnapshotPoint(snapshot, span.Start);
@@ -78,7 +80,7 @@ namespace Microsoft.VisualStudio.Text
}
if (end.Position < start.Position)
{
- throw new ArgumentOutOfRangeException("end");
+ throw new ArgumentOutOfRangeException(nameof(end));
}
this.start = start;
@@ -98,7 +100,7 @@ namespace Microsoft.VisualStudio.Text
if (length < 0 ||
start.Position + length > start.Snapshot.Length)
{
- throw new ArgumentOutOfRangeException("length");
+ throw new ArgumentOutOfRangeException(nameof(length));
}
this.start = start;
@@ -148,7 +150,7 @@ namespace Microsoft.VisualStudio.Text
{
if (targetSnapshot == null)
{
- throw new ArgumentNullException("targetSnapshot");
+ throw new ArgumentNullException(nameof(targetSnapshot));
}
if (targetSnapshot.TextBuffer != this.Start.Snapshot.TextBuffer)
{
@@ -156,8 +158,8 @@ namespace Microsoft.VisualStudio.Text
}
Span targetSpan = targetSnapshot.Version.VersionNumber > this.Snapshot.Version.VersionNumber
- ? Tracking.TrackSpanForwardInTime(spanTrackingMode, Span, this.Snapshot.Version, targetSnapshot.Version)
- : Tracking.TrackSpanBackwardInTime(spanTrackingMode, Span, this.Snapshot.Version, targetSnapshot.Version);
+ ? Tracking.TrackSpanForwardInTime(spanTrackingMode, this.Span, this.Snapshot.Version, targetSnapshot.Version)
+ : Tracking.TrackSpanBackwardInTime(spanTrackingMode, this.Span, this.Snapshot.Version, targetSnapshot.Version);
return new SnapshotSpan(targetSnapshot, targetSpan);
}
@@ -170,7 +172,7 @@ namespace Microsoft.VisualStudio.Text
/// </summary>
public Span Span
{
- get { return new Span(start, length); }
+ get { return new Span(this.start, this.length); }
}
/// <summary>
@@ -180,7 +182,7 @@ namespace Microsoft.VisualStudio.Text
{
get
{
- return start;
+ return this.start;
}
}
@@ -192,7 +194,7 @@ namespace Microsoft.VisualStudio.Text
{
get
{
- return start + length;
+ return this.start + this.length;
}
}
@@ -436,7 +438,7 @@ namespace Microsoft.VisualStudio.Text
else
{
string tag;
- this.Snapshot.TextBuffer.Properties.TryGetProperty<string>("tag", out tag);
+ this.Snapshot.TextBuffer.Properties.TryGetProperty("tag", out tag);
return string.Format(System.Globalization.CultureInfo.CurrentCulture,
"{0}_v{1}_{2}_'{3}'",
tag ?? "?",
@@ -455,7 +457,7 @@ namespace Microsoft.VisualStudio.Text
{
if (obj is SnapshotSpan)
{
- SnapshotSpan other = (SnapshotSpan)obj;
+ var other = (SnapshotSpan)obj;
return other == this;
}
else
diff --git a/src/Text/Def/TextData/Model/Span.cs b/src/Text/Def/TextData/Model/Span.cs
index e7e3940..738f8a5 100644
--- a/src/Text/Def/TextData/Model/Span.cs
+++ b/src/Text/Def/TextData/Model/Span.cs
@@ -6,12 +6,14 @@ namespace Microsoft.VisualStudio.Text
{
using System;
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// An immutable integer interval that describes a range of values from <see cref="Start"/> to <see cref="End"/> that is closed on
/// the left and open on the right: [Start .. End). A zpan is usually applied to an <see cref="ITextSnapshot"/> to denote a span of text,
/// but it is independent of any particular text buffer or snapshot.
/// </summary>
public struct Span
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
#region Private Members
@@ -34,11 +36,11 @@ namespace Microsoft.VisualStudio.Text
{
if (start < 0)
{
- throw new ArgumentOutOfRangeException("start");
+ throw new ArgumentOutOfRangeException(nameof(start));
}
if (start + length < start)
{
- throw new ArgumentOutOfRangeException("length");
+ throw new ArgumentOutOfRangeException(nameof(length));
}
this.start = start;
this.length = length;
diff --git a/src/Text/Def/TextData/Model/TextBufferCreatedEventArgs.cs b/src/Text/Def/TextData/Model/TextBufferCreatedEventArgs.cs
index 25c532e..93e2e69 100644
--- a/src/Text/Def/TextData/Model/TextBufferCreatedEventArgs.cs
+++ b/src/Text/Def/TextData/Model/TextBufferCreatedEventArgs.cs
@@ -24,7 +24,7 @@ namespace Microsoft.VisualStudio.Text
{
if (textBuffer == null)
{
- throw new ArgumentNullException("textBuffer");
+ throw new ArgumentNullException(nameof(textBuffer));
}
TextBuffer = textBuffer;
}
diff --git a/src/Text/Def/TextData/Model/TextContentChangingEventArgs.cs b/src/Text/Def/TextData/Model/TextContentChangingEventArgs.cs
index cb828d8..b431b3c 100644
--- a/src/Text/Def/TextData/Model/TextContentChangingEventArgs.cs
+++ b/src/Text/Def/TextData/Model/TextContentChangingEventArgs.cs
@@ -39,7 +39,7 @@ namespace Microsoft.VisualStudio.Text
{
if (beforeSnapshot == null)
{
- throw new ArgumentNullException("beforeSnapshot");
+ throw new ArgumentNullException(nameof(beforeSnapshot));
}
Canceled = false;
diff --git a/src/Text/Def/TextData/Model/TextImageLine.cs b/src/Text/Def/TextData/Model/TextImageLine.cs
index b7e95c1..de9dbbf 100644
--- a/src/Text/Def/TextData/Model/TextImageLine.cs
+++ b/src/Text/Def/TextData/Model/TextImageLine.cs
@@ -5,6 +5,7 @@
namespace Microsoft.VisualStudio.Text
{
using System;
+ using System.Diagnostics.CodeAnalysis;
/// <summary>
/// Immutable information about a line of text from an <see cref="ITextImage"/>.
@@ -36,12 +37,17 @@ namespace Microsoft.VisualStudio.Text
/// <summary>
/// The <see cref="ITextImage"/> in which the line appears.
/// </summary>
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "Type is readonly")]
+#pragma warning disable CA1051 // Do not declare visible instance fields
public readonly ITextImage Image;
+#pragma warning restore CA1051 // Do not declare visible instance fields
/// <summary>
/// The extent of the line, excluding any line break characters.
/// </summary>
+#pragma warning disable CA1051 // Do not declare visible instance fields
public readonly Span Extent;
+#pragma warning restore CA1051 // Do not declare visible instance fields
/// <summary>
/// The extent of the line, including any line break characters.
@@ -51,7 +57,9 @@ namespace Microsoft.VisualStudio.Text
/// <summary>
/// The 0-origin line number of the line.
/// </summary>
+#pragma warning disable CA1051 // Do not declare visible instance fields
public readonly int LineNumber;
+#pragma warning restore CA1051 // Do not declare visible instance fields
/// <summary>
/// The position of the first character in the line.
@@ -87,7 +95,9 @@ namespace Microsoft.VisualStudio.Text
/// <summary>
/// Length of line break characters (always falls in the range [0..2]).
/// </summary>
+#pragma warning disable CA1051 // Do not declare visible instance fields
public readonly int LineBreakLength;
+#pragma warning restore CA1051 // Do not declare visible instance fields
/// <summary>
/// The text of the line, excluding any line break characters.
diff --git a/src/Text/Def/TextData/Model/TextSnapshotChangedEventArgs.cs b/src/Text/Def/TextData/Model/TextSnapshotChangedEventArgs.cs
index 4581044..9582fb4 100644
--- a/src/Text/Def/TextData/Model/TextSnapshotChangedEventArgs.cs
+++ b/src/Text/Def/TextData/Model/TextSnapshotChangedEventArgs.cs
@@ -31,11 +31,11 @@ namespace Microsoft.VisualStudio.Text
{
if (beforeSnapshot == null)
{
- throw new ArgumentNullException("beforeSnapshot");
+ throw new ArgumentNullException(nameof(beforeSnapshot));
}
if (afterSnapshot == null)
{
- throw new ArgumentNullException("afterSnapshot");
+ throw new ArgumentNullException(nameof(afterSnapshot));
}
this.before = beforeSnapshot;
this.after = afterSnapshot;
diff --git a/src/Text/Def/TextData/Model/TextSnapshotToTextReader.cs b/src/Text/Def/TextData/Model/TextSnapshotToTextReader.cs
index 35f630e..7befcc3 100644
--- a/src/Text/Def/TextData/Model/TextSnapshotToTextReader.cs
+++ b/src/Text/Def/TextData/Model/TextSnapshotToTextReader.cs
@@ -79,13 +79,13 @@ namespace Microsoft.VisualStudio.Text
if (_currentPosition == -1)
throw new ObjectDisposedException("TextSnapshotToTextReader");
if (buffer == null)
- throw new ArgumentNullException("buffer");
+ throw new ArgumentNullException(nameof(buffer));
if (index < 0)
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
if (count < 0)
- throw new ArgumentOutOfRangeException("count");
+ throw new ArgumentOutOfRangeException(nameof(count));
if (((index + count) < 0) || ((index + count) > buffer.Length))
- throw new ArgumentOutOfRangeException("count");
+ throw new ArgumentOutOfRangeException(nameof(count));
int charactersToRead = System.Math.Min(_snapshot.Length - _currentPosition, count);
_snapshot.CopyTo(_currentPosition, buffer, index, charactersToRead);
@@ -159,7 +159,7 @@ namespace Microsoft.VisualStudio.Text
public TextSnapshotToTextReader(ITextSnapshot textSnapshot)
{
if (textSnapshot == null)
- throw new ArgumentNullException("textSnapshot");
+ throw new ArgumentNullException(nameof(textSnapshot));
_snapshot = textSnapshot;
}
diff --git a/src/Text/Def/TextData/Model/Tracking.cs b/src/Text/Def/TextData/Model/Tracking.cs
index 3058af0..8f515b2 100644
--- a/src/Text/Def/TextData/Model/Tracking.cs
+++ b/src/Text/Def/TextData/Model/Tracking.cs
@@ -3,7 +3,6 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
//
using System;
-using System.Collections.Generic;
using System.Diagnostics;
namespace Microsoft.VisualStudio.Text
@@ -20,15 +19,15 @@ namespace Microsoft.VisualStudio.Text
{
if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative)
{
- throw new ArgumentOutOfRangeException("trackingMode");
+ throw new ArgumentOutOfRangeException(nameof(trackingMode));
}
if (currentVersion == null)
{
- throw new ArgumentNullException("currentVersion");
+ throw new ArgumentNullException(nameof(currentVersion));
}
if (targetVersion == null)
{
- throw new ArgumentNullException("targetVersion");
+ throw new ArgumentNullException(nameof(targetVersion));
}
if (targetVersion.TextBuffer != currentVersion.TextBuffer)
{
@@ -36,11 +35,11 @@ namespace Microsoft.VisualStudio.Text
}
if (targetVersion.VersionNumber < currentVersion.VersionNumber)
{
- throw new ArgumentOutOfRangeException("targetVersion");
+ throw new ArgumentOutOfRangeException(nameof(targetVersion));
}
if (currentPosition < 0 || currentPosition > currentVersion.Length)
{
- throw new ArgumentOutOfRangeException("currentPosition");
+ throw new ArgumentOutOfRangeException(nameof(currentPosition));
}
// track forward in time
@@ -64,15 +63,15 @@ namespace Microsoft.VisualStudio.Text
{
if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative)
{
- throw new ArgumentOutOfRangeException("trackingMode");
+ throw new ArgumentOutOfRangeException(nameof(trackingMode));
}
if (currentVersion == null)
{
- throw new ArgumentNullException("currentVersion");
+ throw new ArgumentNullException(nameof(currentVersion));
}
if (targetVersion == null)
{
- throw new ArgumentNullException("targetVersion");
+ throw new ArgumentNullException(nameof(targetVersion));
}
if (targetVersion.Identifier != currentVersion.Identifier)
{
@@ -80,11 +79,11 @@ namespace Microsoft.VisualStudio.Text
}
if (targetVersion.VersionNumber < currentVersion.VersionNumber)
{
- throw new ArgumentOutOfRangeException("targetVersion");
+ throw new ArgumentOutOfRangeException(nameof(targetVersion));
}
if (currentPosition < 0 || currentPosition > currentVersion.Length)
{
- throw new ArgumentOutOfRangeException("currentPosition");
+ throw new ArgumentOutOfRangeException(nameof(currentPosition));
}
// track forward in time
@@ -226,15 +225,15 @@ namespace Microsoft.VisualStudio.Text
{
if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative)
{
- throw new ArgumentOutOfRangeException("trackingMode");
+ throw new ArgumentOutOfRangeException(nameof(trackingMode));
}
if (currentVersion == null)
{
- throw new ArgumentNullException("currentVersion");
+ throw new ArgumentNullException(nameof(currentVersion));
}
if (targetVersion == null)
{
- throw new ArgumentNullException("targetVersion");
+ throw new ArgumentNullException(nameof(targetVersion));
}
if (targetVersion.TextBuffer != currentVersion.TextBuffer)
{
@@ -242,11 +241,11 @@ namespace Microsoft.VisualStudio.Text
}
if (targetVersion.VersionNumber > currentVersion.VersionNumber)
{
- throw new ArgumentOutOfRangeException("targetVersion");
+ throw new ArgumentOutOfRangeException(nameof(targetVersion));
}
if (currentPosition < 0 || currentPosition > currentVersion.Length)
{
- throw new ArgumentOutOfRangeException("currentPosition");
+ throw new ArgumentOutOfRangeException(nameof(currentPosition));
}
// track backwards in time
@@ -278,15 +277,15 @@ namespace Microsoft.VisualStudio.Text
{
if (trackingMode < PointTrackingMode.Positive || trackingMode > PointTrackingMode.Negative)
{
- throw new ArgumentOutOfRangeException("trackingMode");
+ throw new ArgumentOutOfRangeException(nameof(trackingMode));
}
if (currentVersion == null)
{
- throw new ArgumentNullException("currentVersion");
+ throw new ArgumentNullException(nameof(currentVersion));
}
if (targetVersion == null)
{
- throw new ArgumentNullException("targetVersion");
+ throw new ArgumentNullException(nameof(targetVersion));
}
if (targetVersion.Identifier != currentVersion.Identifier)
{
@@ -294,11 +293,11 @@ namespace Microsoft.VisualStudio.Text
}
if (targetVersion.VersionNumber > currentVersion.VersionNumber)
{
- throw new ArgumentOutOfRangeException("targetVersion");
+ throw new ArgumentOutOfRangeException(nameof(targetVersion));
}
if (currentPosition < 0 || currentPosition > currentVersion.Length)
{
- throw new ArgumentOutOfRangeException("currentPosition");
+ throw new ArgumentOutOfRangeException(nameof(currentPosition));
}
// track backwards in time
@@ -402,15 +401,15 @@ namespace Microsoft.VisualStudio.Text
{
if (trackingMode < SpanTrackingMode.EdgeExclusive || trackingMode > SpanTrackingMode.Custom)
{
- throw new ArgumentOutOfRangeException("trackingMode");
+ throw new ArgumentOutOfRangeException(nameof(trackingMode));
}
if (currentVersion == null)
{
- throw new ArgumentNullException("currentVersion");
+ throw new ArgumentNullException(nameof(currentVersion));
}
if (targetVersion == null)
{
- throw new ArgumentNullException("targetVersion");
+ throw new ArgumentNullException(nameof(targetVersion));
}
if (targetVersion.TextBuffer != currentVersion.TextBuffer)
{
@@ -418,11 +417,11 @@ namespace Microsoft.VisualStudio.Text
}
if (span.End > currentVersion.Length)
{
- throw new ArgumentOutOfRangeException("span");
+ throw new ArgumentOutOfRangeException(nameof(span));
}
if (targetVersion.VersionNumber < currentVersion.VersionNumber)
{
- throw new ArgumentOutOfRangeException("targetVersion");
+ throw new ArgumentOutOfRangeException(nameof(targetVersion));
}
int resultStart =
@@ -444,15 +443,15 @@ namespace Microsoft.VisualStudio.Text
{
if (trackingMode < SpanTrackingMode.EdgeExclusive || trackingMode > SpanTrackingMode.Custom)
{
- throw new ArgumentOutOfRangeException("trackingMode");
+ throw new ArgumentOutOfRangeException(nameof(trackingMode));
}
if (currentVersion == null)
{
- throw new ArgumentNullException("currentVersion");
+ throw new ArgumentNullException(nameof(currentVersion));
}
if (targetVersion == null)
{
- throw new ArgumentNullException("targetVersion");
+ throw new ArgumentNullException(nameof(targetVersion));
}
if (targetVersion.Identifier != currentVersion.Identifier)
{
@@ -460,11 +459,11 @@ namespace Microsoft.VisualStudio.Text
}
if (span.End > currentVersion.Length)
{
- throw new ArgumentOutOfRangeException("span");
+ throw new ArgumentOutOfRangeException(nameof(span));
}
if (targetVersion.VersionNumber < currentVersion.VersionNumber)
{
- throw new ArgumentOutOfRangeException("targetVersion");
+ throw new ArgumentOutOfRangeException(nameof(targetVersion));
}
int resultStart =
@@ -489,15 +488,15 @@ namespace Microsoft.VisualStudio.Text
{
if (trackingMode < SpanTrackingMode.EdgeExclusive || trackingMode > SpanTrackingMode.Custom)
{
- throw new ArgumentOutOfRangeException("trackingMode");
+ throw new ArgumentOutOfRangeException(nameof(trackingMode));
}
if (currentVersion == null)
{
- throw new ArgumentNullException("currentVersion");
+ throw new ArgumentNullException(nameof(currentVersion));
}
if (targetVersion == null)
{
- throw new ArgumentNullException("targetVersion");
+ throw new ArgumentNullException(nameof(targetVersion));
}
if (targetVersion.TextBuffer != currentVersion.TextBuffer)
{
@@ -505,11 +504,11 @@ namespace Microsoft.VisualStudio.Text
}
if (span.End > currentVersion.Length)
{
- throw new ArgumentOutOfRangeException("span");
+ throw new ArgumentOutOfRangeException(nameof(span));
}
if (targetVersion.VersionNumber > currentVersion.VersionNumber)
{
- throw new ArgumentOutOfRangeException("targetVersion");
+ throw new ArgumentOutOfRangeException(nameof(targetVersion));
}
int resultStart =
@@ -533,15 +532,15 @@ namespace Microsoft.VisualStudio.Text
{
if (trackingMode < SpanTrackingMode.EdgeExclusive || trackingMode > SpanTrackingMode.Custom)
{
- throw new ArgumentOutOfRangeException("trackingMode");
+ throw new ArgumentOutOfRangeException(nameof(trackingMode));
}
if (currentVersion == null)
{
- throw new ArgumentNullException("currentVersion");
+ throw new ArgumentNullException(nameof(currentVersion));
}
if (targetVersion == null)
{
- throw new ArgumentNullException("targetVersion");
+ throw new ArgumentNullException(nameof(targetVersion));
}
if (targetVersion.Identifier != currentVersion.Identifier)
{
@@ -549,11 +548,11 @@ namespace Microsoft.VisualStudio.Text
}
if (span.End > currentVersion.Length)
{
- throw new ArgumentOutOfRangeException("span");
+ throw new ArgumentOutOfRangeException(nameof(span));
}
if (targetVersion.VersionNumber > currentVersion.VersionNumber)
{
- throw new ArgumentOutOfRangeException("targetVersion");
+ throw new ArgumentOutOfRangeException(nameof(targetVersion));
}
int resultStart =
diff --git a/src/Text/Def/TextData/Model/VersionedPosition.cs b/src/Text/Def/TextData/Model/VersionedPosition.cs
index 8f9e036..7b921b6 100644
--- a/src/Text/Def/TextData/Model/VersionedPosition.cs
+++ b/src/Text/Def/TextData/Model/VersionedPosition.cs
@@ -5,14 +5,19 @@
namespace Microsoft.VisualStudio.Text
{
using System;
+ using System.Diagnostics.CodeAnalysis;
/// <summary>
/// Describes a location in a specific <see cref="ITextImageVersion"/>.
/// </summary>
public struct VersionedPosition : IEquatable<VersionedPosition>
{
+
+#pragma warning disable CA1051 // Do not declare visible instance fields
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "Type is readonly")]
public readonly ITextImageVersion Version;
public readonly int Position;
+#pragma warning restore CA1051 // Do not declare visible instance fields
public readonly static VersionedPosition Invalid = new VersionedPosition();
@@ -82,7 +87,7 @@ namespace Microsoft.VisualStudio.Text
{
return (this.Version == null)
? nameof(Invalid)
- : string.Format(System.Globalization.CultureInfo.CurrentCulture, "v{1}_{2}",
+ : string.Format(System.Globalization.CultureInfo.CurrentCulture, "v{0}_{1}",
this.Version.VersionNumber,
this.Position);
}
diff --git a/src/Text/Def/TextData/Model/VersionedSpan.cs b/src/Text/Def/TextData/Model/VersionedSpan.cs
index 67de693..75f589d 100644
--- a/src/Text/Def/TextData/Model/VersionedSpan.cs
+++ b/src/Text/Def/TextData/Model/VersionedSpan.cs
@@ -5,14 +5,18 @@
namespace Microsoft.VisualStudio.Text
{
using System;
+ using System.Diagnostics.CodeAnalysis;
/// <summary>
/// Describes a span in a specific <see cref="ITextImageVersion"/>.
/// </summary>
public struct VersionedSpan : IEquatable<VersionedSpan>
{
+#pragma warning disable CA1051 // Do not declare visible instance fields
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "Type is readonly")]
public readonly ITextImageVersion Version;
public readonly Span Span;
+#pragma warning disable CA1051 // Do not declare visible instance fields
public readonly static VersionedSpan Invalid = new VersionedSpan();
@@ -82,7 +86,7 @@ namespace Microsoft.VisualStudio.Text
{
return (this.Version == null)
? nameof(Invalid)
- : string.Format(System.Globalization.CultureInfo.CurrentCulture, "v{1}_{2}",
+ : string.Format(System.Globalization.CultureInfo.CurrentCulture, "v{0}_{1}",
this.Version.VersionNumber,
this.Span);
}
diff --git a/src/Text/Def/TextData/TextData.csproj b/src/Text/Def/TextData/TextData.csproj
index a993f1f..e4a64d4 100644
--- a/src/Text/Def/TextData/TextData.csproj
+++ b/src/Text/Def/TextData/TextData.csproj
@@ -4,8 +4,6 @@
<AssemblyName>Microsoft.VisualStudio.Text.Data</AssemblyName>
<TargetFramework>net46</TargetFramework>
<RootNamespace>Microsoft.VisualStudio.Text</RootNamespace>
- <NonShipping>false</NonShipping>
- <IsPackable>true</IsPackable>
<PushToPublicFeed>true</PushToPublicFeed>
<NoWarn>649;436;$(NoWarn)</NoWarn>
<AssemblyAttributeClsCompliant>true</AssemblyAttributeClsCompliant>
@@ -15,8 +13,7 @@
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.VisualStudio.Threading" Version="$(MicrosoftVisualStudioThreadingVersion)" />
- <PackageReference Include="Microsoft.VisualStudio.Validation" Version="$(MicrosoftVisualStudioValidationVersion)" />
+ <PackageReference Include="Microsoft.VisualStudio.Threading" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Core\Def\CoreUtility.csproj" />
diff --git a/src/Text/Def/TextLogic/Classification/ClassificationSpan.cs b/src/Text/Def/TextLogic/Classification/ClassificationSpan.cs
index e952105..e8cd90a 100644
--- a/src/Text/Def/TextLogic/Classification/ClassificationSpan.cs
+++ b/src/Text/Def/TextLogic/Classification/ClassificationSpan.cs
@@ -29,7 +29,7 @@ namespace Microsoft.VisualStudio.Text.Classification
{
if (classification == null)
{
- throw new ArgumentNullException("classification");
+ throw new ArgumentNullException(nameof(classification));
}
this.span = span;
this.classification = classification;
diff --git a/src/Text/Def/TextLogic/Classification/ClassificationTypeAttribute.cs b/src/Text/Def/TextLogic/Classification/ClassificationTypeAttribute.cs
index 21bd9fa..56b3bde 100644
--- a/src/Text/Def/TextLogic/Classification/ClassificationTypeAttribute.cs
+++ b/src/Text/Def/TextLogic/Classification/ClassificationTypeAttribute.cs
@@ -45,11 +45,11 @@ namespace Microsoft.VisualStudio.Text.Classification
{
if (value == null)
{
- throw new ArgumentNullException("value");
+ throw new ArgumentNullException(nameof(value));
}
if (string.IsNullOrEmpty(value))
{
- throw new ArgumentOutOfRangeException("value");
+ throw new ArgumentOutOfRangeException(nameof(value));
}
_name = value;
diff --git a/src/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs b/src/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs
index 991f01c..d1e7af1 100644
--- a/src/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs
+++ b/src/Text/Def/TextLogic/EditorOptions/DefaultOptions.cs
@@ -22,7 +22,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsConvertTabsToSpacesEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue(DefaultOptions.ConvertTabsToSpacesOptionId);
}
@@ -35,7 +35,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static int GetTabSize(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue(DefaultOptions.TabSizeOptionId);
}
@@ -48,7 +48,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static int GetIndentSize(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue(DefaultOptions.IndentSizeOptionId);
}
@@ -61,7 +61,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool GetReplicateNewLineCharacter(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue(DefaultOptions.ReplicateNewLineCharacterOptionId);
}
@@ -74,7 +74,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static string GetNewLineCharacter(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue(DefaultOptions.NewLineCharacterOptionId);
}
@@ -87,7 +87,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool GetTrimTrailingWhieSpace(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue(DefaultOptions.TrimTrailingWhiteSpaceOptionId);
}
@@ -100,11 +100,24 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool GetInsertFinalNewLine(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue(DefaultOptions.InsertFinalNewLineOptionId);
}
+ /// <summary>
+ /// Determines appearance category for tooltips originating in this view
+ /// </summary>
+ /// <param name="options">The <see cref="IEditorOptions"/>.</param>
+ /// <returns>A string containing the appearance category for tooltips originating in this view.</returns>
+ public static string GetTooltipAppearanceCategory(this IEditorOptions options)
+ {
+ if (options == null)
+ throw new ArgumentNullException(nameof(options));
+
+ return options.GetOptionValue(DefaultOptions.TooltipAppearanceCategoryOptionId);
+ }
+
#endregion
}
}
@@ -185,6 +198,12 @@ namespace Microsoft.VisualStudio.Text.Editor
public static readonly EditorOptionKey<bool> InsertFinalNewLineOptionId = new EditorOptionKey<bool>(InsertFinalNewLineOptionName);
public const string InsertFinalNewLineOptionName = "InsertFinalNewLine";
+ /// <summary>
+ /// The default option that determines appearance category for tooltips originating in this view.
+ /// </summary>
+ public static readonly EditorOptionKey<string> TooltipAppearanceCategoryOptionId = new EditorOptionKey<string>(TooltipAppearanceCategoryOptionName);
+ public const string TooltipAppearanceCategoryOptionName = "TooltipAppearanceCategory";
+
#endregion
}
@@ -371,5 +390,23 @@ namespace Microsoft.VisualStudio.Text.Editor
public override EditorOptionKey<bool> Key { get { return DefaultOptions.InsertFinalNewLineOptionId; } }
}
+ /// <summary>
+ /// The option definition that determines whether to insert a final newline.
+ /// </summary>
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(DefaultOptions.TooltipAppearanceCategoryOptionName)]
+ public sealed class TooltipAppearanceCategory : EditorOptionDefinition<string>
+ {
+ /// <summary>
+ /// Gets the default value ("text").
+ /// </summary>
+ public override string Default { get => "text"; }
+
+ /// <summary>
+ /// Gets the editor option key.
+ /// </summary>
+ public override EditorOptionKey<string> Key { get { return DefaultOptions.TooltipAppearanceCategoryOptionId; } }
+ }
+
#endregion
}
diff --git a/src/Text/Def/TextLogic/EditorOptions/DeferCreationAttribute.cs b/src/Text/Def/TextLogic/EditorOptions/DeferCreationAttribute.cs
index 37ae443..ead82ee 100644
--- a/src/Text/Def/TextLogic/EditorOptions/DeferCreationAttribute.cs
+++ b/src/Text/Def/TextLogic/EditorOptions/DeferCreationAttribute.cs
@@ -37,7 +37,7 @@ namespace Microsoft.VisualStudio.Text.Editor
{
if (value == null)
{
- throw new ArgumentNullException("value");
+ throw new ArgumentNullException(nameof(value));
}
this.optionName = value;
}
diff --git a/src/Text/Def/TextLogic/EditorOptions/EditorOptionDefinition.cs b/src/Text/Def/TextLogic/EditorOptions/EditorOptionDefinition.cs
index 7894122..ab99820 100644
--- a/src/Text/Def/TextLogic/EditorOptions/EditorOptionDefinition.cs
+++ b/src/Text/Def/TextLogic/EditorOptions/EditorOptionDefinition.cs
@@ -68,8 +68,8 @@ namespace Microsoft.VisualStudio.Text.Editor
/// <returns><c>true</c> if the two objects are the same, otherwise <c>false</c>.</returns>
public override bool Equals(object obj)
{
- EditorOptionDefinition other = obj as EditorOptionDefinition;
- return other != null && other.Name == this.Name;
+ var other = obj as EditorOptionDefinition;
+ return other != null && string.Equals(other.Name, this.Name, StringComparison.Ordinal);
}
/// <summary>
@@ -97,12 +97,12 @@ namespace Microsoft.VisualStudio.Text.Editor
/// <summary>
/// Gets the name of the option.
/// </summary>
- public sealed override string Name { get { return Key.Name; } }
+ public sealed override string Name { get { return this.Key.Name; } }
/// <summary>
/// Gets the default value of the option.
/// </summary>
- public sealed override object DefaultValue { get { return Default; } }
+ public sealed override object DefaultValue { get { return this.Default; } }
/// <summary>Determines whether the proposed value is valid.
/// </summary>
@@ -113,10 +113,8 @@ namespace Microsoft.VisualStudio.Text.Editor
/// The implementer of this method may modify the value.</remarks>
public sealed override bool IsValid(ref object proposedValue)
{
- if (proposedValue is T)
+ if (proposedValue is T value)
{
- T value = (T)proposedValue;
-
var result = IsValid(ref value);
proposedValue = value;
diff --git a/src/Text/Def/TextLogic/EditorOptions/EditorOptionKey.cs b/src/Text/Def/TextLogic/EditorOptions/EditorOptionKey.cs
index 4c9f1cf..cda9259 100644
--- a/src/Text/Def/TextLogic/EditorOptions/EditorOptionKey.cs
+++ b/src/Text/Def/TextLogic/EditorOptions/EditorOptionKey.cs
@@ -4,26 +4,24 @@
//
namespace Microsoft.VisualStudio.Text.Editor
{
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Represents a type-safe key for editor options.
/// </summary>
/// <typeparam name="T">The type of the option value.</typeparam>
public struct EditorOptionKey<T>
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
- #region Private data
- private string _name;
- #endregion
-
/// <summary>
/// Initializes a new instance of <see cref="EditorOptionKey&lt;T&gt;"/>.
/// </summary>
/// <param name="name">The name of the option key.</param>
- public EditorOptionKey(string name) { _name = name; }
+ public EditorOptionKey(string name) { this.Name = name; }
/// <summary>
/// Gets the name of this key.
/// </summary>
- public string Name { get { return _name; } }
+ public string Name { get; }
#region Object overrides
@@ -36,8 +34,8 @@ namespace Microsoft.VisualStudio.Text.Editor
{
if (obj is EditorOptionKey<T>)
{
- EditorOptionKey<T> other = (EditorOptionKey<T>)obj;
- return other.Name == this.Name;
+ var other = (EditorOptionKey<T>)obj;
+ return string.Equals(other.Name, this.Name, System.StringComparison.Ordinal);
}
return false;
@@ -66,7 +64,7 @@ namespace Microsoft.VisualStudio.Text.Editor
/// </summary>
public static bool operator ==(EditorOptionKey<T> left, EditorOptionKey<T> right)
{
- return left.Name == right.Name;
+ return string.Equals(left.Name, right.Name, System.StringComparison.Ordinal);
}
/// <summary>
diff --git a/src/Text/Def/TextLogic/Find/FindData.cs b/src/Text/Def/TextLogic/Find/FindData.cs
index fe96bcb..c0a5cd2 100644
--- a/src/Text/Def/TextLogic/Find/FindData.cs
+++ b/src/Text/Def/TextLogic/Find/FindData.cs
@@ -6,15 +6,15 @@ using System;
namespace Microsoft.VisualStudio.Text.Operations
{
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Represents the set of data used in a search by the <see cref="ITextSearchService"/>.
/// </summary>
public struct FindData
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
private string _searchString;
private ITextSnapshot _textSnapshotToSearch;
- private FindOptions _findOptions;
- private ITextStructureNavigator _textStructureNavigator;
/// <summary>
/// Initializes a new instance of <see cref="FindData"/> with the specified search pattern, text snapshot,
@@ -30,22 +30,17 @@ namespace Microsoft.VisualStudio.Text.Operations
{
if (searchPattern == null)
{
- throw new ArgumentNullException("searchPattern");
+ throw new ArgumentNullException(nameof(searchPattern));
}
if (searchPattern.Length == 0)
{
- throw new ArgumentOutOfRangeException("searchPattern");
- }
-
- if (textSnapshot == null)
- {
- throw new ArgumentNullException("textSnapshot");
+ throw new ArgumentOutOfRangeException(nameof(searchPattern));
}
_searchString = searchPattern;
- _textSnapshotToSearch = textSnapshot;
- _findOptions = findOptions;
- _textStructureNavigator = textStructureNavigator;
+ _textSnapshotToSearch = textSnapshot ?? throw new ArgumentNullException(nameof(textSnapshot));
+ FindOptions = findOptions;
+ TextStructureNavigator = textStructureNavigator;
}
/// <summary>
@@ -63,8 +58,8 @@ namespace Microsoft.VisualStudio.Text.Operations
{
_searchString = null;
_textSnapshotToSearch = textSnapshot;
- _findOptions = FindOptions.None;
- _textStructureNavigator = null;
+ FindOptions = FindOptions.None;
+ TextStructureNavigator = null;
}
/// <summary>
@@ -79,11 +74,11 @@ namespace Microsoft.VisualStudio.Text.Operations
{
if (value == null)
{
- throw new ArgumentNullException("value");
+ throw new ArgumentNullException(nameof(value));
}
if (value.Length == 0)
{
- throw new ArgumentOutOfRangeException("value");
+ throw new ArgumentOutOfRangeException(nameof(value));
}
_searchString = value;
}
@@ -100,10 +95,10 @@ namespace Microsoft.VisualStudio.Text.Operations
{
FindData other = (FindData)obj;
- return (_searchString == other._searchString) &&
- (_findOptions == other._findOptions) &&
+ return (string.Equals(_searchString, other._searchString, StringComparison.Ordinal)) &&
+ (FindOptions == other.FindOptions) &&
object.ReferenceEquals(_textSnapshotToSearch, other._textSnapshotToSearch) &&
- object.ReferenceEquals(_textStructureNavigator, other._textStructureNavigator);
+ object.ReferenceEquals(TextStructureNavigator, other.TextStructureNavigator);
}
return false;
}
@@ -151,11 +146,7 @@ namespace Microsoft.VisualStudio.Text.Operations
/// <summary>
/// Gets or sets the options that are used for the search.
/// </summary>
- public FindOptions FindOptions
- {
- get { return _findOptions; }
- set { _findOptions = value; }
- }
+ public FindOptions FindOptions { get; set; }
/// <summary>
/// Gets or sets the <see cref="ITextSnapshot"/> on which to perform the search.
@@ -166,21 +157,13 @@ namespace Microsoft.VisualStudio.Text.Operations
get { return _textSnapshotToSearch; }
set
{
- if (value == null)
- {
- throw new ArgumentNullException("value");
- }
- _textSnapshotToSearch = value;
+ _textSnapshotToSearch = value ?? throw new ArgumentNullException(nameof(value));
}
}
/// <summary>
/// Gets or sets the <see cref="ITextStructureNavigator"/> to use in determining word boundaries.
/// </summary>
- public ITextStructureNavigator TextStructureNavigator
- {
- get { return _textStructureNavigator; }
- set { _textStructureNavigator = value; }
- }
+ public ITextStructureNavigator TextStructureNavigator { get; set; }
}
}
diff --git a/src/Text/Def/TextLogic/Find/ITextSearchService2.cs b/src/Text/Def/TextLogic/Find/ITextSearchService2.cs
index 905d0b3..226683e 100644
--- a/src/Text/Def/TextLogic/Find/ITextSearchService2.cs
+++ b/src/Text/Def/TextLogic/Find/ITextSearchService2.cs
@@ -65,7 +65,7 @@ namespace Microsoft.VisualStudio.Text.Operations
SnapshotSpan? Find(SnapshotSpan searchRange, SnapshotPoint startingPosition, string searchPattern, FindOptions options);
/// <summary>
- /// Searches for the next occurence of <paramref name="searchPattern"/> and sets <paramref name="expandedReplacePattern"/> to the result of
+ /// Searches for the next occurrence of <paramref name="searchPattern"/> and sets <paramref name="expandedReplacePattern"/> to the result of
/// the text replacement.
/// </summary>
/// <param name="startingPosition">
@@ -101,7 +101,7 @@ namespace Microsoft.VisualStudio.Text.Operations
SnapshotSpan? FindForReplace(SnapshotPoint startingPosition, string searchPattern, string replacePattern, FindOptions options, out string expandedReplacePattern);
/// <summary>
- /// Searches for the next occurence of <paramref name="searchPattern"/> and sets <paramref name="expandedReplacePattern"/> to the result of
+ /// Searches for the next occurrence of <paramref name="searchPattern"/> and sets <paramref name="expandedReplacePattern"/> to the result of
/// the text replacement.
/// </summary>
/// <param name="searchRange">
@@ -136,7 +136,7 @@ namespace Microsoft.VisualStudio.Text.Operations
SnapshotSpan? FindForReplace(SnapshotSpan searchRange, string searchPattern, string replacePattern, FindOptions options, out string expandedReplacePattern);
/// <summary>
- /// Finds all occurences of the <paramref name="searchPattern"/> in <paramref name="searchRange"/>.
+ /// Finds all occurrences of the <paramref name="searchPattern"/> in <paramref name="searchRange"/>.
/// </summary>
/// <param name="searchRange">
/// The range to search in.
@@ -148,7 +148,7 @@ namespace Microsoft.VisualStudio.Text.Operations
/// The options to use while performing the search operation.
/// </param>
/// <returns>
- /// An <see cref="IEnumerable{SnapshotSpan}"/> containing all occurences of the <paramref name="searchPattern"/>.
+ /// An <see cref="IEnumerable{SnapshotSpan}"/> containing all occurrences of the <paramref name="searchPattern"/>.
/// </returns>
/// <remarks>
/// This method is safe to execute on any thread.
@@ -156,7 +156,7 @@ namespace Microsoft.VisualStudio.Text.Operations
IEnumerable<SnapshotSpan> FindAll(SnapshotSpan searchRange, string searchPattern, FindOptions options);
/// <summary>
- /// Finds all occurences of the <paramref name="searchPattern"/> in <paramref name="searchRange"/> starting from
+ /// Finds all occurrences of the <paramref name="searchPattern"/> in <paramref name="searchRange"/> starting from
/// <paramref name="startingPosition"/>.
/// </summary>
/// <param name="searchRange">
@@ -172,7 +172,7 @@ namespace Microsoft.VisualStudio.Text.Operations
/// The options to use while performing the search operation.
/// </param>
/// <returns>
- /// An <see cref="IEnumerable{SnapshotSpan}"/> containing all occurences of the <paramref name="searchPattern"/>.
+ /// An <see cref="IEnumerable{SnapshotSpan}"/> containing all occurrences of the <paramref name="searchPattern"/>.
/// </returns>
/// <remarks>
/// This method is safe to execute on any thread.
@@ -180,7 +180,7 @@ namespace Microsoft.VisualStudio.Text.Operations
IEnumerable<SnapshotSpan> FindAll(SnapshotSpan searchRange, SnapshotPoint startingPosition, string searchPattern, FindOptions options);
/// <summary>
- /// Searches for all occurences of the <paramref name="searchPattern"/> and calculates all
+ /// Searches for all occurrences of the <paramref name="searchPattern"/> and calculates all
/// the corresponding replacement results for every match according to the <paramref name="replacePattern"/>.
/// </summary>
/// <param name="searchRange">
diff --git a/src/Text/Def/TextLogic/Navigation/TextExtent.cs b/src/Text/Def/TextLogic/Navigation/TextExtent.cs
index 1d416f6..8ca62e4 100644
--- a/src/Text/Def/TextLogic/Navigation/TextExtent.cs
+++ b/src/Text/Def/TextLogic/Navigation/TextExtent.cs
@@ -4,10 +4,12 @@
//
namespace Microsoft.VisualStudio.Text.Operations
{
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Represents the extent of a word.
/// </summary>
public struct TextExtent
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
#region Private Members
diff --git a/src/Text/Def/TextLogic/PatternMatching/PatternMatch.cs b/src/Text/Def/TextLogic/PatternMatching/PatternMatch.cs
index dd907d0..f48141d 100644
--- a/src/Text/Def/TextLogic/PatternMatching/PatternMatch.cs
+++ b/src/Text/Def/TextLogic/PatternMatching/PatternMatch.cs
@@ -4,7 +4,11 @@ using System.Linq;
namespace Microsoft.VisualStudio.Text.PatternMatching
{
+#pragma warning disable CA1815 // Override equals and operator equals on value types
+#pragma warning disable CA1036 // Override methods on comparable types
public struct PatternMatch : IComparable<PatternMatch>
+#pragma warning restore CA1036 // Override methods on comparable types
+#pragma warning restore CA1815 // Override equals and operator equals on value types
{
/// <summary>
/// True if this was a case sensitive match.
diff --git a/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationOptions.cs b/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationOptions.cs
index 5693a3f..dbe5236 100644
--- a/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationOptions.cs
+++ b/src/Text/Def/TextLogic/PatternMatching/PatternMatcherCreationOptions.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace Microsoft.VisualStudio.Text.PatternMatching
@@ -11,12 +12,17 @@ namespace Microsoft.VisualStudio.Text.PatternMatching
/// <summary>
/// Used to tailor character comparisons to the correct culture.
/// </summary>
+#pragma warning disable CA1051 // Do not declare visible instance fields
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "CultureInfo is immutable")]
public readonly CultureInfo CultureInfo;
+#pragma warning restore CA1051 // Do not declare visible instance fields
/// <summary>
/// A set of biniary options, used to control options like case-sensitivity.
/// </summary>
+#pragma warning disable CA1051 // Do not declare visible instance fields
public readonly PatternMatcherCreationFlags Flags;
+#pragma warning disable CA1051 // Do not declare visible instance fields
/// <summary>
/// Characters that should be considered as describing a container/contained boundary. When matching types, this can be the '.' character
@@ -25,7 +31,10 @@ namespace Microsoft.VisualStudio.Text.PatternMatching
///
/// <see langword="null"/> signifies no characters are container boundaries.
/// </summary>
+#pragma warning disable CA1051 // Do not declare visible instance fields
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "Cannot make a breaking change")]
public readonly IReadOnlyCollection<char> ContainerSplitCharacters;
+#pragma warning restore CA1051 // Do not declare visible instance fields
/// <summary>
/// Creates an instance of <see cref="PatternMatcherCreationOptions"/>.
diff --git a/src/Text/Def/TextLogic/Tagging/BatchedTagsChangedEventArgs.cs b/src/Text/Def/TextLogic/Tagging/BatchedTagsChangedEventArgs.cs
index df661ac..bc865ff 100644
--- a/src/Text/Def/TextLogic/Tagging/BatchedTagsChangedEventArgs.cs
+++ b/src/Text/Def/TextLogic/Tagging/BatchedTagsChangedEventArgs.cs
@@ -24,7 +24,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
public BatchedTagsChangedEventArgs(IList<IMappingSpan> spans)
{
if (spans == null)
- throw new ArgumentNullException("spans");
+ throw new ArgumentNullException(nameof(spans));
//Make a copy of spans so we don't need to worry about it changing.
_spans = new ReadOnlyCollection<IMappingSpan>(new List<IMappingSpan>(spans));
diff --git a/src/Text/Def/TextLogic/Tagging/ITag.cs b/src/Text/Def/TextLogic/Tagging/ITag.cs
index 21fe434..a8696f8 100644
--- a/src/Text/Def/TextLogic/Tagging/ITag.cs
+++ b/src/Text/Def/TextLogic/Tagging/ITag.cs
@@ -4,10 +4,12 @@
//
namespace Microsoft.VisualStudio.Text.Tagging
{
+#pragma warning disable CA1040 // Avoid empty interfaces
/// <summary>
/// The base interface of all tags.
/// </summary>
public interface ITag
+#pragma warning restore CA1040 // Avoid empty interfaces
{
}
-} \ No newline at end of file
+}
diff --git a/src/Text/Def/TextLogic/Tagging/ITagAggregator.cs b/src/Text/Def/TextLogic/Tagging/ITagAggregator.cs
index e54c146..41ec3bd 100644
--- a/src/Text/Def/TextLogic/Tagging/ITagAggregator.cs
+++ b/src/Text/Def/TextLogic/Tagging/ITagAggregator.cs
@@ -72,7 +72,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
/// <para>
/// This is a batched version of the TagsChanged event. One or more TagsChanged events
/// are accumulated and then raised as a single BatchedTagsChanged event on idle using the
- /// <see cref="T:System.Windows.Threading.Dispatcher.CurrentDispatcher" /> that was active when the ITagAggregator was
+ /// Dispatcher.CurrentDispatcher that was active when the ITagAggregator was
/// created.
/// </para>
/// <para>
diff --git a/src/Text/Def/TextLogic/Tagging/MappingTagSpan.cs b/src/Text/Def/TextLogic/Tagging/MappingTagSpan.cs
index 562999c..350e95a 100644
--- a/src/Text/Def/TextLogic/Tagging/MappingTagSpan.cs
+++ b/src/Text/Def/TextLogic/Tagging/MappingTagSpan.cs
@@ -55,9 +55,9 @@ namespace Microsoft.VisualStudio.Text.Tagging
public MappingTagSpan(IMappingSpan span, T tag)
{
if (span == null)
- throw new ArgumentNullException("span");
+ throw new ArgumentNullException(nameof(span));
if (tag == null)
- throw new ArgumentNullException("tag");
+ throw new ArgumentNullException(nameof(tag));
Span = span;
Tag = tag;
diff --git a/src/Text/Def/TextLogic/Tagging/SimpleTagger.cs b/src/Text/Def/TextLogic/Tagging/SimpleTagger.cs
index 40048ea..1ffe420 100644
--- a/src/Text/Def/TextLogic/Tagging/SimpleTagger.cs
+++ b/src/Text/Def/TextLogic/Tagging/SimpleTagger.cs
@@ -95,9 +95,9 @@ namespace Microsoft.VisualStudio.Text.Tagging
public TrackingTagSpan<T> CreateTagSpan(ITrackingSpan span, T tag)
{
if (span == null)
- throw new ArgumentNullException("span");
+ throw new ArgumentNullException(nameof(span));
if (tag == null)
- throw new ArgumentNullException("tag");
+ throw new ArgumentNullException(nameof(tag));
var tagSpan = new TrackingTagSpan<T>(span, tag);
@@ -127,7 +127,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
public bool RemoveTagSpan(TrackingTagSpan<T> tagSpan)
{
if (tagSpan == null)
- throw new ArgumentNullException("tagSpan");
+ throw new ArgumentNullException(nameof(tagSpan));
bool removed = false;
@@ -162,7 +162,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
public int RemoveTagSpans(Predicate<TrackingTagSpan<T>> match)
{
if (match == null)
- throw new ArgumentNullException("match");
+ throw new ArgumentNullException(nameof(match));
int removedCount = 0;
@@ -283,7 +283,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
{
if (tagger == null)
{
- throw new ArgumentNullException("tagger");
+ throw new ArgumentNullException(nameof(tagger));
}
_tagger = tagger;
_tagger.StartBatch();
diff --git a/src/Text/Def/TextLogic/Tagging/TagSpan.cs b/src/Text/Def/TextLogic/Tagging/TagSpan.cs
index cd105aa..5636a7c 100644
--- a/src/Text/Def/TextLogic/Tagging/TagSpan.cs
+++ b/src/Text/Def/TextLogic/Tagging/TagSpan.cs
@@ -55,7 +55,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
public TagSpan(SnapshotSpan span, T tag)
{
if (tag == null)
- throw new ArgumentNullException("tag");
+ throw new ArgumentNullException(nameof(tag));
Span = span;
Tag = tag;
diff --git a/src/Text/Def/TextLogic/Tagging/TagTypeAttribute.cs b/src/Text/Def/TextLogic/Tagging/TagTypeAttribute.cs
index 228e1ba..3c5539c 100644
--- a/src/Text/Def/TextLogic/Tagging/TagTypeAttribute.cs
+++ b/src/Text/Def/TextLogic/Tagging/TagTypeAttribute.cs
@@ -24,9 +24,9 @@ namespace Microsoft.VisualStudio.Text.Tagging
public TagTypeAttribute(Type tagType)
{
if (tagType == null)
- throw new ArgumentNullException("tagType");
+ throw new ArgumentNullException(nameof(tagType));
if (!typeof(ITag).IsAssignableFrom(tagType))
- throw new ArgumentException("Given type must derive from ITag", "tagType");
+ throw new ArgumentException("Given type must derive from ITag", nameof(tagType));
this.type = tagType;
}
diff --git a/src/Text/Def/TextLogic/Tagging/TagsChangedEventArgs.cs b/src/Text/Def/TextLogic/Tagging/TagsChangedEventArgs.cs
index 78f33b2..dd1055a 100644
--- a/src/Text/Def/TextLogic/Tagging/TagsChangedEventArgs.cs
+++ b/src/Text/Def/TextLogic/Tagging/TagsChangedEventArgs.cs
@@ -24,7 +24,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
public TagsChangedEventArgs(IMappingSpan span)
{
if (span == null)
- throw new ArgumentNullException("span");
+ throw new ArgumentNullException(nameof(span));
Span = span;
}
diff --git a/src/Text/Def/TextLogic/Tagging/TrackingTagSpan.cs b/src/Text/Def/TextLogic/Tagging/TrackingTagSpan.cs
index 5ebfe29..9fca753 100644
--- a/src/Text/Def/TextLogic/Tagging/TrackingTagSpan.cs
+++ b/src/Text/Def/TextLogic/Tagging/TrackingTagSpan.cs
@@ -32,9 +32,9 @@ namespace Microsoft.VisualStudio.Text.Tagging
public TrackingTagSpan(ITrackingSpan span, T tag)
{
if (span == null)
- throw new ArgumentNullException("span");
+ throw new ArgumentNullException(nameof(span));
if (tag == null)
- throw new ArgumentNullException("tag");
+ throw new ArgumentNullException(nameof(tag));
Span = span;
Tag = tag;
diff --git a/src/Text/Def/TextLogic/Tags/ClassificationTag.cs b/src/Text/Def/TextLogic/Tags/ClassificationTag.cs
index d8e37c5..32994ab 100644
--- a/src/Text/Def/TextLogic/Tags/ClassificationTag.cs
+++ b/src/Text/Def/TextLogic/Tags/ClassificationTag.cs
@@ -22,7 +22,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
public ClassificationTag(IClassificationType type)
{
if (type == null)
- throw new ArgumentNullException("type");
+ throw new ArgumentNullException(nameof(type));
ClassificationType = type;
}
diff --git a/src/Text/Def/TextLogic/Tags/UrlTag.cs b/src/Text/Def/TextLogic/Tags/UrlTag.cs
index 83e086e..b8ed521 100644
--- a/src/Text/Def/TextLogic/Tags/UrlTag.cs
+++ b/src/Text/Def/TextLogic/Tags/UrlTag.cs
@@ -20,7 +20,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
public UrlTag(Uri url)
{
if (url == null)
- throw new ArgumentNullException("url");
+ throw new ArgumentNullException(nameof(url));
Url = url;
}
diff --git a/src/Text/Def/TextLogic/TextLogic.csproj b/src/Text/Def/TextLogic/TextLogic.csproj
index f7b3b78..15d863a 100644
--- a/src/Text/Def/TextLogic/TextLogic.csproj
+++ b/src/Text/Def/TextLogic/TextLogic.csproj
@@ -3,8 +3,6 @@
<AssemblyName>Microsoft.VisualStudio.Text.Logic</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
<TargetFramework>net46</TargetFramework>
- <NonShipping>false</NonShipping>
- <IsPackable>true</IsPackable>
<PushToPublicFeed>true</PushToPublicFeed>
<NoWarn>649;436;$(NoWarn)</NoWarn>
<AssemblyAttributeClsCompliant>true</AssemblyAttributeClsCompliant>
@@ -15,7 +13,7 @@
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
- <PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
+ <PackageReference Include="System.Collections.Immutable" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TextData\TextData.csproj" />
diff --git a/src/Text/Def/TextLogic/TextModel/VirtualSnapshotPoint.cs b/src/Text/Def/TextLogic/TextModel/VirtualSnapshotPoint.cs
index 7828fb4..d4d7762 100644
--- a/src/Text/Def/TextLogic/TextModel/VirtualSnapshotPoint.cs
+++ b/src/Text/Def/TextLogic/TextModel/VirtualSnapshotPoint.cs
@@ -6,10 +6,12 @@ namespace Microsoft.VisualStudio.Text
{
using System;
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Represents a <see cref="SnapshotPoint"/> that may have virtual spaces.
/// </summary>
public struct VirtualSnapshotPoint : IComparable<VirtualSnapshotPoint>
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
private readonly SnapshotPoint _position;
private readonly int _virtualSpaces;
@@ -48,7 +50,7 @@ namespace Microsoft.VisualStudio.Text
public VirtualSnapshotPoint(SnapshotPoint position, int virtualSpaces)
{
if (virtualSpaces < 0)
- throw new ArgumentOutOfRangeException("virtualSpaces");
+ throw new ArgumentOutOfRangeException(nameof(virtualSpaces));
//Treat trying to set virtual spaces in the middle of a line as a soft error. It is easy to do if some 3rd party does an unexpected edit on a text change
//and setting virtualSpaces to 0 is a reasonable fallback behavior.
@@ -73,9 +75,9 @@ namespace Microsoft.VisualStudio.Text
public VirtualSnapshotPoint(ITextSnapshotLine line, int offset)
{
if (line == null)
- throw new ArgumentNullException("line");
+ throw new ArgumentNullException(nameof(line));
if (offset < 0)
- throw new ArgumentOutOfRangeException("offset");
+ throw new ArgumentOutOfRangeException(nameof(offset));
if (offset <= line.Length)
{
@@ -152,12 +154,12 @@ namespace Microsoft.VisualStudio.Text
{
if (snapshot == null)
{
- throw new ArgumentNullException("snapshot");
+ throw new ArgumentNullException(nameof(snapshot));
}
if (snapshot.Version.VersionNumber < _position.Snapshot.Version.VersionNumber)
{
- throw new ArgumentException("VirtualSnapshotPoints can only be translated to later snapshots", "snapshot");
+ throw new ArgumentException("VirtualSnapshotPoints can only be translated to later snapshots", nameof(snapshot));
}
else if (snapshot == _position.Snapshot)
{
diff --git a/src/Text/Def/TextLogic/TextModel/VirtualSnapshotSpan.cs b/src/Text/Def/TextLogic/TextModel/VirtualSnapshotSpan.cs
index c8478f7..03c09de 100644
--- a/src/Text/Def/TextLogic/TextModel/VirtualSnapshotSpan.cs
+++ b/src/Text/Def/TextLogic/TextModel/VirtualSnapshotSpan.cs
@@ -6,10 +6,12 @@ namespace Microsoft.VisualStudio.Text
{
using System;
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Represents two <see cref="VirtualSnapshotPoint" />s
/// </summary>
public struct VirtualSnapshotSpan
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
private readonly VirtualSnapshotPoint _start;
private readonly VirtualSnapshotPoint _end;
@@ -47,7 +49,7 @@ namespace Microsoft.VisualStudio.Text
}
if (end < start)
{
- throw new ArgumentOutOfRangeException("end");
+ throw new ArgumentOutOfRangeException(nameof(end));
}
_start = start;
@@ -290,12 +292,12 @@ namespace Microsoft.VisualStudio.Text
{
if (snapshot == null)
{
- throw new ArgumentNullException("snapshot");
+ throw new ArgumentNullException(nameof(snapshot));
}
if (snapshot.Version.VersionNumber < _start.Position.Snapshot.Version.VersionNumber)
{
- throw new ArgumentException("VirtualSnapshotSpans can only be translated to later snapshots", "snapshot");
+ throw new ArgumentException("VirtualSnapshotSpans can only be translated to later snapshots", nameof(snapshot));
}
else if (snapshot == _start.Position.Snapshot)
{
diff --git a/src/Text/Def/TextUI/Adornments/ToolTipService/IViewElementFactoryService.cs b/src/Text/Def/TextUI/Adornments/ToolTipService/IViewElementFactoryService.cs
index c5d324a..0ed776c 100644
--- a/src/Text/Def/TextUI/Adornments/ToolTipService/IViewElementFactoryService.cs
+++ b/src/Text/Def/TextUI/Adornments/ToolTipService/IViewElementFactoryService.cs
@@ -11,7 +11,7 @@
/// This is a MEF service that can be obtained via the <see cref="ImportAttribute"/> in a MEF exported class.
/// </para>
/// <para>
- /// The editor supports <see cref="ClassifiedTextElement"/>s, <see cref="ImageElement"/>s, and <see cref="object"/>
+ /// The editor supports <see cref="ClassifiedTextElement"/>s, <see cref="ContainerElement"/>, <see cref="ImageElement"/>s, and <see cref="object"/>
/// on all platforms. Text and image elements are converted to colorized text and images respectively and
/// other objects are displayed as the <see cref="string"/> returned by <see cref="object.ToString()"/>
/// unless an extender exports a <see cref="IViewElementFactory"/> for that type.
diff --git a/src/Text/Def/TextUI/Adornments/ToolTipService/ToolTipParameters.cs b/src/Text/Def/TextUI/Adornments/ToolTipService/ToolTipParameters.cs
index daad688..87bf4f4 100644
--- a/src/Text/Def/TextUI/Adornments/ToolTipService/ToolTipParameters.cs
+++ b/src/Text/Def/TextUI/Adornments/ToolTipService/ToolTipParameters.cs
@@ -1,6 +1,7 @@
namespace Microsoft.VisualStudio.Text.Adornments
{
using System;
+ using System.Diagnostics.CodeAnalysis;
/// <summary>
/// Determines behavior for a <see cref="IToolTipPresenter"/>.
@@ -12,6 +13,7 @@
/// <summary>
/// Default options for a mouse tracking tooltip.
/// </summary>
+ [SuppressMessage("Microsoft.Security", "CA2104", Justification = "Type is readonly")]
public static readonly ToolTipParameters Default = new ToolTipParameters();
/// <summary>
diff --git a/src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ContainerElementStyle.cs b/src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ContainerElementStyle.cs
index d9f8b22..69c3600 100644
--- a/src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ContainerElementStyle.cs
+++ b/src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ContainerElementStyle.cs
@@ -1,18 +1,28 @@
namespace Microsoft.VisualStudio.Text.Adornments
{
+ using System;
+
+#pragma warning disable CA1714 // Flags enums should have plural names
/// <summary>
/// The layout style for a <see cref="ContainerElement"/>.
/// </summary>
+ [Flags]
public enum ContainerElementStyle
+#pragma warning restore CA1714 // Flags enums should have plural names
{
/// <summary>
/// Contents are end-to-end, and wrapped when the control becomes too wide.
/// </summary>
- Wrapped,
+ Wrapped = 0b_0000,
/// <summary>
/// Contents are stacked vertically.
/// </summary>
- Stacked
+ Stacked = 0b_0001,
+
+ /// <summary>
+ /// Additional padding above and below content.
+ /// </summary>
+ VerticalPadding = 0b_0010
}
}
diff --git a/src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ImageElement.cs b/src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ImageElement.cs
index f4c477c..079449a 100644
--- a/src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ImageElement.cs
+++ b/src/Text/Def/TextUI/Adornments/ToolTipService/ViewElementFactories/ImageElement.cs
@@ -1,29 +1,47 @@
namespace Microsoft.VisualStudio.Text.Adornments
{
+ using System;
using Microsoft.VisualStudio.Core.Imaging;
/// <summary>
- /// Represents an image in an <see cref="IToolTipService"/> <see cref="IToolTipPresenter"/>.
+ /// Represents cross platform compatible image.
/// </summary>
///
/// <remarks>
/// <see cref="ImageElement"/>s should be constructed with <see cref="Microsoft.VisualStudio.Core.Imaging.ImageId"/>s
/// that correspond to an image on that platform.
/// </remarks>
- public sealed class ImageElement
+ public class ImageElement
{
/// <summary>
/// Creates a new instance of an image element.
/// </summary>
- /// <param name="iamgeId"> A unique identifier for an image.</param>
+ /// <param name="imageId"> A unique identifier for an image</param>
public ImageElement(ImageId imageId)
{
this.ImageId = imageId;
}
/// <summary>
+ /// Creates a new instance of an image element.
+ /// </summary>
+ /// <param name="imageId"> A unique identifier for an image</param>
+ /// <param name="automationName"> Localized description of the image</param>
+ public ImageElement(ImageId imageId, string automationName)
+ : this(imageId)
+ {
+ // Let's allow empty strings, as long as they are not null references
+ this.AutomationName = automationName ?? throw new ArgumentNullException(nameof(automationName));
+ }
+
+ /// <summary>
/// A unique identifier for an image.
/// </summary>
public ImageId ImageId { get; }
+
+ /// <summary>
+ /// Localized description of the image
+ /// </summary>
+ public string AutomationName { get; }
}
}
diff --git a/src/Text/Def/TextUI/Editor/CaretPosition.cs b/src/Text/Def/TextUI/Editor/CaretPosition.cs
index 8b51e56..0297473 100644
--- a/src/Text/Def/TextUI/Editor/CaretPosition.cs
+++ b/src/Text/Def/TextUI/Editor/CaretPosition.cs
@@ -6,10 +6,12 @@ namespace Microsoft.VisualStudio.Text.Editor
{
using System;
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Represents the position of a caret in an <see cref="ITextView"/>.
/// </summary>
public struct CaretPosition
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
#region Private Members
VirtualSnapshotPoint _bufferPosition;
@@ -28,7 +30,7 @@ namespace Microsoft.VisualStudio.Text.Editor
{
if (mappingPoint == null)
{
- throw new ArgumentNullException("mappingPoint");
+ throw new ArgumentNullException(nameof(mappingPoint));
}
_bufferPosition = bufferPosition;
diff --git a/src/Text/Def/Internal/TextUI/ITextView2.cs b/src/Text/Def/TextUI/Editor/ITextView2.cs
index 38ff665..ed797ee 100644
--- a/src/Text/Def/Internal/TextUI/ITextView2.cs
+++ b/src/Text/Def/TextUI/Editor/ITextView2.cs
@@ -1,39 +1,39 @@
-//
+//
// 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 internal APIs that are subject to change without notice.
-// Use at your own risk.
-//
+
+using System;
+
namespace Microsoft.VisualStudio.Text.Editor
{
- using System;
-
/// <summary>
- /// An extension of the ITextView that exposes some internal hooks.
+ /// Extensions to <see cref="ITextView"/>, augmenting functionality. For every member here
+ /// there should also be an extension method in <see cref="TextViewExtensions"/>.
/// </summary>
public interface ITextView2 : ITextView
{
/// <summary>
- /// The MaxTextRightCoordinate of the view based only on the text contained in the view.
+ /// Determines whether the view is in the process of being laid out or is preparing to be laid out.
/// </summary>
- double RawMaxTextRightCoordinate
+ /// <remarks>
+ /// As opposed to <see cref="ITextView.InLayout"/>, it is safe to get the <see cref="ITextView.TextViewLines"/>
+ /// but attempting to queue another layout will cause a reentrant layout exception.
+ /// </remarks>
+ bool InOuterLayout
{
get;
}
/// <summary>
- /// The minimum value for the view's MaxTextRightCoordinate.
+ /// Gets an object for managing selections within the view.
/// </summary>
- /// <remarks>
- /// If setting this value changes the view's MaxTextRightCoordinate, the view will raise a layout changed event.
- /// </remarks>
- double MinMaxTextRightCoordinate
+ IMultiSelectionBroker MultiSelectionBroker
{
get;
- set;
}
+
/// <summary>
/// Raised whenever the view's MaxTextRightCoordinate is changed.
/// </summary>
diff --git a/src/Text/Def/TextUI/Editor/ITextViewRoleSet.cs b/src/Text/Def/TextUI/Editor/ITextViewRoleSet.cs
index b3fba65..ae66092 100644
--- a/src/Text/Def/TextUI/Editor/ITextViewRoleSet.cs
+++ b/src/Text/Def/TextUI/Editor/ITextViewRoleSet.cs
@@ -7,10 +7,12 @@ namespace Microsoft.VisualStudio.Text.Editor
using System;
using System.Collections.Generic;
+#pragma warning disable CA1710 // Identifiers should have correct suffix
/// <summary>
/// Set of text view roles.
/// </summary>
public interface ITextViewRoleSet : IEnumerable<string>
+#pragma warning restore CA1710 // Identifiers should have correct suffix
{
/// <summary>
/// Compute whether the given text view role is a member of the set.
diff --git a/src/Text/Def/TextUI/Editor/MarginContainerAttribute.cs b/src/Text/Def/TextUI/Editor/MarginContainerAttribute.cs
index 815b804..aa89687 100644
--- a/src/Text/Def/TextUI/Editor/MarginContainerAttribute.cs
+++ b/src/Text/Def/TextUI/Editor/MarginContainerAttribute.cs
@@ -26,7 +26,7 @@ namespace Microsoft.VisualStudio.Text.Editor
public MarginContainerAttribute(string marginContainer)
{
if (marginContainer == null)
- throw new ArgumentNullException("marginContainer");
+ throw new ArgumentNullException(nameof(marginContainer));
if (marginContainer.Length == 0)
throw new ArgumentException("marginContainer is an empty string.");
diff --git a/src/Text/Def/TextUI/Editor/MouseHoverEventArgs.cs b/src/Text/Def/TextUI/Editor/MouseHoverEventArgs.cs
index 3a1b1da..1a94192 100644
--- a/src/Text/Def/TextUI/Editor/MouseHoverEventArgs.cs
+++ b/src/Text/Def/TextUI/Editor/MouseHoverEventArgs.cs
@@ -33,12 +33,12 @@ namespace Microsoft.VisualStudio.Text.Editor
public MouseHoverEventArgs(ITextView view, int position, IMappingPoint textPosition)
{
if (view == null)
- throw new ArgumentNullException("view");
+ throw new ArgumentNullException(nameof(view));
#pragma warning suppress 56506 // ToDo: Add a comment on why it is not necessary to check view.TextSnapshot
if ((position < 0) || (position > view.TextSnapshot.Length)) // Allow positions at the end of the file
- throw new ArgumentOutOfRangeException("position");
+ throw new ArgumentOutOfRangeException(nameof(position));
if (textPosition == null)
- throw new ArgumentNullException("textPosition");
+ throw new ArgumentNullException(nameof(textPosition));
// we could be very paranoid and check:
//if (textPosition.AnchorBuffer != view.TextBuffer)
// throw new ArgumentException();
diff --git a/src/Text/Def/TextUI/Editor/ReplacesAttribute.cs b/src/Text/Def/TextUI/Editor/ReplacesAttribute.cs
index 7b9b738..a842509 100644
--- a/src/Text/Def/TextUI/Editor/ReplacesAttribute.cs
+++ b/src/Text/Def/TextUI/Editor/ReplacesAttribute.cs
@@ -29,7 +29,7 @@ namespace Microsoft.VisualStudio.Text.Editor
public ReplacesAttribute(string replaces)
{
if (replaces == null)
- throw new ArgumentNullException("replaces");
+ throw new ArgumentNullException(nameof(replaces));
if (replaces.Length == 0)
throw new ArgumentException("replaces is an empty string.");
@@ -47,4 +47,4 @@ namespace Microsoft.VisualStudio.Text.Editor
}
}
}
-} \ No newline at end of file
+}
diff --git a/src/Text/Def/TextUI/Editor/TextViewCreatedEventArgs.cs b/src/Text/Def/TextUI/Editor/TextViewCreatedEventArgs.cs
index 21527c3..009cf3d 100644
--- a/src/Text/Def/TextUI/Editor/TextViewCreatedEventArgs.cs
+++ b/src/Text/Def/TextUI/Editor/TextViewCreatedEventArgs.cs
@@ -24,7 +24,7 @@ namespace Microsoft.VisualStudio.Text.Editor
{
if (textView == null)
{
- throw new ArgumentNullException("textView");
+ throw new ArgumentNullException(nameof(textView));
}
TextView = textView;
}
diff --git a/src/Text/Def/TextUI/Editor/TextViewExtensions.cs b/src/Text/Def/TextUI/Editor/TextViewExtensions.cs
new file mode 100644
index 0000000..ae2cfab
--- /dev/null
+++ b/src/Text/Def/TextUI/Editor/TextViewExtensions.cs
@@ -0,0 +1,90 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+using System;
+
+namespace Microsoft.VisualStudio.Text.Editor
+{
+ /// <summary>
+ /// Utility <see cref="ITextView"/> extension methods.
+ /// </summary>
+ public static class TextViewExtensions
+ {
+ /// <summary>
+ /// Gets whether given <see cref="ITextView"/> is embedded in another <see cref="ITextView"/>.
+ /// </summary>
+ /// <param name="textView">The <see cref="ITextView"/> for which to determine if it's embedded.</param>
+ /// <returns><c>true</c> if given <see cref="ITextView"/> is embedded, <c>false</c> otherwise.</returns>
+ public static bool IsEmbeddedTextView(this ITextView textView)
+ {
+ if (textView == null)
+ {
+ throw new ArgumentNullException(nameof(textView));
+ }
+
+ return textView.Roles.Contains(PredefinedTextViewRoles.EmbeddedPeekTextView);
+ }
+
+ /// <summary>
+ /// Gets containing <see cref="ITextView"/> for given embedded <see cref="ITextView"/>.
+ /// </summary>
+ /// <param name="textView">An embedded <see cref="ITextView"/>, for which to get a containing <see cref="ITextView"/>.</param>
+ /// <param name="containingTextView">A <see cref="ITextView"/> that contains given <see cref="ITextView"/> or null if
+ /// given <see cref="ITextView"/> is not embedded in another <see cref="ITextView"/>.</param>
+ /// <returns><c>true</c> if containing <see cref="ITextView"/> was found, <c>false</c> otherwise.</returns>
+ public static bool TryGetContainingTextView(this ITextView textView, out ITextView containingTextView)
+ {
+ if (textView == null)
+ {
+ throw new ArgumentNullException(nameof(textView));
+ }
+
+ // Extra scrutiny because Peek is on a different layer and we cannot just rely on it doing the right thing
+ if (textView.IsEmbeddedTextView())
+ {
+ bool success = textView.Properties.TryGetProperty("PeekContainingTextView", out containingTextView);
+ if (!success || containingTextView == null)
+ {
+ throw new InvalidOperationException("Unexpected failure to obtain containing text view of an embedded text view.");
+ }
+
+ return true;
+ }
+
+ containingTextView = null;
+ return false;
+ }
+
+ /// <summary>
+ /// Determines whether a view is in the process of being laid out or is preparing to be laid out.
+ /// </summary>
+ /// <param name="textView">The <see cref="ITextView"/> to check.</param>
+ /// <remarks>
+ /// As opposed to <see cref="ITextView.InLayout"/>, it is safe to get the <see cref="ITextView.TextViewLines"/>
+ /// but attempting to queue another layout will cause a reentrant layout exception.
+ /// </remarks>
+ public static bool GetInOuterLayout(this ITextView textView)
+ {
+ if (textView == null)
+ {
+ throw new ArgumentNullException(nameof(textView));
+ }
+
+ return ((ITextView2)textView).InOuterLayout;
+ }
+
+ /// <summary>
+ /// Gets an object for managing selections within the view.
+ /// </summary>
+ public static IMultiSelectionBroker GetMultiSelectionBroker(this ITextView textView)
+ {
+ if (textView == null)
+ {
+ throw new ArgumentNullException(nameof(textView));
+ }
+
+ return ((ITextView2)textView).MultiSelectionBroker;
+ }
+ }
+}
diff --git a/src/Text/Def/TextUI/Editor/TextViewLayoutChangedEventArgs.cs b/src/Text/Def/TextUI/Editor/TextViewLayoutChangedEventArgs.cs
index e26769c..9bb0376 100644
--- a/src/Text/Def/TextUI/Editor/TextViewLayoutChangedEventArgs.cs
+++ b/src/Text/Def/TextUI/Editor/TextViewLayoutChangedEventArgs.cs
@@ -63,13 +63,13 @@ namespace Microsoft.VisualStudio.Text.Editor
IList<ITextViewLine> translatedLines)
{
if (oldState == null)
- throw new ArgumentNullException("oldState");
+ throw new ArgumentNullException(nameof(oldState));
if (newState == null)
- throw new ArgumentNullException("newState");
+ throw new ArgumentNullException(nameof(newState));
if (translatedLines == null)
- throw new ArgumentNullException("translatedLines");
+ throw new ArgumentNullException(nameof(translatedLines));
if (newOrReformattedLines == null)
- throw new ArgumentNullException("newOrReformattedLines");
+ throw new ArgumentNullException(nameof(newOrReformattedLines));
_oldViewState = oldState;
_newViewState = newState;
diff --git a/src/Text/Def/TextUI/Editor/TextViewRoleAttribute.cs b/src/Text/Def/TextUI/Editor/TextViewRoleAttribute.cs
index e17c675..e630939 100644
--- a/src/Text/Def/TextUI/Editor/TextViewRoleAttribute.cs
+++ b/src/Text/Def/TextUI/Editor/TextViewRoleAttribute.cs
@@ -23,7 +23,7 @@ namespace Microsoft.VisualStudio.Text.Editor
{
if (string.IsNullOrEmpty(role))
{
- throw new ArgumentNullException("role");
+ throw new ArgumentNullException(nameof(role));
}
this.roles = role;
}
@@ -36,4 +36,4 @@ namespace Microsoft.VisualStudio.Text.Editor
get { return this.roles; }
}
}
-} \ No newline at end of file
+}
diff --git a/src/Text/Def/TextUI/Editor/ViewRelativePosition.cs b/src/Text/Def/TextUI/Editor/ViewRelativePosition.cs
index 41f2861..78a1e82 100644
--- a/src/Text/Def/TextUI/Editor/ViewRelativePosition.cs
+++ b/src/Text/Def/TextUI/Editor/ViewRelativePosition.cs
@@ -16,6 +16,6 @@ namespace Microsoft.VisualStudio.Text.Editor
/// <summary>
/// The offset with respect to the bottom of the view.
/// </summary>
- Bottom
+ Bottom
}
-} \ No newline at end of file
+}
diff --git a/src/Text/Def/TextUI/Editor/ViewState.cs b/src/Text/Def/TextUI/Editor/ViewState.cs
index 30d677c..505bb86 100644
--- a/src/Text/Def/TextUI/Editor/ViewState.cs
+++ b/src/Text/Def/TextUI/Editor/ViewState.cs
@@ -61,7 +61,7 @@ namespace Microsoft.VisualStudio.Text.Editor
public ViewState(ITextView view, double effectiveViewportWidth, double effectiveViewportHeight)
{
if (view == null)
- throw new ArgumentNullException("view");
+ throw new ArgumentNullException(nameof(view));
this.ViewportLeft = view.ViewportLeft;
this.ViewportTop = view.ViewportTop;
diff --git a/src/Text/Def/TextUI/EditorOptions/ViewOptions.cs b/src/Text/Def/TextUI/EditorOptions/ViewOptions.cs
index 0255860..b35cb6f 100644
--- a/src/Text/Def/TextUI/EditorOptions/ViewOptions.cs
+++ b/src/Text/Def/TextUI/EditorOptions/ViewOptions.cs
@@ -23,7 +23,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsVirtualSpaceEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewOptions.UseVirtualSpaceId);
}
@@ -36,7 +36,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsOverwriteModeEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewOptions.OverwriteModeId);
}
@@ -49,7 +49,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsAutoScrollEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewOptions.AutoScrollId);
}
@@ -62,7 +62,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static WordWrapStyles WordWrapStyle(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<WordWrapStyles>(DefaultTextViewOptions.WordWrapStyleId);
}
@@ -75,7 +75,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsVisibleWhitespaceEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewOptions.UseVisibleWhitespaceId);
}
@@ -89,7 +89,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool DoesViewProhibitUserInput(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewOptions.ViewProhibitUserInputId);
}
@@ -102,7 +102,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsOutliningUndoEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue(DefaultTextViewOptions.OutliningUndoOptionId);
}
@@ -115,7 +115,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsDragDropEditingEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue(DefaultTextViewOptions.DragDropEditingId);
}
@@ -128,7 +128,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsViewportLeftClipped(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewOptions.IsViewportLeftClippedId);
}
@@ -150,7 +150,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsVerticalScrollBarEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewHostOptions.VerticalScrollBarId);
}
@@ -163,7 +163,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsHorizontalScrollBarEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewHostOptions.HorizontalScrollBarId);
}
@@ -176,7 +176,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsGlyphMarginEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewHostOptions.GlyphMarginId);
}
@@ -189,7 +189,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsSelectionMarginEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewHostOptions.SelectionMarginId);
}
@@ -202,7 +202,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsLineNumberMarginEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewHostOptions.LineNumberMarginId);
}
@@ -215,7 +215,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsChangeTrackingEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewHostOptions.ChangeTrackingId);
}
@@ -229,7 +229,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsOutliningMarginEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewHostOptions.OutliningMarginId);
}
@@ -242,7 +242,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
public static bool IsZoomControlEnabled(this IEditorOptions options)
{
if (options == null)
- throw new ArgumentNullException("options");
+ throw new ArgumentNullException(nameof(options));
return options.GetOptionValue<bool>(DefaultTextViewHostOptions.ZoomControlId);
}
@@ -254,7 +254,7 @@ namespace Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods
/// <returns><c>true</c> if the editor is in either "Extra Contrast" or "High Contrast" modes, otherwise <c>false</c>.</returns>
public static bool IsInContrastMode(this IEditorOptions options)
{
- if(options == null)
+ if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
@@ -336,6 +336,18 @@ namespace Microsoft.VisualStudio.Text.Editor
public const string ShowBlockStructureName = "TextView/ShowBlockStructure";
/// <summary>
+ /// Should the carets be rendered.
+ /// </summary>
+ public static readonly EditorOptionKey<bool> ShouldCaretsBeRenderedId = new EditorOptionKey<bool>(ShouldCaretsBeRenderedName);
+ public const string ShouldCaretsBeRenderedName = "TextView/ShouldCaretsBeRendered";
+
+ /// <summary>
+ /// Should the selections be rendered.
+ /// </summary>
+ public static readonly EditorOptionKey<bool> ShouldSelectionsBeRenderedId = new EditorOptionKey<bool>(ShouldSelectionsBeRenderedName);
+ public const string ShouldSelectionsBeRenderedName = "TextView/ShouldSelectionsBeRendered";
+
+ /// <summary>
/// Whether or not to replace the coding characters and special symbols (such as (,),{,},etc.) with their textual representation
/// for automated objects to produce friendly text for screen readers.
/// </summary>
@@ -365,6 +377,12 @@ namespace Microsoft.VisualStudio.Text.Editor
/// </summary>
public const string BraceCompletionEnabledOptionName = "BraceCompletion/Enabled";
public readonly static EditorOptionKey<bool> BraceCompletionEnabledOptionId = new EditorOptionKey<bool>(BraceCompletionEnabledOptionName);
+
+ /// <summary>
+ /// Defines how wide the caret should be rendered. This is typically used to support accessibility requirements.
+ /// </summary>
+ public const string CaretWidthOptionName = "TextView/CaretWidth";
+ public readonly static EditorOptionKey<double> CaretWidthId = new EditorOptionKey<double>(CaretWidthOptionName);
#endregion
}
@@ -685,6 +703,42 @@ namespace Microsoft.VisualStudio.Text.Editor
}
/// <summary>
+ /// Defines the Should Carets Be Rendered option.
+ /// </summary>
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(DefaultTextViewOptions.ShouldCaretsBeRenderedName)]
+ public sealed class ShouldCaretsBeRendered : ViewOptionDefinition<bool>
+ {
+ /// <summary>
+ /// Gets the default value, which is <c>true</c>.
+ /// </summary>
+ public override bool Default { get { return true; } }
+
+ /// <summary>
+ /// Gets the default text view host value.
+ /// </summary>
+ public override EditorOptionKey<bool> Key { get { return DefaultTextViewOptions.ShouldCaretsBeRenderedId; } }
+ }
+
+ /// <summary>
+ /// Defines the Should Selection Be Rendered option.
+ /// </summary>
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(DefaultTextViewOptions.ShouldSelectionsBeRenderedName)]
+ public sealed class ShouldSelectionsBeRendered : ViewOptionDefinition<bool>
+ {
+ /// <summary>
+ /// Gets the default value, which is <c>true</c>.
+ /// </summary>
+ public override bool Default { get { return true; } }
+
+ /// <summary>
+ /// Gets the default text view host value.
+ /// </summary>
+ public override EditorOptionKey<bool> Key { get { return DefaultTextViewOptions.ShouldSelectionsBeRenderedId; } }
+ }
+
+ /// <summary>
/// Defines the option to enable providing annotated text in automation controls so that screen readers can properly
/// read contents of code.
/// </summary>
@@ -930,4 +984,22 @@ namespace Microsoft.VisualStudio.Text.Editor
/// </summary>
public override EditorOptionKey<bool> Key { get { return DefaultTextViewOptions.DisplayUrlsAsHyperlinksId; } }
}
+
+ /// <summary>
+ /// The option definition that determines how wide the caret should be rendered.
+ /// </summary>
+ [Export(typeof(EditorOptionDefinition))]
+ [Name(DefaultTextViewOptions.CaretWidthOptionName)]
+ public sealed class CaretWidthOption : EditorOptionDefinition<double>
+ {
+ /// <summary>
+ /// Gets the default value <c>1.0</c>.
+ /// </summary>
+ public override double Default => 1.0;
+
+ /// <summary>
+ /// Gets the editor option key.
+ /// </summary>
+ public override EditorOptionKey<double> Key => DefaultTextViewOptions.CaretWidthId;
+ }
}
diff --git a/src/Text/Def/TextUI/Find/IncrementalSearchResult.cs b/src/Text/Def/TextUI/Find/IncrementalSearchResult.cs
index d027b12..3b4f0f4 100644
--- a/src/Text/Def/TextUI/Find/IncrementalSearchResult.cs
+++ b/src/Text/Def/TextUI/Find/IncrementalSearchResult.cs
@@ -5,6 +5,7 @@
namespace Microsoft.VisualStudio.Text.IncrementalSearch
{
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Consolidates the result of an incremental search operation.
/// </summary>
@@ -14,6 +15,7 @@ namespace Microsoft.VisualStudio.Text.IncrementalSearch
/// the position of the first result.
/// </remarks>
public struct IncrementalSearchResult
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
#region Public Properties
@@ -108,4 +110,4 @@ namespace Microsoft.VisualStudio.Text.IncrementalSearch
#endregion //Object Overrides
}
-} \ No newline at end of file
+}
diff --git a/src/Text/Def/TextUI/Formatting/LineTransform.cs b/src/Text/Def/TextUI/Formatting/LineTransform.cs
index 6f377c0..25a7fe2 100644
--- a/src/Text/Def/TextUI/Formatting/LineTransform.cs
+++ b/src/Text/Def/TextUI/Formatting/LineTransform.cs
@@ -6,6 +6,7 @@ namespace Microsoft.VisualStudio.Text.Formatting
{
using System;
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// Represents the transform from a formatted text line to a rendered text line.
/// </summary>
@@ -27,6 +28,7 @@ namespace Microsoft.VisualStudio.Text.Formatting
/// corresponds to one pixel on the display.</para>
/// </remarks>
public struct LineTransform
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
private readonly double _topSpace;
private readonly double _bottomSpace;
@@ -87,16 +89,16 @@ namespace Microsoft.VisualStudio.Text.Formatting
public LineTransform(double topSpace, double bottomSpace, double verticalScale, double right)
{
if (double.IsNaN(topSpace))
- throw new ArgumentOutOfRangeException("topSpace");
+ throw new ArgumentOutOfRangeException(nameof(topSpace));
if (double.IsNaN(bottomSpace))
- throw new ArgumentOutOfRangeException("bottomSpace");
+ throw new ArgumentOutOfRangeException(nameof(bottomSpace));
if ((verticalScale <= 0.0) || double.IsNaN(verticalScale))
- throw new ArgumentOutOfRangeException("verticalScale");
+ throw new ArgumentOutOfRangeException(nameof(verticalScale));
if ((right < 0.0) || double.IsNaN(right))
- throw new ArgumentOutOfRangeException("right");
+ throw new ArgumentOutOfRangeException(nameof(right));
_topSpace = topSpace;
_bottomSpace = bottomSpace;
diff --git a/src/Text/Def/TextUI/Formatting/TextAndAdornmentSequenceChangedEventArgs.cs b/src/Text/Def/TextUI/Formatting/TextAndAdornmentSequenceChangedEventArgs.cs
index 7a7fee3..e1e7b41 100644
--- a/src/Text/Def/TextUI/Formatting/TextAndAdornmentSequenceChangedEventArgs.cs
+++ b/src/Text/Def/TextUI/Formatting/TextAndAdornmentSequenceChangedEventArgs.cs
@@ -25,7 +25,7 @@ namespace Microsoft.VisualStudio.Text.Formatting
public TextAndAdornmentSequenceChangedEventArgs(IMappingSpan span)
{
if (span == null)
- throw new ArgumentNullException("span");
+ throw new ArgumentNullException(nameof(span));
this.Span = span;
}
diff --git a/src/Text/Def/TextUI/Formatting/TextBounds.cs b/src/Text/Def/TextUI/Formatting/TextBounds.cs
index 58113da..519eb3a 100644
--- a/src/Text/Def/TextUI/Formatting/TextBounds.cs
+++ b/src/Text/Def/TextUI/Formatting/TextBounds.cs
@@ -6,6 +6,7 @@ namespace Microsoft.VisualStudio.Text.Formatting
{
using System;
+#pragma warning disable CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
/// <summary>
/// The bounds of a span of text in a given text line.
/// </summary>
@@ -27,6 +28,7 @@ namespace Microsoft.VisualStudio.Text.Formatting
/// corresponds to one pixel on the display.</para>
/// </remarks>
public struct TextBounds
+#pragma warning restore CA1066 // Type {0} should implement IEquatable<T> because it overrides Equals
{
#region Private Members
private readonly double _leading;
@@ -64,17 +66,17 @@ namespace Microsoft.VisualStudio.Text.Formatting
{
// Validate
if (double.IsNaN(leading))
- throw new ArgumentOutOfRangeException("leading");
+ throw new ArgumentOutOfRangeException(nameof(leading));
if (double.IsNaN(top))
- throw new ArgumentOutOfRangeException("top");
+ throw new ArgumentOutOfRangeException(nameof(top));
if (double.IsNaN(bidiWidth))
- throw new ArgumentOutOfRangeException("bidiWidth");
+ throw new ArgumentOutOfRangeException(nameof(bidiWidth));
if (double.IsNaN(height) || (height < 0.0))
- throw new ArgumentOutOfRangeException("height");
+ throw new ArgumentOutOfRangeException(nameof(height));
if (double.IsNaN(textTop))
- throw new ArgumentOutOfRangeException("textTop");
+ throw new ArgumentOutOfRangeException(nameof(textTop));
if (double.IsNaN(textHeight) || (textHeight < 0.0))
- throw new ArgumentOutOfRangeException("textHeight");
+ throw new ArgumentOutOfRangeException(nameof(textHeight));
_leading = leading;
_top = top;
diff --git a/src/Text/Def/TextUI/MultiCaret/AbstractSelectionPresentationProperties.cs b/src/Text/Def/TextUI/MultiCaret/AbstractSelectionPresentationProperties.cs
new file mode 100644
index 0000000..351d733
--- /dev/null
+++ b/src/Text/Def/TextUI/MultiCaret/AbstractSelectionPresentationProperties.cs
@@ -0,0 +1,49 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+
+using Microsoft.VisualStudio.Text.Formatting;
+
+namespace Microsoft.VisualStudio.Text
+{
+ /// <summary>
+ /// Provides UI specific properties about an <see cref="Selection"/>.
+ /// </summary>
+ public abstract class AbstractSelectionPresentationProperties
+ {
+ /// <summary>
+ /// Gets the position that the caret prefers to occupy on a given line. This position may not be honored
+ /// if virtual space is off and the line is insufficiently long. See <see cref="CaretBounds"/> for the
+ /// actual location.
+ /// </summary>
+ public virtual double PreferredXCoordinate { get; protected set; }
+
+ /// <summary>
+ /// Gets the position that the caret prefers to occupy vertically in the view. This position is used for operations
+ /// such as page up/down, but may not be honored if there is an adornment at the desired location. See
+ /// <see cref="CaretBounds"/> for the actual location.
+ /// </summary>
+ public virtual double PreferredYCoordinate { get; protected set; }
+
+ /// <summary>
+ /// Gets the caret location and size.
+ /// </summary>
+ public virtual TextBounds CaretBounds { get; }
+
+ /// <summary>
+ /// Gets whether the caret is shown in its entirety on the screen.
+ /// </summary>
+ public virtual bool IsWithinViewport { get; }
+
+ /// <summary>
+ /// Gets whether the caret should be rendered as overwrite.
+ /// </summary>
+ public virtual bool IsOverwriteMode { get; }
+
+ /// <summary>
+ /// Gets the <see cref="ITextViewLine"/> that contains the <see cref="Selection.InsertionPoint"/>.
+ /// </summary>
+ public virtual ITextViewLine ContainingTextViewLine { get; }
+ }
+}
diff --git a/src/Text/Def/TextUI/MultiCaret/IMultiSelectionBroker.cs b/src/Text/Def/TextUI/MultiCaret/IMultiSelectionBroker.cs
new file mode 100644
index 0000000..5ca3d8d
--- /dev/null
+++ b/src/Text/Def/TextUI/MultiCaret/IMultiSelectionBroker.cs
@@ -0,0 +1,292 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+using System;
+using System.Collections.Generic;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Formatting;
+
+namespace Microsoft.VisualStudio.Text
+{
+ /// <summary>
+ /// Manages all the caret and selecting behavior for an <see cref="ITextView"/>.
+ /// Handles multiple selections, and box selection. Throughout this namespace carets
+ /// are considered to be part of Selections, and are represented by <see cref="Selection.InsertionPoint"/>.
+ /// </summary>
+ public interface IMultiSelectionBroker
+ {
+ /// <summary>
+ /// Gets the view for which this broker manages selections.
+ /// </summary>
+ ITextView TextView { get; }
+
+ /// <summary>
+ /// Gets the current <see cref="ITextSnapshot"/> that is associated with anchor,
+ /// active, and insertion points for everything managed by this broker. This snapshot
+ /// will always be based in the <see cref="ITextViewModel.EditBuffer"/> for the associated
+ /// <see cref="ITextView"/>.
+ /// </summary>
+ ITextSnapshot CurrentSnapshot { get; }
+
+ #region Add/Remove/Get Selections
+ /// <summary>
+ /// Gets a list of all selections associated with <see cref="TextView" />. They will
+ /// be sorted in the order of appearence in the underlying snapshot. This property is
+ /// intended for edit operations and may be computationally expensive. If not all
+ /// selections are required, use <see cref="GetSelectionsIntersectingSpan(SnapshotSpan)"/> instead.
+ ///
+ /// This returns a selection as an <see cref="Selection"/>.
+ /// </summary>
+ IReadOnlyList<Selection> AllSelections { get; }
+
+ /// <summary>
+ /// Gets whether there are multiple selections in <see cref="AllSelections"/>.
+ /// </summary>
+ bool HasMultipleSelections { get; }
+
+ /// <summary>
+ /// Gets a list of all the selections that intersect the given span. Virtual whitespace is ignored for this method.
+ /// </summary>
+ /// <param name="span">The span of interest.</param>
+ /// <returns>The list of <see cref="Selection"/> objects.</returns>
+ IReadOnlyList<Selection> GetSelectionsIntersectingSpan(SnapshotSpan span);
+
+ /// <summary>
+ /// Gets a list of all the selections that intersect the given span collection. Virtual whitespace is ignored for this method.
+ /// </summary>
+ /// <param name="spanCollection"></param>
+ /// <returns></returns>
+ IReadOnlyList<Selection> GetSelectionsIntersectingSpans(NormalizedSnapshotSpanCollection spanCollection);
+
+ /// <summary>
+ /// Adds a selection to <see cref="AllSelections"/>.
+ /// </summary>
+ /// <param name="selection">The selection to add</param>.
+ /// <remarks>This will throw if it not based on <see cref="CurrentSnapshot"/>.</remarks>
+ void AddSelection(Selection selection);
+
+ /// <summary>
+ /// Adds a list of selections to <see cref="AllSelections"/>.
+ /// </summary>
+ /// <param name="range">The list of selections to add.</param>
+ /// <remarks>This will throw if any of the selections are not based on <see cref="CurrentSnapshot"/>.</remarks>
+ void AddSelectionRange(IEnumerable<Selection> range);
+
+ /// <summary>
+ /// Clears the current selections and adds one as the new value. This also becomes the <see cref="PrimarySelection"/>.
+ /// </summary>
+ /// <param name="selection">The selection to leave as the value of <see cref="PrimarySelection"/> and sole
+ /// member of <see cref="AllSelections"/>.</param>
+ /// <remarks>This will throw if it not based on <see cref="CurrentSnapshot"/>.</remarks>
+ void SetSelection(Selection selection);
+
+ /// <summary>
+ /// Clears the current selections, adds the provided range, and sets the primary selection.
+ /// </summary>
+ /// <param name="range">Selections that should be part of <see cref="AllSelections"/>.</param>
+ /// <param name="primary">The selection that should be set as <see cref="PrimarySelection"/>.</param>
+ /// <remarks>
+ /// If range is null or does not contain primary, primary will also be added to <see cref="AllSelections"/>.
+ /// This will throw if any of the selections are not based on <see cref="CurrentSnapshot"/>.
+ /// </remarks>
+ void SetSelectionRange(IEnumerable<Selection> range, Selection primary);
+
+ /// <summary>
+ /// Removes a selection from the view.
+ /// </summary>
+ /// <param name="selection">The selection to remove.</param>
+ /// <returns><c>true</c> if successful. <c>false</c> otherwise. This can fail if either the selection passed in does not exist in the view, or
+ /// it is the last one.</returns>
+ bool TryRemoveSelection(Selection selection);
+
+ /// <summary>
+ /// Gets the primary selection which should remain after invoking <see cref="ClearSecondarySelections"/>.
+ /// </summary>
+ Selection PrimarySelection { get; }
+
+ /// <summary>
+ /// Attempts to set the provided selection to be the new <see cref="PrimarySelection"/>.
+ /// </summary>
+ /// <param name="candidate">The new candidate for primary selection.</param>
+ /// <returns>Whether the set operation was successful. This will return <c>false</c> if the candidate is not
+ /// found in <see cref="AllSelections"/>.</returns>
+ bool TrySetAsPrimarySelection(Selection candidate);
+
+ /// <summary>
+ /// Removes all but the <see cref="PrimarySelection"/> from the session.
+ /// </summary>
+ void ClearSecondarySelections();
+
+ /// <summary>
+ /// Performs a predefined manipulation on all <see cref="Selection"/>s contained by <see cref="TextView"/>.
+ /// </summary>
+ /// <param name="action">The manipulation to perform.</param>
+ /// <remarks>Overlapping selections will be merged after all manipulations have been applied.</remarks>
+ void PerformActionOnAllSelections(PredefinedSelectionTransformations action);
+
+ /// <summary>
+ /// Performs a custom action on all <see cref="Selection"/>s contained by <see cref="TextView"/>.
+ /// </summary>
+ /// <param name="action">The action to perform. This will be called once per Selection
+ /// and the supplied <see cref="ISelectionTransformer"/> contains methods to adjust an individual Selection.</param>
+ /// <remarks>Overlapping selections will be merged after all actions have been performed.</remarks>
+ void PerformActionOnAllSelections(Action<ISelectionTransformer> action);
+
+ /// <summary>
+ /// Attempts to perform a predefined action on a single <see cref="Selection"/>.
+ /// </summary>
+ /// <param name="before">The selection on which to perform the manipulation</param>
+ /// <param name="action">The manipulation to perform.</param>
+ /// <param name="after">Overlapping selections will be merged after the manipulation has been performed.
+ /// This parameter reports back the Selection post manipulation and post merge.</param>
+ /// <returns><c>true</c> if the manipulation was performed. <c>false</c> otherwise. Typically, <c>false</c> implies that the
+ /// before Selection did not exist in <see cref="AllSelections"/>.</returns>
+ bool TryPerformActionOnSelection(Selection before, PredefinedSelectionTransformations action, out Selection after);
+
+ /// <summary>
+ /// Attempts to perform a custom action on a single <see cref="Selection"/>.
+ /// </summary>
+ /// <param name="before">The selection on which to perform the action.</param>
+ /// <param name="action">The action to perform.</param>
+ /// <param name="after">Overlapping selections will be merged after the action has been performed.
+ /// This parameter reports back the Selection post action and post merge.</param>
+ /// <returns><c>true</c> if the action was performed. <c>false</c> otherwise. Typically, <c>false</c> implies that the
+ /// beforeSelection did not exist in <see cref="AllSelections"/>.</returns>
+ bool TryPerformActionOnSelection(Selection before, Action<ISelectionTransformer> action, out Selection after);
+
+ /// <summary>
+ /// Attempts to make the given Selection visible in the view.
+ /// </summary>
+ /// <param name="selection">The selection to ensure visiblity on.</param>
+ /// <param name="options">How the selection span should be made visible.</param>
+ /// <returns><c>true</c> if the selection was in <see cref="AllSelections"/> and is now in view. <c>false</c> otherwise.</returns>
+ /// /// <remarks>
+ /// This will first ensure that the selection span is visible, erring on the side of showing the <see cref="Selection.ActivePoint"/>.
+ /// Then if the <see cref="Selection.InsertionPoint"/> is different than the <see cref="Selection.ActivePoint"/>, the
+ /// <see cref="Selection.InsertionPoint"/> will be ensured visible.
+ /// </remarks>
+ bool TryEnsureVisible(Selection selection, EnsureSpanVisibleOptions options);
+
+
+ /// <summary>
+ /// Adds a box of selections with the given points as its corners.
+ /// </summary>
+ /// <param name="selection">A selection defining the characteristics of the box.</param>
+ /// <remarks>
+ /// Calling this method will clear all existing selections.
+ /// </remarks>
+ void SetBoxSelection(Selection selection);
+
+ /// <summary>
+ /// If <see cref="IsBoxSelection"/> is <c>true</c>, returns an instantiated <see cref="Selection"/>
+ /// which the caller can interrogate or manipulate to work with the box itself. Calls to
+ /// <see cref="AllSelections"/> or <see cref="GetSelectionsIntersectingSpan(SnapshotSpan)"/> will return individual
+ /// per line entries rather than the full box.
+ ///
+ /// If <see cref="IsBoxSelection"/> is <c>false</c>, this will return null.
+ /// </summary>
+ Selection BoxSelection { get; }
+
+ /// <summary>
+ /// Returns <c>true</c> if <see cref="SetBoxSelection(Selection)"/> has been
+ /// called, and selections are being managed by the box geometry, instead of manually by the user. <see cref="ClearSecondarySelections"/>
+ /// and <see cref="BreakBoxSelection"/> will both revert this to <c>false</c>, and several other methods like
+ /// <see cref="AddSelection(Selection)"/> will indirectly also set this back to <c>false</c>.
+ /// </summary>
+ bool IsBoxSelection { get; }
+
+ /// <summary>
+ /// Clears <see cref="BoxSelection"/>, but retains the current state of selections. This is a useful utility when performing gestures like End and Home
+ /// where each selection moves, but the result is not necessarily a box.
+ /// </summary>
+ void BreakBoxSelection();
+
+ #endregion
+
+ #region Get Selections
+
+ /// <summary>
+ /// Gets the list of spans within <see cref="CurrentSnapshot"/> that are selected. While two selections cannot
+ /// overlap, they may inhabit virtual space, and selections may be adjacent. This will merge those spans and return
+ /// the minimum set of spans that could be used to describe the selection. This can be a costly operation
+ /// and should only be run when needed.
+ /// </summary>
+ NormalizedSnapshotSpanCollection SelectedSpans { get; }
+
+ /// <summary>
+ /// Gives the set of spans selected. There is exactly one span per selection, but it may be empty.
+ /// They will be sorted in the order of appearence in the document.
+ /// </summary>
+ IReadOnlyList<VirtualSnapshotSpan> VirtualSelectedSpans { get; }
+
+ /// <summary>
+ /// Gets the span containing all selections, complete with virtual space.
+ /// </summary>
+ VirtualSnapshotSpan SelectionExtent { get; }
+
+ #endregion
+
+ #region Environment Integration
+
+ /// <summary>
+ /// Whether or not selections are active within <see cref="TextView"/>.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// If <see cref="ActivationTracksFocus"/> is <c>true</c>, this property is automatically
+ /// updated when the <see cref="ITextView"/> gains and loses aggregate focus. You can still
+ /// override it while <see cref="ActivationTracksFocus"/> is <c>false</c>, but the value will change
+ /// whenever focus changes.
+ /// </para>
+ /// </remarks>
+ bool AreSelectionsActive { get; set; }
+
+ /// <summary>
+ /// Determines whether <see cref="AreSelectionsActive"/> should track when the <see cref="ITextView"/> gains and
+ /// loses aggregate focus. The default is <c>true</c>.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// While the value of this property is <c>true</c>, the value of <see cref="AreSelectionsActive"/> will track
+ /// <see cref="ITextView.HasAggregateFocus"/>. When the value of this property changes to <c>true</c>,
+ /// the value of <see cref="AreSelectionsActive"/> will be immediately updated.
+ /// </para>
+ /// </remarks>
+ bool ActivationTracksFocus { get; set; }
+
+ /// <summary>
+ /// Occurs when selections are added/removed/updated. Also when the primary selection is changed, and when
+ /// box selection mode is entered/exited.
+ /// </summary>
+ event EventHandler MultiSelectionSessionChanged;
+
+ /// <summary>
+ /// Temporarily disables <see cref="MultiSelectionSessionChanged"/>, but instead queues up all actions
+ /// to be included in the resultant <see cref="MultiSelectionChangedEventArgs"/> once the operation
+ /// is completed. Selection merges will also be deferred until the end of batch operations.
+ /// </summary>
+ /// <returns>An object that should be disposed once the batch operation is complete.</returns>
+ /// </param>
+ IDisposable BeginBatchOperation();
+
+ #endregion
+
+ /// <summary>
+ /// Trys to get the UI properties associated with the given Selection.
+ /// </summary>
+ /// <param name="selection">The selection of interest.</param>
+ /// <param name="properties">Returns out the properties if successful.</param>
+ /// <returns><c>true</c> if the supplied selection was found and the properties returned. <c>false</c> otherwise.</returns>
+ bool TryGetSelectionPresentationProperties(Selection selection, out AbstractSelectionPresentationProperties properties);
+
+ /// <summary>
+ /// Performs the given transformation on the given Selection without updating <see cref="AllSelections"/>.
+ /// The behavior of Preferred X and Y coordinates for selections that are already in the broker is undefined.
+ /// </summary>
+ /// <param name="source">The selection to transform</param>
+ /// <param name="">The transformation to perform</param>
+ /// <returns>The transformed selection</returns>
+ Selection TransformSelection(Selection source, PredefinedSelectionTransformations transformation);
+ }
+}
diff --git a/src/Text/Def/TextUI/MultiCaret/ISelectionTransformer.cs b/src/Text/Def/TextUI/MultiCaret/ISelectionTransformer.cs
new file mode 100644
index 0000000..5c842ad
--- /dev/null
+++ b/src/Text/Def/TextUI/MultiCaret/ISelectionTransformer.cs
@@ -0,0 +1,82 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+namespace Microsoft.VisualStudio.Text
+{
+ /// <summary>
+ /// Allows changing existing <see cref="ISelection"/> objects as part of <see cref="IMultiSelectionBroker.PerformActionOnAllSelections(System.Action{ISelectionTransformer})"
+ /// and <see cref="IMultiSelectionBroker.TryPerformActionOnRegion(ISelection, out ISelection, System.Action{ISelectionTransformer})"/>./>
+ /// </summary>
+ public interface ISelectionTransformer
+ {
+ /// <summary>
+ /// Gets the Selection to transform. This will change through calls to <see cref="PerformAction(PredefinedSelectionTransformations)"/>,
+ /// <see cref="MoveTo(VirtualSnapshotPoint, bool, PositionAffinity)"/>, and
+ /// <see cref="MoveTo(VirtualSnapshotPoint, VirtualSnapshotPoint, VirtualSnapshotPoint, PositionAffinity)"/>.
+ /// </summary>
+ Selection Selection { get; }
+
+ /// <summary>
+ /// Moves the insertion and active points to the given location.
+ /// </summary>
+ /// <param name="point">The point to move to.</param>
+ /// <param name="select">If <c>true</c>, leaves the anchor point where it is. If <c>false</c>, moves the anchor point too.</param>
+ /// <param name="insertionPointAffinity">
+ /// The affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ void MoveTo(VirtualSnapshotPoint point, bool select, PositionAffinity insertionPointAffinity);
+
+ /// <summary>
+ /// Sets the anchor, active, and insertion points to the specified locations.
+ /// </summary>
+ /// <param name="anchorPoint">Specifies the stationary end of the selection span.</param>
+ /// <param name="activePoint">Specifies the mobile end of the selection span.</param>
+ /// <param name="insertionPoint">Specifies the location of the caret.</param>
+ /// <param name="insertionPointAffinity">
+ /// Specifies the affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ void MoveTo(VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint, VirtualSnapshotPoint insertionPoint, PositionAffinity insertionPointAffinity);
+
+ /// <summary>
+ /// Updates internal state to cache the current location as the desired reference point for navigation events.
+ /// </summary>
+ /// <remarks>
+ /// This affects events like <see cref="PredefinedSelectionTransformations.MoveToPreviousLine"/> where the current
+ /// X location of the rendered caret is used to project to the new location. Typically this method should be called
+ /// in cases where the user is stating where they want to focus. Since this grabs the current state, there is no
+ /// equivalent release method.
+ /// </remarks>
+ void CapturePreferredReferencePoint();
+
+ /// <summary>
+ /// Updates internal state to cache the current x location as the desired reference point for navigation events.
+ /// </summary>
+ /// <remarks>
+ /// This affects events like <see cref="PredefinedSelectionTransformations.MoveToPreviousLine"/> where the current
+ /// X location of the rendered caret is used to project to the new location. Typically this method should be called
+ /// in cases where the user is stating where they want to focus. Since this grabs the current state, there is no
+ /// equivalent release method.
+ /// </remarks>
+ void CapturePreferredXReferencePoint();
+
+ /// <summary>
+ /// Updates internal state to cache the current y location as the desired reference point for navigation events.
+ /// </summary>
+ /// <remarks>
+ /// This affects events like <see cref="PredefinedSelectionTransformations.MovePageUp"/> where the current
+ /// Y location of the rendered caret is used to project to the new location. Typically this method should be called
+ /// in cases where the user is stating where they want to focus. Since this grabs the current state, there is no
+ /// equivalent release method.
+ /// </remarks>
+ void CapturePreferredYReferencePoint();
+
+ /// <summary>
+ /// Transforms <see cref="Selection"/> in a predefined way.
+ /// </summary>
+ /// <param name="action">The kind of transformation to perform</param>
+ void PerformAction(PredefinedSelectionTransformations action);
+ }
+}
diff --git a/src/Text/Def/TextUI/MultiCaret/PredefinedSelectionTransformations.cs b/src/Text/Def/TextUI/MultiCaret/PredefinedSelectionTransformations.cs
new file mode 100644
index 0000000..d8ced81
--- /dev/null
+++ b/src/Text/Def/TextUI/MultiCaret/PredefinedSelectionTransformations.cs
@@ -0,0 +1,158 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+namespace Microsoft.VisualStudio.Text
+{
+ /// <summary>
+ /// Defines a set of actions that are predefined for manipulating selections within a view. For custom manipulations see the usage
+ /// of <see cref="ISelectionTransformer"/>. These transformations can be passed in to
+ /// <see cref="IMultiSelectionBroker.PerformActionOnAllSelections(PredefinedSelectionTransformations)"/>,
+ /// <see cref="IMultiSelectionBroker.TryPerformActionOnSelection(Selection, PredefinedSelectionTransformations, out Selection)"/>,
+ /// and <see cref="ISelectionTransformer.PerformAction(PredefinedSelectionTransformations)"/>.
+ /// </summary>
+#pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names
+ public enum PredefinedSelectionTransformations
+#pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names
+ {
+ /// <summary>
+ /// Resets the active and anchor points to be at the insertion point.
+ /// </summary>
+ ClearSelection,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points ahead one position in the view.
+ /// </summary>
+ MoveToNextCaretPosition,
+
+ /// <summary>
+ /// Moves the active and insertion points ahead one position in the view, keeping the anchor point where it is.
+ /// </summary>
+ SelectToNextCaretPosition,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points back one position in the view.
+ /// </summary>
+ MoveToPreviousCaretPosition,
+
+ /// <summary>
+ /// Moves the active and insertion points back one position in the view, keeping the anchor point where it is.
+ /// </summary>
+ SelectToPreviousCaretPosition,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points ahead to the beginning of the next word.
+ /// </summary>
+ MoveToNextWord,
+
+ /// <summary>
+ /// Moves the active and insertion points ahead to the beginning of the next word, keeping the anchor point where it is.
+ /// </summary>
+ SelectToNextWord,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points back to the end of the previous word.
+ /// </summary>
+ MoveToPreviousWord,
+
+ /// <summary>
+ /// Moves the active and insertion points back to the end of the previous word, keeping the anchor point where it is.
+ /// </summary>
+ SelectToPreviousWord,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points back to the beginning of the current line.
+ /// </summary>
+ MoveToBeginningOfLine,
+
+ /// <summary>
+ /// Moves the active and insertion points back to the beginning of the current line, keeping the anchor point where it is.
+ /// </summary>
+ SelectToBeginningOfLine,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points alternately between the beginning of the line, and the first non-whitespace character.
+ /// </summary>
+ MoveToHome,
+
+ /// <summary>
+ /// Moves the active and insertion points alternately between the beginning of the line, and the first non-whitespace character, keeping the anchor point where it is.
+ /// </summary>
+ SelectToHome,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points ahead to the end of the current line.
+ /// </summary>
+ MoveToEndOfLine,
+
+ /// <summary>
+ /// Moves the active and insertion points ahead to the end of the current line, keeping the anchor point where it is.
+ /// </summary>
+ SelectToEndOfLine,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points ahead to next line, staying as close to the user's preferred x-coordinate in the view as possible.
+ /// </summary>
+ MoveToNextLine,
+
+ /// <summary>
+ /// Moves the active and insertion points ahead to next line, staying as close to the user's preferred x-coordinate in the view as possible, keeping the anchor point where it is.
+ /// </summary>
+ SelectToNextLine,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points back to the previous line, staying as close to the user's preferred x-coordinate in the view as possible.
+ /// </summary>
+ MoveToPreviousLine,
+
+ /// <summary>
+ /// Moves the active and insertion points back to the previous line, staying as close to the user's preferred x-coordinate in the view as possible, keeping the anchor point where it is.
+ /// </summary>
+ SelectToPreviousLine,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points back one viewport height, staying as close to the user's preferred x and y coordinates in the view as possible.
+ /// </summary>
+ MovePageUp,
+
+ /// <summary>
+ /// Moves the active and insertion points back one viewport height, staying as close to the user's preferred x and y coordinates in the view as possible, keeping the anchor point where it is.
+ /// </summary>
+ SelectPageUp,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points ahead one viewport height, staying as close to the user's preferred x and y coordinates in the view as possible.
+ /// </summary>
+ MovePageDown,
+
+ /// <summary>
+ /// Moves the active and insertion points ahead one viewport height, staying as close to the user's preferred x and y coordinates in the view as possible, keeping the anchor point where it is.
+ /// </summary>
+ SelectPageDown,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points back to the beginning of the document.
+ /// </summary>
+ MoveToStartOfDocument,
+
+ /// <summary>
+ /// Moves the active and insertion points back to the beginning of the document, keeping the anchor point where it is.
+ /// </summary>
+ SelectToStartOfDocument,
+
+ /// <summary>
+ /// Moves the active, anchor, and insertion points ahead to the end of the document.
+ /// </summary>
+ MoveToEndOfDocument,
+
+ /// <summary>
+ /// Moves the active and insertion points ahead to the end of the document, keeping the anchor point where it is.
+ /// </summary>
+ SelectToEndOfDocument,
+
+ /// <summary>
+ /// Moves the anchor point to the beginning of the current word. Moves the active and insertion points to the end of the current word.
+ /// </summary>
+ SelectCurrentWord
+ }
+}
diff --git a/src/Text/Def/TextUI/MultiCaret/Selection.cs b/src/Text/Def/TextUI/MultiCaret/Selection.cs
new file mode 100644
index 0000000..7f27834
--- /dev/null
+++ b/src/Text/Def/TextUI/MultiCaret/Selection.cs
@@ -0,0 +1,313 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+using System;
+
+namespace Microsoft.VisualStudio.Text
+{
+ /// <summary>
+ /// Manages the insertion, anchor, and active points for a single caret and its associated
+ /// selection.
+ /// </summary>
+ public struct Selection : IEquatable<Selection>
+ {
+ /// <summary>
+ /// A static instance of a selection that is invalid and can be used to check for instantiation.
+ /// </summary>
+ public static readonly Selection Invalid = new Selection();
+
+ /// <summary>
+ /// Instantiates a new Selection with a zero-width extent at the provided insertion point.
+ /// </summary>
+ /// <param name="insertionPoint">The location where a caret should be rendered and edits performed.</param>
+ /// <param name="insertionPointAffinity">
+ /// The affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ public Selection(VirtualSnapshotPoint insertionPoint, PositionAffinity insertionPointAffinity = PositionAffinity.Successor)
+ : this(insertionPoint, insertionPoint, insertionPoint, insertionPointAffinity)
+ { }
+
+ /// <summary>
+ /// Instantiates a new Selection with a zero-width extent at the provided insertion point.
+ /// </summary>
+ /// <param name="insertionPoint">The location where a caret should be rendered and edits performed.</param>
+ /// <param name="insertionPointAffinity">
+ /// The affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ public Selection(SnapshotPoint insertionPoint, PositionAffinity insertionPointAffinity = PositionAffinity.Successor)
+ : this(new VirtualSnapshotPoint(insertionPoint),
+ new VirtualSnapshotPoint(insertionPoint),
+ new VirtualSnapshotPoint(insertionPoint),
+ insertionPointAffinity)
+ { }
+
+ /// <summary>
+ /// Instantiates a new Selection with the given extent. Anchor and active points are defined by isReversed, and the
+ /// insertion point is located at the active point.
+ /// </summary>
+ /// <param name="extent">The span that the selection covers.</param>
+ /// <param name="isReversed">
+ /// True implies that <see cref="ActivePoint"/> comes before <see cref="AnchorPoint"/>.
+ /// The <see cref="InsertionPoint"/> is set to the <see cref="ActivePoint"/>.
+ /// <see cref="InsertionPointAffinity"/> is set to <see cref="PositionAffinity.Predecessor"/> when isReversed is true.
+ /// <see cref="PositionAffinity.Successor"/> otherwise.
+ /// </param>
+ public Selection(VirtualSnapshotSpan extent, bool isReversed = false)
+ {
+ if (isReversed)
+ {
+ AnchorPoint = extent.End;
+ ActivePoint = InsertionPoint = extent.Start;
+ InsertionPointAffinity = PositionAffinity.Successor;
+ }
+ else
+ {
+ AnchorPoint = extent.Start;
+ ActivePoint = InsertionPoint = extent.End;
+
+ // The goal here is to keep the caret with the selection box. If we're wordwrapped, and the
+ // box is at the end of a line, Predecessor will keep the caret on the previous line.
+ InsertionPointAffinity = PositionAffinity.Predecessor;
+ }
+ }
+
+ /// <summary>
+ /// Instantiates a new Selection with the given extent. Anchor and active points are defined by isReversed, and the
+ /// insertion point is located at the active point.
+ /// </summary>
+ /// <param name="extent">The span that the selection covers.</param>
+ /// <param name="isReversed">
+ /// True implies that <see cref="ActivePoint"/> comes before <see cref="AnchorPoint"/>.
+ /// The <see cref="InsertionPoint"/> is set to the <see cref="ActivePoint"/>.
+ /// <see cref="InsertionPointAffinity"/> is set to <see cref="PositionAffinity.Predecessor"/> when isReversed is true.
+ /// <see cref="PositionAffinity.Successor"/> otherwise.
+ /// </param>
+ public Selection(SnapshotSpan extent, bool isReversed = false)
+ : this(new VirtualSnapshotSpan(extent), isReversed)
+ { }
+
+ /// <summary>
+ /// Instantiates a new Selection with the given anchor and active points, and the
+ /// insertion point is located at the active point.
+ /// </summary>
+ /// <param name="anchorPoint">The location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is.</param>
+ /// <param name="activePoint">location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click.</param>
+ public Selection(VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint)
+ : this(insertionPoint: activePoint,
+ anchorPoint: anchorPoint,
+ activePoint: activePoint,
+ insertionPointAffinity: (anchorPoint < activePoint) ? PositionAffinity.Predecessor : PositionAffinity.Successor)
+ {
+ }
+
+ /// <summary>
+ /// Instantiates a new Selection with the given anchor and active points, and the
+ /// insertion point is located at the active point.
+ /// </summary>
+ /// <param name="anchorPoint">The location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is.</param>
+ /// <param name="activePoint">location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click.</param>
+ public Selection(SnapshotPoint anchorPoint, SnapshotPoint activePoint)
+ : this(anchorPoint: new VirtualSnapshotPoint(anchorPoint),
+ activePoint: new VirtualSnapshotPoint(activePoint))
+ {
+ }
+
+ /// <summary>
+ /// Instantiates a new Selection.
+ /// </summary>
+ /// <param name="insertionPoint">The location where a caret should be rendered and edits performed.</param>
+ /// <param name="anchorPoint">The location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is.</param>
+ /// <param name="activePoint">location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click.</param>
+ /// <param name="insertionPointAffinity">
+ /// The affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ public Selection(VirtualSnapshotPoint insertionPoint,
+ VirtualSnapshotPoint anchorPoint,
+ VirtualSnapshotPoint activePoint,
+ PositionAffinity insertionPointAffinity = PositionAffinity.Successor)
+ {
+ if (insertionPoint.Position.Snapshot != anchorPoint.Position.Snapshot || insertionPoint.Position.Snapshot != activePoint.Position.Snapshot)
+ {
+ throw new ArgumentException("All points must be on the same snapshot.");
+ }
+
+ InsertionPoint = insertionPoint;
+ AnchorPoint = anchorPoint;
+ ActivePoint = activePoint;
+ InsertionPointAffinity = insertionPointAffinity;
+ }
+
+ /// <summary>
+ /// Instantiates a new Selection.
+ /// </summary>
+ /// <param name="insertionPoint">The location where a caret should be rendered and edits performed.</param>
+ /// <param name="anchorPoint">The location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is.</param>
+ /// <param name="activePoint">location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click.</param>
+ /// <param name="insertionPointAffinity">
+ /// The affinity of the insertion point. This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </param>
+ public Selection(SnapshotPoint insertionPoint,
+ SnapshotPoint anchorPoint,
+ SnapshotPoint activePoint,
+ PositionAffinity insertionPointAffinity = PositionAffinity.Successor)
+ : this(new VirtualSnapshotPoint(insertionPoint),
+ new VirtualSnapshotPoint(anchorPoint),
+ new VirtualSnapshotPoint(activePoint),
+ insertionPointAffinity)
+ { }
+
+ /// <summary>
+ /// Gets whether this selection contains meaningful data.
+ /// </summary>
+ public bool IsValid
+ {
+ get
+ {
+ return this != Invalid && this.InsertionPoint.Position.Snapshot != null;
+ }
+ }
+
+ /// <summary>
+ /// Gets the location where a caret should be rendered and edits performed.
+ /// </summary>
+ public VirtualSnapshotPoint InsertionPoint { get; }
+
+ /// <summary>
+ /// Gets the location of the fixed selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would remain where it is. If this is an empty selection, this will be at the
+ /// <see cref="InsertionPoint"/>.
+ /// </summary>
+ public VirtualSnapshotPoint AnchorPoint { get; }
+
+ /// <summary>
+ /// Gets the location of the movable selection endpoint, meaning if a user were to hold shift and click,
+ /// this point would be changed to the location of the click. If this is an empty selection, this will be at the
+ /// <see cref="InsertionPoint"/>.
+ /// </summary>
+ public VirtualSnapshotPoint ActivePoint { get; }
+
+ /// <summary>
+ /// Gets the affinity of the insertion point.
+ /// This is used in places like word-wrap where one buffer position can represent both the
+ /// end of one line and the beginning of the next.
+ /// </summary>
+ public PositionAffinity InsertionPointAffinity { get; }
+
+ /// <summary>
+ /// True if <see cref="AnchorPoint"/> is later in the document than <see cref="ActivePoint"/>. False otherwise.
+ /// </summary>
+ public bool IsReversed
+ {
+ get
+ {
+ return ActivePoint < AnchorPoint;
+ }
+ }
+
+ /// <summary>
+ /// True if <see cref="AnchorPoint"/> equals <see cref="ActivePoint"/>. False otherwise.
+ /// </summary>
+ public bool IsEmpty
+ {
+ get
+ {
+ return ActivePoint == AnchorPoint;
+ }
+ }
+
+ /// <summary>
+ /// Returns the smaller of <see cref="ActivePoint"/> and <see cref="AnchorPoint"/>.
+ /// </summary>
+ public VirtualSnapshotPoint Start
+ {
+ get
+ {
+ return IsReversed ? ActivePoint : AnchorPoint;
+ }
+ }
+
+ /// <summary>
+ /// Returns the larger of <see cref="ActivePoint"/> and <see cref="AnchorPoint"/>.
+ /// </summary>
+ public VirtualSnapshotPoint End
+ {
+ get
+ {
+ return IsReversed ? AnchorPoint : ActivePoint;
+ }
+ }
+
+ /// <summary>
+ /// Returns the span from <see cref="Start"/> to <see cref="End"/>.
+ /// </summary>
+ public VirtualSnapshotSpan Extent
+ {
+ get
+ {
+ return new VirtualSnapshotSpan(Start, End);
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ // We are fortunate enough to have 3 interesting points here. If you xor an even number of snapshot point hashcodes
+ // together, the snapshot component gets cancelled out.
+
+ // However, the common case is that ActivePoint and InsertionPoint are exactly equal, so we need to do something to change that.
+ // Invert the bytes in InsertionPoint.GetHashCode().
+ var insertionHash = (uint)InsertionPoint.GetHashCode();
+ insertionHash = (((0x0000FFFF & insertionHash) << 16) | ((0xFFFF0000 & insertionHash) >> 16));
+
+ int pointHashes = AnchorPoint.GetHashCode() ^ ActivePoint.GetHashCode() ^ (int)insertionHash;
+
+ // InsertionPointAffinity.GetHashCode() returns either 0 or 1 which can get stomped on by the rest of the hash codes.
+ // Generate more interesting hash code values below:
+ int affinityHash = InsertionPointAffinity == PositionAffinity.Predecessor
+ ? affinityHash = 04122013
+ : affinityHash = 10172014;
+
+ return pointHashes ^ affinityHash;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is Selection && Equals((Selection)obj);
+ }
+
+ public bool Equals(Selection other)
+ {
+ return this.ActivePoint == other.ActivePoint
+ && this.AnchorPoint == other.AnchorPoint
+ && this.InsertionPoint == other.InsertionPoint
+ && this.InsertionPointAffinity == other.InsertionPointAffinity;
+ }
+
+ public static bool operator ==(Selection left, Selection right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Selection left, Selection right)
+ {
+ return !left.Equals(right);
+ }
+
+ public override string ToString()
+ {
+ return $"Ins:{InsertionPoint} Anc:{AnchorPoint} Act:{ActivePoint} Aff:{InsertionPointAffinity}";
+ }
+ }
+}
diff --git a/src/Text/Def/TextUI/Operations/IUndoMetadataEditTag.cs b/src/Text/Def/TextUI/Operations/IUndoMetadataEditTag.cs
new file mode 100644
index 0000000..b965983
--- /dev/null
+++ b/src/Text/Def/TextUI/Operations/IUndoMetadataEditTag.cs
@@ -0,0 +1,28 @@
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+//
+using Microsoft.VisualStudio.Text.Editor;
+
+namespace Microsoft.VisualStudio.Text.Operations
+{
+#if false // Work in progress
+ public interface IUndoMetadataEditTag : IEditTag
+ {
+ /// <summary>
+ /// The view from which the edit was initiated. May be null.
+ /// </summary>
+ ITextView InitiatingView { get; }
+
+ /// <summary>
+ /// A localized description of the edit (which can be displayed in the undo list).
+ /// </summary>
+ string Description { get; }
+
+ /// <summary>
+ /// Consecutive edits with the same, non-null, may be merged.
+ /// </summary>
+ object MergeType { get; }
+ }
+#endif
+}
diff --git a/src/Text/Def/TextUI/Strings.Designer.cs b/src/Text/Def/TextUI/Strings.Designer.cs
deleted file mode 100644
index f993ce8..0000000
--- a/src/Text/Def/TextUI/Strings.Designer.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-//------------------------------------------------------------------------------
-// <auto-generated>
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-// </auto-generated>
-//------------------------------------------------------------------------------
-
-namespace Microsoft.VisualStudio.Text.Editor {
- using System;
-
-
- /// <summary>
- /// A strongly-typed resource class, for looking up localized strings, etc.
- /// </summary>
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // 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.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Strings {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Strings() {
- }
-
- /// <summary>
- /// Returns the cached ResourceManager instance used by this class.
- /// </summary>
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- 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.Editor.Strings", typeof(Strings).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- /// <summary>
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- /// </summary>
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to Buffer mismatch between oldSnapshot and newSnapshot..
- /// </summary>
- internal static string BufferMismatch {
- get {
- return ResourceManager.GetString("BufferMismatch", resourceCulture);
- }
- }
-
- /// <summary>
- /// Looks up a localized string similar to oldSnapshot&apos;s version is not older than newSnapshot&apos;s version..
- /// </summary>
- internal static string VersionError {
- get {
- return ResourceManager.GetString("VersionError", resourceCulture);
- }
- }
- }
-}
diff --git a/src/Text/Def/TextUI/Strings.resx b/src/Text/Def/TextUI/Strings.resx
deleted file mode 100644
index 279c501..0000000
--- a/src/Text/Def/TextUI/Strings.resx
+++ /dev/null
@@ -1,126 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<root>
- <!--
- Microsoft ResX Schema
-
- Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
- associated with the data types.
-
- Example:
-
- ... ado.net/XML headers & schema ...
- <resheader name="resmimetype">text/microsoft-resx</resheader>
- <resheader name="version">2.0</resheader>
- <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
- <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
- <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
- <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
- <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
- <value>[base64 mime encoded serialized .NET Framework object]</value>
- </data>
- <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
- <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
- <comment>This is a comment</comment>
- </data>
-
- There are any number of "resheader" rows that contain simple
- name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
- mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
- extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
- read any of the formats listed below.
-
- mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
- : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
- : and then encoded with base64 encoding.
-
- mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
- : using a System.ComponentModel.TypeConverter
- : and then encoded with base64 encoding.
- -->
- <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
- <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
- <xsd:element name="root" msdata:IsDataSet="true">
- <xsd:complexType>
- <xsd:choice maxOccurs="unbounded">
- <xsd:element name="metadata">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" />
- </xsd:sequence>
- <xsd:attribute name="name" use="required" type="xsd:string" />
- <xsd:attribute name="type" type="xsd:string" />
- <xsd:attribute name="mimetype" type="xsd:string" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="assembly">
- <xsd:complexType>
- <xsd:attribute name="alias" type="xsd:string" />
- <xsd:attribute name="name" type="xsd:string" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="data">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
- <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
- <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
- <xsd:attribute ref="xml:space" />
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="resheader">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
- </xsd:sequence>
- <xsd:attribute name="name" type="xsd:string" use="required" />
- </xsd:complexType>
- </xsd:element>
- </xsd:choice>
- </xsd:complexType>
- </xsd:element>
- </xsd:schema>
- <resheader name="resmimetype">
- <value>text/microsoft-resx</value>
- </resheader>
- <resheader name="version">
- <value>2.0</value>
- </resheader>
- <resheader name="reader">
- <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <resheader name="writer">
- <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
- </resheader>
- <data name="BufferMismatch" xml:space="preserve">
- <value>Buffer mismatch between oldSnapshot and newSnapshot.</value>
- </data>
- <data name="VersionError" xml:space="preserve">
- <value>oldSnapshot's version is not older than newSnapshot's version.</value>
- </data>
-</root> \ No newline at end of file
diff --git a/src/Text/Def/TextUI/Tags/ErrorTag.cs b/src/Text/Def/TextUI/Tags/ErrorTag.cs
index 2867819..9b2b029 100644
--- a/src/Text/Def/TextUI/Tags/ErrorTag.cs
+++ b/src/Text/Def/TextUI/Tags/ErrorTag.cs
@@ -22,7 +22,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
public ErrorTag(string errorType, object toolTipContent)
{
if (errorType == null)
- throw new ArgumentNullException("errorType");
+ throw new ArgumentNullException(nameof(errorType));
ErrorType = errorType;
ToolTipContent = toolTipContent;
diff --git a/src/Text/Def/TextUI/Tags/TextMarkerTag.cs b/src/Text/Def/TextUI/Tags/TextMarkerTag.cs
index a36ec85..fd3284c 100644
--- a/src/Text/Def/TextUI/Tags/TextMarkerTag.cs
+++ b/src/Text/Def/TextUI/Tags/TextMarkerTag.cs
@@ -19,7 +19,7 @@ namespace Microsoft.VisualStudio.Text.Tagging
public TextMarkerTag(string type)
{
if (type == null)
- throw new ArgumentNullException("type");
+ throw new ArgumentNullException(nameof(type));
Type = type;
}
diff --git a/src/Text/Def/TextUI/TextUI.csproj b/src/Text/Def/TextUI/TextUI.csproj
index 18cf4e5..c5839d2 100644
--- a/src/Text/Def/TextUI/TextUI.csproj
+++ b/src/Text/Def/TextUI/TextUI.csproj
@@ -1,13 +1,12 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Microsoft.VisualStudio.Text.UI</AssemblyName>
<RootNamespace>Microsoft.VisualStudio.Text.Editor</RootNamespace>
<TargetFramework>net46</TargetFramework>
- <NonShipping>false</NonShipping>
- <IsPackable>true</IsPackable>
<PushToPublicFeed>true</PushToPublicFeed>
<NoWarn>649;436;$(NoWarn)</NoWarn>
<AssemblyAttributeClsCompliant>true</AssemblyAttributeClsCompliant>
+ <LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
diff --git a/src/Text/Def/TextUI/Utilities/AbstractUIThreadOperationContext.cs b/src/Text/Def/TextUI/Utilities/AbstractUIThreadOperationContext.cs
index 8047106..05a522b 100644
--- a/src/Text/Def/TextUI/Utilities/AbstractUIThreadOperationContext.cs
+++ b/src/Text/Def/TextUI/Utilities/AbstractUIThreadOperationContext.cs
@@ -5,10 +5,12 @@ using System.Threading;
namespace Microsoft.VisualStudio.Utilities
{
+#pragma warning disable CA1063 // Implement IDisposable Correctly
/// <summary>
/// Abstract base implementation of the <see cref="IUIThreadOperationContext"/> interface.
/// </summary>
public abstract class AbstractUIThreadOperationContext : IUIThreadOperationContext
+#pragma warning restore CA1063 // Implement IDisposable Correctly
{
private List<IUIThreadOperationScope> _scopes;
private bool _allowCancellation;
@@ -137,11 +139,14 @@ namespace Microsoft.VisualStudio.Utilities
{
}
+#pragma warning disable CA1063 // Implement IDisposable Correctly
/// <summary>
/// Disposes this instance.
/// </summary>
public virtual void Dispose()
+#pragma warning restore CA1063 // Implement IDisposable Correctly
{
+ GC.SuppressFinalize(this);
}
/// <summary>
@@ -193,7 +198,7 @@ namespace Microsoft.VisualStudio.Utilities
get { return _description; }
set
{
- if (_description != value)
+ if (!string.Equals(_description, value, StringComparison.Ordinal))
{
_description = value;
_context.OnScopeChanged(this);
diff --git a/src/Text/Def/TextUI/Utilities/IUIThreadOperationScope.cs b/src/Text/Def/TextUI/Utilities/IUIThreadOperationScope.cs
index 8f1d05f..4995d63 100644
--- a/src/Text/Def/TextUI/Utilities/IUIThreadOperationScope.cs
+++ b/src/Text/Def/TextUI/Utilities/IUIThreadOperationScope.cs
@@ -29,10 +29,12 @@ namespace Microsoft.VisualStudio.Utilities
IProgress<ProgressInfo> Progress { get; }
}
+#pragma warning disable CA1815 // Override equals and operator equals on value types
/// <summary>
/// Represents an update of a progress.
/// </summary>
public struct ProgressInfo
+#pragma warning restore CA1815 // Override equals and operator equals on value types
{
/// <summary>
/// A number of already completed items.
diff --git a/src/Text/Def/TextUI/Utilities/UIThreadOperationStatus.cs b/src/Text/Def/TextUI/Utilities/UIThreadOperationStatus.cs
index b7e47ae..b8b505d 100644
--- a/src/Text/Def/TextUI/Utilities/UIThreadOperationStatus.cs
+++ b/src/Text/Def/TextUI/Utilities/UIThreadOperationStatus.cs
@@ -1,9 +1,11 @@
namespace Microsoft.VisualStudio.Utilities
{
+#pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names
/// <summary>
/// Represents a status of executing a potentially long running operation on the UI thread.
/// </summary>
public enum UIThreadOperationStatus
+#pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names
{
/// <summary>
/// An operation was successfully completed.
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
diff --git a/src/Text/Util/TextDataUtil/BufferTracker.cs b/src/Text/Util/TextDataUtil/BufferTracker.cs
index f64e6bc..0ed007f 100644
--- a/src/Text/Util/TextDataUtil/BufferTracker.cs
+++ b/src/Text/Util/TextDataUtil/BufferTracker.cs
@@ -102,7 +102,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
foreach (KeyValuePair<Object, Object> pair in ((IPropertyOwner)buffer).Properties.PropertyList)
{
- if (pair.Key.ToString() != "tag")
+ if (!string.Equals(pair.Key.ToString(), "tag", StringComparison.Ordinal))
{
string rhsType;
if (pair.Value == null)
diff --git a/src/Text/Util/TextDataUtil/FrugalList.cs b/src/Text/Util/TextDataUtil/FrugalList.cs
index fe554fe..c6ac083 100644
--- a/src/Text/Util/TextDataUtil/FrugalList.cs
+++ b/src/Text/Util/TextDataUtil/FrugalList.cs
@@ -20,6 +20,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
#endif
+#pragma warning disable CA1710 // Identifiers should have correct suffix
/// <summary>
/// <para>
/// This implementation is intended for lists that are usually empty or have a single element.
@@ -36,6 +37,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
/// </summary>
/// <typeparam name="T">The type of the list element.</typeparam>
public class FrugalList<T> : IList<T>, IReadOnlyList<T>
+#pragma warning restore CA1710 // Identifiers should have correct suffix
{
const int InitialTailSize = 2; // initial size of array list
@@ -65,7 +67,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (elements == null)
{
- throw new ArgumentNullException("elements");
+ throw new ArgumentNullException(nameof(elements));
}
switch (elements.Count)
{
@@ -106,7 +108,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (list == null)
{
- throw new ArgumentNullException("list");
+ throw new ArgumentNullException(nameof(list));
}
for (int i = 0; i < list.Count; ++i)
@@ -124,7 +126,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (match == null)
{
- throw new ArgumentNullException("match");
+ throw new ArgumentNullException(nameof(match));
}
int removed = 0;
for (int i = Count - 1; i >= 0; --i)
@@ -180,7 +182,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (index < 0 || index > this.Count)
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
if (index == 0)
{
@@ -214,7 +216,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (index < 0 || index >= Count)
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
int count = Count;
@@ -249,13 +251,15 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
}
+ // Analyzer has a bug where it is giving a false positive in this location.
+#pragma warning disable CA1065 // Do not raise exceptions in unexpected locations
public T this[int index]
{
get
{
if (index < 0 || index >= Count)
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
if (index == 0)
@@ -271,7 +275,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (index < 0 || index >= Count)
{
- throw new ArgumentOutOfRangeException("index");
+ throw new ArgumentOutOfRangeException(nameof(index));
}
if (index == 0)
@@ -284,6 +288,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
}
}
+#pragma warning restore CA1065 // Do not raise exceptions in unexpected locations
public void Clear()
{
@@ -312,7 +317,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (array == null)
{
- throw new ArgumentNullException("array");
+ throw new ArgumentNullException(nameof(array));
}
int count = Count;
if (count > 0)
diff --git a/src/Text/Util/TextDataUtil/GuardedOperations.cs b/src/Text/Util/TextDataUtil/GuardedOperations.cs
index 4a9ff53..add89d2 100644
--- a/src/Text/Util/TextDataUtil/GuardedOperations.cs
+++ b/src/Text/Util/TextDataUtil/GuardedOperations.cs
@@ -25,7 +25,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
private List<Lazy<IExtensionErrorHandler>> _errorHandlerExports = null;
[ImportMany]
- private List<Lazy<IExtensionPerformanceTracker>> _perTrackerExports = null;
+ private List<Lazy<IExtensionPerformanceTracker>> _perfTrackerExports = null;
[Import]
private JoinableTaskContext _joinableTaskContext;
@@ -38,6 +38,9 @@ namespace Microsoft.VisualStudio.Text.Utilities
private FrugalList<IExtensionErrorHandler> _errorHandlers;
private FrugalList<IExtensionPerformanceTracker> _perfTrackers;
+ private static Exception LastHandledException = null;
+ private static string LastHandleExceptionStackTrace = null;
+
public GuardedOperations()
{
}
@@ -82,7 +85,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
catch (Exception)
{
- Debug.Fail("Exception instantiating error handler!");
+ GuardedOperations.Fail("Exception instantiating error handler!");
}
}
}
@@ -98,13 +101,13 @@ namespace Microsoft.VisualStudio.Text.Utilities
if (_perfTrackers == null)
{
_perfTrackers = new FrugalList<IExtensionPerformanceTracker>();
- if (_perTrackerExports != null) // can be null during unit testing
+ if (_perfTrackerExports != null) // can be null during unit testing
{
- foreach (var export in _perTrackerExports)
+ for (int i = 0; i < _perfTrackerExports.Count; i++)
{
try
{
- var perfTracker = export.Value;
+ var perfTracker = _perfTrackerExports[i].Value;
if (perfTracker != null)
{
_perfTrackers.Add(perfTracker);
@@ -112,7 +115,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
catch (Exception)
{
- Debug.Fail("Exception instantiating perf tracker");
+ GuardedOperations.Fail("Exception instantiating perf tracker");
}
}
}
@@ -150,8 +153,9 @@ namespace Microsoft.VisualStudio.Text.Utilities
where TMetadataView : IContentTypeMetadata
{
var candidates = new List<Lazy<TExtension, TMetadataView>>();
- foreach (var providerHandle in providerHandles)
+ for (int i = 0; (i < providerHandles.Count); ++i)
{
+ var providerHandle = providerHandles[i];
foreach (string contentTypeName in providerHandle.Metadata.ContentTypes)
{
if (string.Compare(dataContentType.TypeName, contentTypeName, StringComparison.OrdinalIgnoreCase) == 0)
@@ -385,6 +389,23 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
}
+ public void CallExtensionPoint(object errorSource, Action call, Predicate<Exception> exceptionFilter)
+ {
+ try
+ {
+ BeforeCallingExtensionPoint(errorSource ?? call);
+ call();
+ }
+ catch (Exception e) when (exceptionFilter(e))
+ {
+ HandleException(errorSource, e);
+ }
+ finally
+ {
+ AfterCallingExtensionPoint(errorSource ?? call);
+ }
+ }
+
public T CallExtensionPoint<T>(object errorSource, Func<T> call, T valueOnThrow)
{
try
@@ -418,24 +439,27 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
try
{
- await asyncAction();
+ await asyncAction().ConfigureAwait(true);
}
+ catch (OperationCanceledException) { } // swallow OperationCanceledException in async method calls
catch (Exception e)
{
HandleException(errorSource, e);
}
}
- public async Task CallExtensionPointAsync(Func<Task> asyncAction)
- {
- await CallExtensionPointAsync(errorSource: null, asyncAction: asyncAction);
- }
+ public Task CallExtensionPointAsync(Func<Task> asyncAction) => CallExtensionPointAsync(errorSource: null, asyncAction: asyncAction);
public async Task<T> CallExtensionPointAsync<T>(object errorSource, Func<Task<T>> asyncCall, T valueOnThrow)
{
try
{
- return await asyncCall();
+ return await asyncCall().ConfigureAwait(true);
+ }
+ catch (OperationCanceledException)
+ {
+ // swallow OperationCanceledException in async method calls
+ return valueOnThrow;
}
catch (Exception e)
{
@@ -444,10 +468,8 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
}
- public async Task<T> CallExtensionPointAsync<T>(Func<Task<T>> asyncCall, T valueOnThrow)
- {
- return await CallExtensionPointAsync<T>(errorSource: null, asyncCall: asyncCall, valueOnThrow: valueOnThrow);
- }
+ public Task<T> CallExtensionPointAsync<T>(Func<Task<T>> asyncCall, T valueOnThrow)
+ => CallExtensionPointAsync<T>(errorSource: null, asyncCall: asyncCall, valueOnThrow: valueOnThrow);
public void RaiseEvent(object sender, EventHandler eventHandlers)
{
@@ -458,8 +480,9 @@ namespace Microsoft.VisualStudio.Text.Utilities
var handlers = eventHandlers.GetInvocationList();
- foreach (EventHandler handler in handlers)
+ for (int i = 0; (i < handlers.Length); ++i)
{
+ var handler = (EventHandler)(handlers[i]);
try
{
BeforeCallingEventHandler(handler);
@@ -484,8 +507,9 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
var handlers = eventHandlers.GetInvocationList();
- foreach (EventHandler<TArgs> handler in handlers)
+ for (int i = 0; (i < handlers.Length); ++i)
{
+ var handler = (EventHandler<TArgs>)(handlers[i]);
try
{
BeforeCallingEventHandler(handler);
@@ -509,15 +533,16 @@ namespace Microsoft.VisualStudio.Text.Utilities
return;
}
- foreach (var perfTracker in PerfTrackers)
+ for (int i = 0; i < PerfTrackers.Count; i++)
{
+ var perfTracker = PerfTrackers[i];
try
{
- perfTracker.AfterCallingEventHandler(handler);
+ PerfTrackers[i].AfterCallingEventHandler(handler);
}
catch (Exception e)
{
- HandleException(perfTracker, e);
+ HandleException(PerfTrackers[i], e);
}
}
}
@@ -529,15 +554,16 @@ namespace Microsoft.VisualStudio.Text.Utilities
return;
}
- foreach (var perfTracker in PerfTrackers)
+ for (int i = 0; i < PerfTrackers.Count; i++)
{
+ var perfTracker = PerfTrackers[i];
try
{
- perfTracker.AfterCallingExtension(extensionPoint);
+ PerfTrackers[i].AfterCallingExtension(extensionPoint);
}
catch (Exception e)
{
- HandleException(perfTracker, e);
+ HandleException(PerfTrackers[i], e);
}
}
}
@@ -549,15 +575,16 @@ namespace Microsoft.VisualStudio.Text.Utilities
return;
}
- foreach (var perfTracker in PerfTrackers)
+ for (int i = 0; i < PerfTrackers.Count; i++)
{
+ var perfTracker = PerfTrackers[i];
try
{
- perfTracker.BeforeCallingEventHandler(handler);
+ PerfTrackers[i].BeforeCallingEventHandler(handler);
}
catch (Exception e)
{
- HandleException(perfTracker, e);
+ HandleException(PerfTrackers[i], e);
}
}
}
@@ -569,15 +596,16 @@ namespace Microsoft.VisualStudio.Text.Utilities
return;
}
- foreach (var perfTracker in PerfTrackers)
+ for (int i = 0; i < PerfTrackers.Count; i++)
{
+ var perfTracker = PerfTrackers[i];
try
{
- perfTracker.BeforeCallingExtension(extensionPoint);
+ PerfTrackers[i].BeforeCallingExtension(extensionPoint);
}
catch (Exception e)
{
- HandleException(perfTracker, e);
+ HandleException(PerfTrackers[i], e);
}
}
}
@@ -585,23 +613,27 @@ namespace Microsoft.VisualStudio.Text.Utilities
public void HandleException(object errorSource, Exception e)
{
bool handled = false;
- foreach (var errorHandler in ErrorHandlers)
+ for (int i = 0; (i < ErrorHandlers.Count); ++i)
{
+ var errorHandler = ErrorHandlers[i];
try
{
+ GuardedOperations.LastHandledException = e;
+ GuardedOperations.LastHandleExceptionStackTrace = e.StackTrace;
+
errorHandler.HandleError(errorSource, e);
handled = true;
}
catch (Exception doubleFaultException)
{
// TODO: What is the right behavior here?
- Debug.Fail(doubleFaultException.ToString());
+ GuardedOperations.Fail(doubleFaultException.ToString());
}
}
if (!handled)
{
// TODO: What is the right behavior here?
- Debug.Fail(e.ToString());
+ GuardedOperations.Fail(e.ToString());
if (GuardedOperations.ReThrowIfNoHandlers)
throw new Exception("Unhandled exception.", e);
@@ -695,12 +727,13 @@ namespace Microsoft.VisualStudio.Text.Utilities
var handlers = eventHandlers.GetInvocationList();
- foreach (AsyncEventHandler<TArgs> handler in handlers)
+ for (int i = 0; (i < handlers.Length); ++i)
{
+ var handler = (AsyncEventHandler<TArgs>)(handlers[i]);
try
{
BeforeCallingEventHandler(handler);
- await handler(sender, args);
+ await handler(sender, args).ConfigureAwait(true);
}
catch (Exception e)
{
@@ -713,5 +746,18 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
}).Task;
}
+
+ static bool _ignoreFailures = false;
+
+ [Conditional("DEBUG")]
+ private static void Fail(string message)
+ {
+ if (!_ignoreFailures)
+ {
+ if (Debugger.IsAttached)
+ Debugger.Break();
+ Debug.Fail(message);
+ }
+ }
}
}
diff --git a/src/Text/Util/TextDataUtil/ListUtilities.cs b/src/Text/Util/TextDataUtil/ListUtilities.cs
index a61d5cb..18013d7 100644
--- a/src/Text/Util/TextDataUtil/ListUtilities.cs
+++ b/src/Text/Util/TextDataUtil/ListUtilities.cs
@@ -14,11 +14,10 @@ namespace Microsoft.VisualStudio.Text.Utilities
internal static class ListUtilities
{
/// <summary>
- /// Do a binary search in <paramref name="list"/> for an element that matches <paramref name="target"/>
+ /// Do a binary search in <paramref name="list"/> for an element that matches an implicit target (built into the comparison function).
/// </summary>
/// <param name="list">List to search.</param>
- /// <param name="target">Object of the search.</param>
- /// <param name="compare">Comparison function between an element and target (returns &lt; 0 if e comes before t, 0 if e matches, &gt; 0 if e comes after).
+ /// <param name="compare">Comparison function between an element (returns &lt; 0 if e comes before t, 0 if e matches, &gt; 0 if e comes after).
/// <param name="index">Index of the matching element (or, if there is no exact match, index of the element that follows it).</param>
/// <returns>true if an exact match was found.</returns>
/// <remarks>Yes, I know there is List.BinarySearch but that doesn't do exactly what I need most of the time.</remarks>
@@ -61,4 +60,4 @@ namespace Microsoft.VisualStudio.Text.Utilities
return source.Cast<T?>().FirstOrDefault();
}
}
- }
+}
diff --git a/src/Text/Util/TextDataUtil/MappingPointSnapshot.cs b/src/Text/Util/TextDataUtil/MappingPointSnapshot.cs
index 3e6c110..121979a 100644
--- a/src/Text/Util/TextDataUtil/MappingPointSnapshot.cs
+++ b/src/Text/Util/TextDataUtil/MappingPointSnapshot.cs
@@ -40,7 +40,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public SnapshotPoint? GetPoint(ITextBuffer targetBuffer, PositionAffinity affinity)
{
if (targetBuffer == null)
- throw new ArgumentNullException("targetBuffer");
+ throw new ArgumentNullException(nameof(targetBuffer));
if (_unmappable)
return null;
@@ -59,7 +59,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public SnapshotPoint? GetPoint(ITextSnapshot targetSnapshot, PositionAffinity affinity)
{
if (targetSnapshot == null)
- throw new ArgumentNullException("targetSnapshot");
+ throw new ArgumentNullException(nameof(targetSnapshot));
if (_unmappable)
return null;
@@ -75,7 +75,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public SnapshotPoint? GetPoint(Predicate<ITextBuffer> match, PositionAffinity affinity)
{
if (match == null)
- throw new ArgumentNullException("match");
+ throw new ArgumentNullException(nameof(match));
if (_unmappable)
return null;
@@ -94,7 +94,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public SnapshotPoint? GetInsertionPoint(Predicate<ITextBuffer> match)
{
if (match == null)
- throw new ArgumentNullException("match");
+ throw new ArgumentNullException(nameof(match));
if (_unmappable)
return null;
diff --git a/src/Text/Util/TextDataUtil/MappingSpanSnapshot.cs b/src/Text/Util/TextDataUtil/MappingSpanSnapshot.cs
index 88714fd..6f758ad 100644
--- a/src/Text/Util/TextDataUtil/MappingSpanSnapshot.cs
+++ b/src/Text/Util/TextDataUtil/MappingSpanSnapshot.cs
@@ -6,6 +6,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
using System;
using System.Collections.Generic;
+ using System.Globalization;
using Microsoft.VisualStudio.Text.Projection;
internal class MappingSpanSnapshot : IMappingSpan
@@ -41,7 +42,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public NormalizedSnapshotSpanCollection GetSpans(ITextBuffer targetBuffer)
{
if (targetBuffer == null)
- throw new ArgumentNullException("targetBuffer");
+ throw new ArgumentNullException(nameof(targetBuffer));
if (_unmappable)
return NormalizedSnapshotSpanCollection.Empty;
@@ -76,7 +77,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public NormalizedSnapshotSpanCollection GetSpans(ITextSnapshot targetSnapshot)
{
if (targetSnapshot == null)
- throw new ArgumentNullException("targetSnapshot");
+ throw new ArgumentNullException(nameof(targetSnapshot));
if (_unmappable)
return NormalizedSnapshotSpanCollection.Empty;
@@ -203,7 +204,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
public override string ToString()
{
- return String.Format("MappingSpanSnapshot anchored at {0}", _anchor);
+ return string.Format(CultureInfo.CurrentCulture, "MappingSpanSnapshot anchored at {0}", _anchor);
}
}
}
diff --git a/src/Text/Util/TextDataUtil/PooledObjects/ObjectPool`1.cs b/src/Text/Util/TextDataUtil/PooledObjects/ObjectPool`1.cs
index 2aa6e92..8af4993 100644
--- a/src/Text/Util/TextDataUtil/PooledObjects/ObjectPool`1.cs
+++ b/src/Text/Util/TextDataUtil/PooledObjects/ObjectPool`1.cs
@@ -224,7 +224,9 @@ namespace Microsoft.VisualStudio.Text.Utilities
/// return a larger array to the pool than was originally allocated.
/// </summary>
[Conditional("DEBUG")]
+#pragma warning disable CA1822 // Mark members as static
internal void ForgetTrackedObject(T old, T replacement = null)
+#pragma warning restore CA1822 // Mark members as static
{
#if DETECT_LEAKS
LeakTracker tracker;
diff --git a/src/Text/Util/TextDataUtil/ProjectionSpanDiffer.cs b/src/Text/Util/TextDataUtil/ProjectionSpanDiffer.cs
index 951cf14..1a43cb9 100644
--- a/src/Text/Util/TextDataUtil/ProjectionSpanDiffer.cs
+++ b/src/Text/Util/TextDataUtil/ProjectionSpanDiffer.cs
@@ -5,6 +5,7 @@
namespace Microsoft.VisualStudio.Text.Utilities
{
using System;
+ using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Microsoft.VisualStudio.Text.Differencing;
@@ -12,42 +13,15 @@ namespace Microsoft.VisualStudio.Text.Utilities
internal class ProjectionSpanDiffer
{
- private IDifferenceService diffService;
+ private readonly IDifferenceService diffService;
- private ReadOnlyCollection<SnapshotSpan> inputDeletedSnapSpans;
- private ReadOnlyCollection<SnapshotSpan> inputInsertedSnapSpans;
- private bool computed;
+ private readonly ReadOnlyCollection<SnapshotSpan> inputDeletedSnapSpans;
+ private readonly ReadOnlyCollection<SnapshotSpan> inputInsertedSnapSpans;
// exposed to unit tests
internal List<SnapshotSpan>[] deletedSurrogates;
internal List<SnapshotSpan>[] insertedSurrogates;
- public static ProjectionSpanDifference DiffSourceSpans(IDifferenceService diffService,
- IProjectionSnapshot left,
- IProjectionSnapshot right)
- {
- if (left == null)
- {
- throw new ArgumentNullException("left");
- }
- if (right == null)
- {
- throw new ArgumentNullException("right");
- }
-
- if (!object.ReferenceEquals(left.TextBuffer, right.TextBuffer))
- {
- throw new ArgumentException("left does not belong to the same text buffer as right");
- }
-
- ProjectionSpanDiffer differ = new ProjectionSpanDiffer
- (diffService,
- left.GetSourceSpans(),
- right.GetSourceSpans());
-
- return new ProjectionSpanDifference(differ.GetDifferences(), differ.InsertedSpans, differ.DeletedSpans);
- }
-
public ProjectionSpanDiffer(IDifferenceService diffService,
ReadOnlyCollection<SnapshotSpan> deletedSnapSpans,
ReadOnlyCollection<SnapshotSpan> insertedSnapSpans)
@@ -57,34 +31,31 @@ namespace Microsoft.VisualStudio.Text.Utilities
this.inputInsertedSnapSpans = insertedSnapSpans;
}
- public ReadOnlyCollection<SnapshotSpan> DeletedSpans { get; private set; }
- public ReadOnlyCollection<SnapshotSpan> InsertedSpans { get; private set; }
+ private IDifferenceCollection<SnapshotSpan> differences;
public IDifferenceCollection<SnapshotSpan> GetDifferences()
{
- if (!this.computed)
+ if (this.differences == null)
{
DecomposeSpans();
- this.computed = true;
- }
- var deletedSpans = new List<SnapshotSpan>();
- var insertedSpans = new List<SnapshotSpan>();
+ var deletedSpans = new List<SnapshotSpan>();
+ var insertedSpans = new List<SnapshotSpan>();
- DeletedSpans = deletedSpans.AsReadOnly();
- InsertedSpans = insertedSpans.AsReadOnly();
+ for (int s = 0; s < deletedSurrogates.Length; ++s)
+ {
+ deletedSpans.AddRange(deletedSurrogates[s]);
+ }
- for (int s = 0; s < deletedSurrogates.Length; ++s)
- {
- deletedSpans.AddRange(deletedSurrogates[s]);
- }
+ for (int s = 0; s < insertedSurrogates.Length; ++s)
+ {
+ insertedSpans.AddRange(insertedSurrogates[s]);
+ }
- for (int s = 0; s < insertedSurrogates.Length; ++s)
- {
- insertedSpans.AddRange(insertedSurrogates[s]);
+ differences = this.diffService.DifferenceSequences(deletedSpans, insertedSpans);
}
- return this.diffService.DifferenceSequences(deletedSpans, insertedSpans);
+ return differences;
}
#region Internal (for testing) helpers
@@ -277,11 +248,10 @@ namespace Microsoft.VisualStudio.Text.Utilities
}
}
- private int Comparison(Thing left, Thing right)
+ private static int Comparison(Thing left, Thing right)
{
return left.span.Start - right.span.Start;
}
#endregion
-
}
}
diff --git a/src/Text/Util/TextDataUtil/ProjectionSpanDifference.cs b/src/Text/Util/TextDataUtil/ProjectionSpanDifference.cs
deleted file mode 100644
index 99d3267..0000000
--- a/src/Text/Util/TextDataUtil/ProjectionSpanDifference.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-//
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-//
-using System.Collections.ObjectModel;
-using Microsoft.VisualStudio.Text.Differencing;
-
-namespace Microsoft.VisualStudio.Text.Utilities
-{
- /// <summary>
- /// Represents the set of differences between two projection snapshots.
- /// </summary>
- public class ProjectionSpanDifference
- {
- /// <summary>
- /// Initializes a new instance of <see cref="ProjectionSpanDifference"/>.
- /// </summary>
- /// <param name="differenceCollection">The collection of snapshot spans that include the differences.</param>
- /// <param name="insertedSpans">A read-only collection of the inserted snapshot spans.</param>
- /// <param name="deletedSpans">A read-only collection of the deleted snapshot spans.</param>
- public ProjectionSpanDifference(IDifferenceCollection<SnapshotSpan> differenceCollection, ReadOnlyCollection<SnapshotSpan> insertedSpans, ReadOnlyCollection<SnapshotSpan> deletedSpans)
- {
- DifferenceCollection = differenceCollection;
- InsertedSpans = insertedSpans;
- DeletedSpans = deletedSpans;
- }
-
- /// <summary>
- /// The collection of differences between the two snapshots.
- /// </summary>
- public IDifferenceCollection<SnapshotSpan> DifferenceCollection { get; private set; }
-
- /// <summary>
- /// The read-only collection of inserted snapshot spans.
- /// </summary>
- public ReadOnlyCollection<SnapshotSpan> InsertedSpans { get; private set; }
-
- /// <summary>
- /// The read-only collection of deleted snapshot spans.
- /// </summary>
- public ReadOnlyCollection<SnapshotSpan> DeletedSpans { get; private set; }
- }
-}
diff --git a/src/Text/Util/TextDataUtil/TextModelOptions.cs b/src/Text/Util/TextDataUtil/TextModelOptions.cs
index 1863bfd..a07d797 100644
--- a/src/Text/Util/TextDataUtil/TextModelOptions.cs
+++ b/src/Text/Util/TextDataUtil/TextModelOptions.cs
@@ -8,6 +8,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
// these values should be set by MEF component in editor options component. defaults
// are here just in case that doesn't happen in some configuration.
+#pragma warning disable CA2211 // Non-constant fields should not be visible
public static int CompressedStorageFileSizeThreshold = 5 * 1024 * 1024; // 5 MB file (typically 10 MB in memory)
public static int CompressedStoragePageSize = 1 * 1024 * 1024; // 1 MB per page (so 10 pages at the low end)
public static int CompressedStorageMaxLoadedPages = 3; // at most 3 pages loaded
@@ -18,5 +19,6 @@ namespace Microsoft.VisualStudio.Text.Utilities
public static int StringRebuilderMaxLinesToConsolidate = 8; // Combine adjacent pieces when number of lines is less than this
public static int DiffSizeThreshold = 25 * 1024 * 1024; // threshold above which to do poor man's diff
+#pragma warning restore CA2211 // Non-constant fields should not be visible
}
}
diff --git a/src/Text/Util/TextDataUtil/TextUtilities.cs b/src/Text/Util/TextDataUtil/TextUtilities.cs
index 26aa19a..e64703e 100644
--- a/src/Text/Util/TextDataUtil/TextUtilities.cs
+++ b/src/Text/Util/TextDataUtil/TextUtilities.cs
@@ -76,7 +76,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (text == null)
{
- throw new ArgumentNullException("text");
+ throw new ArgumentNullException(nameof(text));
}
int lines = 0;
for (int i = 0; i < text.Length; )
@@ -106,7 +106,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (text == null)
{
- throw new ArgumentNullException("text");
+ throw new ArgumentNullException(nameof(text));
}
int lines = 0;
for (int i = 0; i < length; )
@@ -257,11 +257,11 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (buffer == null)
{
- throw new ArgumentNullException("buffer");
+ throw new ArgumentNullException(nameof(buffer));
}
if (tag == null)
{
- throw new ArgumentNullException("tag");
+ throw new ArgumentNullException(nameof(tag));
}
string existingTag = "";
if (!buffer.Properties.TryGetProperty<string>("tag", out existingTag))
@@ -284,7 +284,7 @@ namespace Microsoft.VisualStudio.Text.Utilities
{
if (buffer == null)
{
- throw new ArgumentNullException("buffer");
+ throw new ArgumentNullException(nameof(buffer));
}
string tag = "";
buffer.Properties.TryGetProperty<string>("tag", out tag);
diff --git a/src/Text/Util/TextUIUtil/VacuousTextViewModel.cs b/src/Text/Util/TextUIUtil/VacuousTextViewModel.cs
index 341ed2a..baf84fa 100644
--- a/src/Text/Util/TextUIUtil/VacuousTextViewModel.cs
+++ b/src/Text/Util/TextUIUtil/VacuousTextViewModel.cs
@@ -48,7 +48,9 @@ namespace Microsoft.VisualStudio.Text.Utilities
get { return this.EditBuffer; }
}
- public void Dispose()
+#pragma warning disable CA1063 // Implement IDisposable Correctly
+ public void Dispose()
+#pragma warning restore CA1063 // Implement IDisposable Correctly
{
GC.SuppressFinalize(this);
}