private bool IsCaretOutsideItemBounds( Model model, SnapshotPoint caretPoint, CompletionItem item, Dictionary<TextSpan, string> textSpanToText, Dictionary<TextSpan, ViewTextSpan> textSpanToViewSpan) { // Easy first check. See if the caret point is before the start of the item. ViewTextSpan filterSpanInViewBuffer; if (!textSpanToViewSpan.TryGetValue(item.FilterSpan, out filterSpanInViewBuffer)) { filterSpanInViewBuffer = model.GetSubjectBufferFilterSpanInViewBuffer(item.FilterSpan); textSpanToViewSpan[item.FilterSpan] = filterSpanInViewBuffer; } if (caretPoint < filterSpanInViewBuffer.TextSpan.Start) { return true; } var textSnapshot = caretPoint.Snapshot; var currentText = model.GetCurrentTextInSnapshot(item.FilterSpan, textSnapshot, textSpanToText); var currentTextSpan = new TextSpan(filterSpanInViewBuffer.TextSpan.Start, currentText.Length); return !currentTextSpan.IntersectsWith(caretPoint); }
private bool IsFilterTextEmpty( Model model, SnapshotPoint caretPoint, CompletionItem item, Dictionary <TextSpan, string> textSpanToText, Dictionary <TextSpan, ViewTextSpan> textSpanToViewSpan) { // Easy first check. See if the caret point is before the start of the item. ViewTextSpan filterSpanInViewBuffer; if (!textSpanToViewSpan.TryGetValue(item.FilterSpan, out filterSpanInViewBuffer)) { filterSpanInViewBuffer = model.GetSubjectBufferFilterSpanInViewBuffer(item.FilterSpan); textSpanToViewSpan[item.FilterSpan] = filterSpanInViewBuffer; } if (caretPoint < filterSpanInViewBuffer.TextSpan.Start) { return(true); } var textSnapshot = caretPoint.Snapshot; var currentText = model.GetCurrentTextInSnapshot(item.FilterSpan, textSnapshot, textSpanToText); var currentTextSpan = new TextSpan(filterSpanInViewBuffer.TextSpan.Start, currentText.Length); return(currentText.Length == 0); }
private string GetCurrentFilterText(Model model, CompletionItem selectedItem) { var textSnapshot = this.TextView.TextSnapshot; var viewSpan = model.GetSubjectBufferFilterSpanInViewBuffer(selectedItem.FilterSpan); var filterText = model.GetCurrentTextInSnapshot( viewSpan, textSnapshot, GetCaretPointInViewBuffer()); return(filterText); }
private bool IsHardSelection( Model model, CompletionItem bestFilterMatch, ITextSnapshot textSnapshot, IList <ICompletionRules> completionRulesList, CompletionTriggerInfo triggerInfo, CompletionFilterReason reason) { if (model.Builder != null) { return(bestFilterMatch != null && bestFilterMatch.DisplayText == model.Builder.DisplayText); } if (bestFilterMatch == null || model.UseSuggestionCompletionMode) { return(false); } // We don't have a builder and we have a best match. Normally this will be hard // selected, except for a few cases. Specifically, if no filter text has been // provided, and this is not a preselect match then we will soft select it. This // happens when the completion list comes up implicitly and there is something in // the MRU list. In this case we do want to select it, but not with a hard // selection. Otherwise you can end up with the following problem: // // dim i as integer =<space> // // Completion will comes up after = with 'integer' selected (Because of MRU). We do // not want 'space' to commit this. var viewSpan = model.GetSubjectBufferFilterSpanInViewBuffer(bestFilterMatch.FilterSpan); var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null); foreach (var completionRules in completionRulesList) { var shouldSoftSelect = completionRules.ShouldSoftSelectItem(GetExternallyUsableCompletionItem(bestFilterMatch), fullFilterText, triggerInfo); if (shouldSoftSelect == true) { return(false); } } // If the user moved the caret left after they started typing, the 'best' match may not match at all // against the full text span that this item would be replacing. if (!MatchesFilterText(bestFilterMatch, fullFilterText, completionRulesList, triggerInfo, reason)) { return(false); } // There was either filter text, or this was a preselect match. In either case, we // can hard select this. return(true); }
private void Commit(CompletionItem item, TextChange textChange, Model model, char?commitChar) { AssertIsForeground(); // We could only be called if we had a model at this point. Contract.ThrowIfNull(model); item = Controller.GetExternallyUsableCompletionItem(item); // 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.StopModelComputation(); // NOTE(cyrusn): It is intentional that we get the undo history for the // surface buffer and not the subject buffer. using (var transaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.Intellisense)) { // We want to merge with any of our other programmatic edits (e.g. automatic brace completion) transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; // Check if the provider wants to perform custom commit itself. Otherwise we will // handle things. var provider = item.CompletionProvider as ICustomCommitCompletionProvider; if (provider == null) { var viewBuffer = this.TextView.TextBuffer; // Use character based diffing here to avoid overwriting the commit character placed into the editor. var editOptions = new EditOptions(new StringDifferenceOptions { DifferenceType = StringDifferenceTypes.Character, IgnoreTrimWhiteSpace = EditOptions.DefaultMinimalChange.DifferenceOptions.IgnoreTrimWhiteSpace }); using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) { var viewSpan = model.GetSubjectBufferFilterSpanInViewBuffer(textChange.Span); var currentSpan = model.GetCurrentSpanInSnapshot(viewSpan, viewBuffer.CurrentSnapshot); // In order to play nicely with automatic brace completion, we need to // not touch the opening paren. We'll check our span and textchange // for ( and adjust them accordingly if we find them. // all this is needed since we don't use completion set mechanism provided by VS but we implement everything ourselves. // due to that, existing brace completion engine in editor that should take care of interaction between brace completion // and intellisense doesn't work for us. so we need this kind of workaround to support it nicely. bool textChanged; var finalText = GetFinalText(textChange, commitChar.GetValueOrDefault(), out textChanged); currentSpan = GetFinalSpan(currentSpan, commitChar.GetValueOrDefault(), textChanged); var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); var virtualCaretPoint = this.TextView.GetVirtualCaretPoint(this.SubjectBuffer); if (caretPoint.HasValue && virtualCaretPoint.HasValue) { // TODO(dustinca): We need to call a different API here. TryMoveCaretToAndEnsureVisible might center within the view. this.TextView.TryMoveCaretToAndEnsureVisible(new VirtualSnapshotPoint(caretPoint.Value)); } caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); // Now that we're doing character level diffing, we need to move the caret to the end of // the span being replaced. Otherwise, we can replace M|ai with Main and wind up with // M|ain, since character based diffing makes that quite legit. if (caretPoint.HasValue) { var endInSubjectBuffer = this.TextView.BufferGraph.MapDownToBuffer(currentSpan.End, PointTrackingMode.Positive, caretPoint.Value.Snapshot.TextBuffer, PositionAffinity.Predecessor); if (caretPoint.Value < endInSubjectBuffer) { this.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(currentSpan.Snapshot.TextBuffer.CurrentSnapshot, currentSpan.End.Position)); } } textEdit.Replace(currentSpan, finalText); textEdit.Apply(); } // We've manipulated the caret position in order to generate the correct edit. However, // if the insertion is long enough, the caret will scroll out of the visible area. // Re-center the view. using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) { var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); if (caretPoint.HasValue) { this.TextView.Caret.EnsureVisible(); } } transaction.Complete(); } else { // Let the provider handle this. provider.Commit(item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar); transaction.Complete(); } } var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); var formattingService = document.GetLanguageService <IEditorFormattingService>(); if (formattingService != null && (item.ShouldFormatOnCommit || (commitChar != null && formattingService.SupportsFormattingOnTypedCharacter(document, commitChar.GetValueOrDefault())))) { // Formatting the completion item affected span is done as a separate transaction because this gives the user // the flexibility to undo the formatting but retain the changes associated with the completion item using (var formattingTransaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntellisenseCommitFormatting)) { var changes = formattingService.GetFormattingChangesAsync(document, textChange.Span, CancellationToken.None).WaitAndGetResult(CancellationToken.None); document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, CancellationToken.None); formattingTransaction.Complete(); } } // Let the rules factories know that this item was committed. foreach (var rules in GetCompletionRules()) { rules.CompletionItemComitted(item); } }
private void Commit(CompletionItem item, TextChange textChange, Model model, char? commitChar) { AssertIsForeground(); // We could only be called if we had a model at this point. Contract.ThrowIfNull(model); item = Controller.GetExternallyUsableCompletionItem(item); // 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.StopModelComputation(); // NOTE(cyrusn): It is intentional that we get the undo history for the // surface buffer and not the subject buffer. using (var transaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntelliSense)) { // We want to merge with any of our other programmatic edits (e.g. automatic brace completion) transaction.MergePolicy = AutomaticCodeChangeMergePolicy.Instance; // Check if the provider wants to perform custom commit itself. Otherwise we will // handle things. var provider = item.CompletionProvider as ICustomCommitCompletionProvider; if (provider == null) { var viewBuffer = this.TextView.TextBuffer; // Use character based diffing here to avoid overwriting the commit character placed into the editor. var editOptions = new EditOptions(new StringDifferenceOptions { DifferenceType = StringDifferenceTypes.Character, IgnoreTrimWhiteSpace = EditOptions.DefaultMinimalChange.DifferenceOptions.IgnoreTrimWhiteSpace }); using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) { var viewSpan = model.GetSubjectBufferFilterSpanInViewBuffer(textChange.Span); var currentSpan = model.GetCurrentSpanInSnapshot(viewSpan, viewBuffer.CurrentSnapshot); // In order to play nicely with automatic brace completion, we need to // not touch the opening paren. We'll check our span and textchange // for ( and adjust them accordingly if we find them. // all this is needed since we don't use completion set mechanism provided by VS but we implement everything ourselves. // due to that, existing brace completion engine in editor that should take care of interaction between brace completion // and intellisense doesn't work for us. so we need this kind of workaround to support it nicely. bool textChanged; var finalText = GetFinalText(textChange, commitChar.GetValueOrDefault(), out textChanged); currentSpan = GetFinalSpan(currentSpan, commitChar.GetValueOrDefault(), textChanged); var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); var virtualCaretPoint = this.TextView.GetVirtualCaretPoint(this.SubjectBuffer); if (caretPoint.HasValue && virtualCaretPoint.HasValue) { // TODO(dustinca): We need to call a different API here. TryMoveCaretToAndEnsureVisible might center within the view. this.TextView.TryMoveCaretToAndEnsureVisible(new VirtualSnapshotPoint(caretPoint.Value)); } caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); // Now that we're doing character level diffing, we need to move the caret to the end of // the span being replaced. Otherwise, we can replace M|ai with Main and wind up with // M|ain, since character based diffing makes that quite legit. if (caretPoint.HasValue) { var endInSubjectBuffer = this.TextView.BufferGraph.MapDownToBuffer(currentSpan.End, PointTrackingMode.Positive, caretPoint.Value.Snapshot.TextBuffer, PositionAffinity.Predecessor); if (caretPoint.Value < endInSubjectBuffer) { this.TextView.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(currentSpan.Snapshot.TextBuffer.CurrentSnapshot, currentSpan.End.Position)); } } textEdit.Replace(currentSpan, finalText); textEdit.Apply(); } // We've manipulated the caret position in order to generate the correct edit. However, // if the insertion is long enough, the caret will scroll out of the visible area. // Re-center the view. using (var textEdit = viewBuffer.CreateEdit(editOptions, reiteratedVersionNumber: null, editTag: null)) { var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); if (caretPoint.HasValue) { this.TextView.Caret.EnsureVisible(); } } transaction.Complete(); } else { // Let the provider handle this. provider.Commit(item, this.TextView, this.SubjectBuffer, model.TriggerSnapshot, commitChar); transaction.Complete(); } } var document = this.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); var formattingService = document.GetLanguageService<IEditorFormattingService>(); var commitCharTriggersFormatting = commitChar != null && (formattingService?.SupportsFormattingOnTypedCharacter(document, commitChar.GetValueOrDefault()) ?? false); if (formattingService != null && (item.ShouldFormatOnCommit || commitCharTriggersFormatting)) { // Formatting the completion item affected span is done as a separate transaction because this gives the user // the flexibility to undo the formatting but retain the changes associated with the completion item using (var formattingTransaction = _undoHistoryRegistry.GetHistory(this.TextView.TextBuffer).CreateTransaction(EditorFeaturesResources.IntelliSenseCommitFormatting)) { var caretPoint = this.TextView.GetCaretPoint(this.SubjectBuffer); IList<TextChange> changes; if (commitCharTriggersFormatting && caretPoint.HasValue) { // if the commit character is supported by formatting service, then let the formatting service // find the appropriate range to format. changes = formattingService.GetFormattingChangesAsync(document, commitChar.Value, caretPoint.Value.Position, CancellationToken.None).WaitAndGetResult(CancellationToken.None); } else { // if this is not a supported trigger character for formatting service (space or tab etc.) // then format the span of the textchange. changes = formattingService.GetFormattingChangesAsync(document, textChange.Span, CancellationToken.None).WaitAndGetResult(CancellationToken.None); } if (changes != null && !changes.IsEmpty()) { document.Project.Solution.Workspace.ApplyTextChanges(document.Id, changes, CancellationToken.None); } formattingTransaction.Complete(); } } // Let the completion rules know that this item was committed. GetCompletionRules().CompletionItemCommitted(item); }
private bool IsHardSelection( Model model, CompletionItem bestFilterMatch, ITextSnapshot textSnapshot, CompletionRules completionRules, CompletionTriggerInfo triggerInfo, CompletionFilterReason reason) { if (model.Builder != null) { return bestFilterMatch != null && bestFilterMatch.DisplayText == model.Builder.DisplayText; } if (bestFilterMatch == null || model.UseSuggestionCompletionMode) { return false; } // We don't have a builder and we have a best match. Normally this will be hard // selected, except for a few cases. Specifically, if no filter text has been // provided, and this is not a preselect match then we will soft select it. This // happens when the completion list comes up implicitly and there is something in // the MRU list. In this case we do want to select it, but not with a hard // selection. Otherwise you can end up with the following problem: // // dim i as integer =<space> // // Completion will comes up after = with 'integer' selected (Because of MRU). We do // not want 'space' to commit this. var viewSpan = model.GetSubjectBufferFilterSpanInViewBuffer(bestFilterMatch.FilterSpan); var fullFilterText = model.GetCurrentTextInSnapshot(viewSpan, textSnapshot, endPoint: null); var shouldSoftSelect = completionRules.ShouldSoftSelectItem(GetExternallyUsableCompletionItem(bestFilterMatch), fullFilterText, triggerInfo); if (shouldSoftSelect) { return false; } // If the user moved the caret left after they started typing, the 'best' match may not match at all // against the full text span that this item would be replacing. if (!completionRules.MatchesFilterText(bestFilterMatch, fullFilterText, triggerInfo, reason)) { return false; } // There was either filter text, or this was a preselect match. In either case, we // can hard select this. return true; }