public void SetCompletionItems( IList <CompletionItem> completionItems, CompletionItem selectedItem, CompletionItem suggestionModeItem, bool suggestionMode, bool isSoftSelected, ImmutableArray <CompletionItemFilter> completionItemFilters, string filterText) { _foregroundThread.AssertIsForeground(); foreach (var item in completionItems) { if (!_displayTextToBoldingTextMap.ContainsKey(item.DisplayText)) { _displayTextToBoldingTextMap.Add(item.DisplayText, CompletionHelper.GetDisplayTextForMatching(item)); } } // Initialize the completion map to a reasonable default initial size (+1 for the builder) CompletionItemMap = CompletionItemMap ?? new Dictionary <CompletionItem, VSCompletion>(completionItems.Count + 1); FilterText = filterText; SuggestionModeItem = suggestionModeItem; // If more than one filter was provided, then present it to the user. if (_showFilters && _filters == null && completionItemFilters.Length > 1) { _filters = completionItemFilters.Select(f => new IntellisenseFilter2(this, f)) .ToArray(); } CreateCompletionListBuilder(selectedItem, suggestionModeItem, suggestionMode); CreateNormalCompletionListItems(completionItems); var selectedCompletionItem = selectedItem != null?GetVSCompletion(selectedItem) : null; SelectionStatus = new CompletionSelectionStatus( selectedCompletionItem, isSelected: !isSoftSelected, isUnique: selectedCompletionItem != null); }
private void Commit( CompletionItem item, Model model, char?commitChar, ITextSnapshot initialTextSnapshot, Action nextHandler) { AssertIsForeground(); // We could only be called if we had a model at this point. Contract.ThrowIfNull(model); // Now that we've captured the model at this point, we can stop ourselves entirely. // This is also desirable as we may have reentrancy problems when we call into // custom commit completion providers. I.e. if the custom provider moves the caret, // then we do not want to process that move as it may put us into an unexpected state. // // TODO(cyrusn): We still have a general reentrancy problem where calling into a custom // commit provider (or just calling into the editor) may cause something to call back // into us. However, for now, we just hope that no such craziness will occur. this.DismissSessionIfActive(); CompletionChange completionChange; using (var transaction = CaretPreservingEditTransaction.TryCreate( EditorFeaturesResources.IntelliSense, TextView, _undoHistoryRegistry, _editorOperationsFactoryService)) { if (transaction == null) { // This text buffer has no undo history and has probably been unmapped. // (Workflow unmaps its projections when losing focus (such as double clicking the completion list)). // Bail on committing completion because we won't be able to find a Document to update either. return; } // We want to merge with any of our other programmatic edits (e.g. automatic brace completion) transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; if (GetCompletionProvider(item) is ICustomCommitCompletionProvider provider) { provider.Commit(item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar); } else { // Right before calling Commit, we may have passed the commitChar through to the // editor. That was so that undoing completion will get us back to the state we // we would be in if completion had done nothing. However, now that we're going // to actually commit, we want to roll back to where we were before we pushed // commit character into the buffer. This has multiple benefits: // // 1) the buffer is in a state we expect it to be in. i.e. we don't have to // worry about what might have happened (like brace-completion) when the // commit char was inserted. // 2) after we commit the item, we'll pass the commit character again into the // buffer (unless the items asks us not to). By doing this, we can make sure // that things like brace-completion or formatting trigger as we expect them // to. var characterWasSentIntoBuffer = commitChar != null && initialTextSnapshot.Version.VersionNumber != this.SubjectBuffer.CurrentSnapshot.Version.VersionNumber; if (characterWasSentIntoBuffer) { RollbackToBeforeTypeChar(initialTextSnapshot); } // Now, get the change the item wants to make. Note that the change will be relative // to the initial snapshot/document the item was triggered from. We'll map that change // forward, then apply it to our current snapshot. var triggerDocument = model.TriggerDocument; var triggerSnapshot = model.TriggerSnapshot; var completionService = CompletionService.GetService(triggerDocument); Contract.ThrowIfNull(completionService, nameof(completionService)); completionChange = completionService.GetChangeAsync( triggerDocument, item, commitChar, CancellationToken.None).WaitAndGetResult(CancellationToken.None); var textChange = completionChange.TextChange; var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan()); var mappedSpan = triggerSnapshotSpan.TranslateTo( this.SubjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); var adjustedNewText = AdjustForVirtualSpace(textChange); var editOptions = GetEditOptions(mappedSpan, adjustedNewText); // The immediate window is always marked read-only and the language service is // responsible for asking the buffer to make itself writable. We'll have to do that for // commit, so we need to drag the IVsTextLines around, too. // We have to ask the buffer to make itself writable, if it isn't already uint immediateWindowBufferUpdateCookie = 0; if (_isImmediateWindow) { immediateWindowBufferUpdateCookie = ((IDebuggerTextView)TextView).StartBufferUpdate(); } // Now actually make the text change to the document. using (var textEdit = this.SubjectBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) { textEdit.Replace(mappedSpan.Span, adjustedNewText); textEdit.ApplyAndLogExceptions(); } if (_isImmediateWindow) { ((IDebuggerTextView)TextView).EndBufferUpdate(immediateWindowBufferUpdateCookie); } // If the completion change requested a new position for the caret to go, // then set the caret to go directly to that point. if (completionChange.NewPosition.HasValue) { SetCaretPosition(desiredCaretPosition: completionChange.NewPosition.Value); } else if (editOptions.ComputeMinimalChange) { // Or, If we're doing a minimal change, then the edit that we make to the // buffer may not make the total text change that places the caret where we // would expect it to go based on the requested change. In this case, // determine where the item should go and set the care manually. // Note: we only want to move the caret if the caret would have been moved // by the edit. i.e. if the caret was actually in the mapped span that // we're replacing. if (TextView.GetCaretPoint(this.SubjectBuffer) is SnapshotPoint caretPositionInBuffer && mappedSpan.IntersectsWith(caretPositionInBuffer)) { SetCaretPosition(desiredCaretPosition: mappedSpan.Start.Position + adjustedNewText.Length); } } // Now, pass along the commit character unless the completion item said not to if (characterWasSentIntoBuffer && !completionChange.IncludesCommitCharacter) { nextHandler(); } if (item.Rules.FormatOnCommit) { var spanToFormat = triggerSnapshotSpan.TranslateTo( this.SubjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); var document = this.GetDocument(); var formattingService = document?.GetLanguageService <IEditorFormattingService>(); if (formattingService != null) { var changes = formattingService.GetFormattingChangesAsync( document, spanToFormat.Span.ToTextSpan(), CancellationToken.None).WaitAndGetResult(CancellationToken.None); document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, CancellationToken.None); } } // If the insertion is long enough, the caret will scroll out of the visible area. // Re-center the view. this.TextView.Caret.EnsureVisible(); } transaction.Complete(); } // Let the completion rules know that this item was committed. this.MakeMostRecentItem(CompletionHelper.GetDisplayTextForMatching(item)); }
private static int GetRecentItemIndex(ImmutableArray <string> recentItems, CompletionItem item) { var index = recentItems.IndexOf(CompletionHelper.GetDisplayTextForMatching(item)); return(-index); }