private void CommitOnTypeChar(char ch) { AssertIsForeground(); // Note: this function is called after the character has already been inserted into the // buffer. var model = sessionOpt.WaitForModel(); // We only call CommitOnTypeChar if ch was a commit character. And we only know if ch // was commit character if we had a selected item. Contract.ThrowIfNull(model); // Replace the selected text span with the desired insertion text. Note: The provided // text span will end up including the last typed char (because we track it in an edge // inclusive manner). Because the span includes the last typed character, it will get // lost unless we add it back in. So we actually insert the desired item text *and* the // character. By doing this we also get proper undo behavior. i.e. if the user types: // // WriteL( // // Then we will first input "WriteL(" into the buffer. We will then replace "WriteL(" // with "WriteLine(". That way if they undo, they will end up with "WriteL" again. var selectedItem = Controller.GetExternallyUsableCompletionItem(model.SelectedItem); var textChange = selectedItem.CompletionProvider.GetTextChange(selectedItem, ch, GetCurrentFilterText(model, selectedItem)); this.Commit(selectedItem, new TextChange(textChange.Span, textChange.NewText + ch), model, ch); }
private void CommitOnEnter(out bool sendThrough, out bool committed) { AssertIsForeground(); var model = sessionOpt.WaitForModel(); // If there's no model, then there's nothing to commit. if (model == null) { // Make sure that the enter gets sent into the buffer. sendThrough = true; committed = false; return; } // If we're in a normal editor or the Immediate window, we'll send the enter through // to the editor. In single-line debugger windows (Watch, etc), however, we don't // want to send the enter though, because those windows don't support displaying // more than one line of text. sendThrough = !_isDebugger || _isImmediateWindow; if (model.IsSoftSelection) { // If the completion list is soft selected, then don't commit on enter. // Instead, just dismiss the completion list. committed = false; return; } var selectedItem = Controller.GetExternallyUsableCompletionItem(model.SelectedItem); // If the selected item is the builder, dismiss if (selectedItem.IsBuilder) { sendThrough = false; committed = false; return; } var completionRules = GetCompletionRules(); if (sendThrough) { // Get the text that the user has currently entered into the buffer var viewSpan = model.GetSubjectBufferFilterSpanInViewBuffer(selectedItem.FilterSpan); var textTypedSoFar = model.GetCurrentTextInSnapshot( viewSpan, this.TextView.TextSnapshot, this.GetCaretPointInViewBuffer()); var options = GetOptions(); if (options != null) { sendThrough = completionRules.SendEnterThroughToEditor(selectedItem, textTypedSoFar, options); } } var textChange = completionRules.GetTextChange(selectedItem); this.Commit(selectedItem, textChange, model, null); committed = true; }
private void CommitOnTab(out bool committed) { AssertIsForeground(); var model = sessionOpt.WaitForModel(); // If there's no model, then there's nothing to commit. if (model == null) { committed = false; return; } var selectedItem = Controller.GetExternallyUsableCompletionItem(model.SelectedItem); // If the selected item is the builder, there's not actually any work to do to commit if (selectedItem.IsBuilder) { committed = true; this.StopModelComputation(); return; } var textChange = selectedItem.CompletionProvider.GetTextChange(selectedItem); Commit(selectedItem, textChange, model, null); committed = true; }
internal override void OnCaretPositionChanged(object sender, EventArgs args) { AssertIsForeground(); if (!IsSessionActive) { // No session, so we don't need to do anything. return; } // If we have a session active then it may be in the process of computing results. If it // has computed the results, then compare where the caret is with all the items. If the // caret isn't within the bounds of the items, then we dismiss completion. var caretPoint = this.GetCaretPointInViewBuffer(); var model = sessionOpt.Computation.InitialUnfilteredModel; if (model == null || this.IsCaretOutsideAllItemBounds(model, caretPoint)) { // Completions hadn't even been computed yet or the caret is out of bounds. // Just cancel everything we're doing. this.StopModelComputation(); return; } // TODO(cyrusn): Find a way to allow the user to cancel out of this. model = sessionOpt.WaitForModel(); if (model == null) { return; } if (model.SelectedItem != null && model.IsHardSelection) { // Switch to soft selection, if user moved caret to the start of a non-empty filter span. // This prevents commiting if user types a commit character at this position later, but still has the list if user types filter character // i.e. blah| -> |blah -> !|blah // We want the filter span non-empty because we still want completion in the following case: // A a = new | -> A a = new (| var selectedItem = Controller.GetExternallyUsableCompletionItem(model.SelectedItem); var currentSpan = model.GetSubjectBufferFilterSpanInViewBuffer(selectedItem.FilterSpan).TextSpan; if (caretPoint == currentSpan.Start && currentSpan.Length > 0) { sessionOpt.SetModelIsHardSelection(false); } } }
void ICommandHandler <CommitUniqueCompletionListItemCommandArgs> .ExecuteCommand(CommitUniqueCompletionListItemCommandArgs args, Action nextHandler) { AssertIsForeground(); if (sessionOpt == null) { // User hit ctrl-space. If there was no completion up then we want to trigger // completion. var completionService = this.CreateCompletionService(); if (completionService == null) { return; } if (!StartNewModelComputation(completionService, filterItems: true)) { return; } } // Get the selected item. If it's unique, then we want to commit it. var model = this.sessionOpt.WaitForModel(); if (model == null) { // Computation failed. Just pass this command on. nextHandler(); return; } // Note: Dev10 behavior seems to be that if there is no unique item that filtering is // turned off. However, i do not know if this is desirable behavior, or merely a bug // with how that convoluted code worked. So i'm not maintaining that behavior here. If // we do want it through, it would be easy to get again simply by asking the model // computation to remove all filtering. if (model.IsUnique) { // We had a unique item in the list. Commit it and dismiss this session. var selectedItem = Controller.GetExternallyUsableCompletionItem(model.SelectedItem); var textChange = selectedItem.CompletionProvider.GetTextChange(selectedItem); this.Commit(selectedItem, textChange, model, null); } }
private void CommitOnEnter(out bool sendThrough, out bool committed) { AssertIsForeground(); var model = sessionOpt.WaitForModel(); // If there's no model, then there's nothing to commit. if (model == null) { // Make sure that the enter gets sent into the buffer. sendThrough = true; committed = false; return; } if (model.IsSoftSelection) { // If the completion list is soft selected, then don't commit on enter. Instead, // send the enter through to the editor and dismiss the completion list. sendThrough = true; committed = false; return; } var selectedItem = Controller.GetExternallyUsableCompletionItem(model.SelectedItem); // If the selected item is the builder, dismiss if (selectedItem.IsBuilder) { sendThrough = false; committed = false; return; } // Get the text that the user has currently entered into the buffer var viewSpan = model.GetSubjectBufferFilterSpanInViewBuffer(selectedItem.FilterSpan); var textTypedSoFar = model.GetCurrentTextInSnapshot( viewSpan, this.TextView.TextSnapshot, this.GetCaretPointInViewBuffer()); var textChange = selectedItem.CompletionProvider.GetTextChange(selectedItem); this.Commit(selectedItem, textChange, model, null); sendThrough = selectedItem.CompletionProvider.SendEnterThroughToEditor(selectedItem, textTypedSoFar); committed = true; }
private void CommitItem(CompletionItem item) { AssertIsForeground(); item = Controller.GetExternallyUsableCompletionItem(item); // We should not be getting called if we didn't even have a computation running. Contract.ThrowIfNull(this.sessionOpt); Contract.ThrowIfNull(this.sessionOpt.Computation.InitialUnfilteredModel); // If the selected item is the builder, there's not actually any work to do to commit if (item.IsBuilder) { this.StopModelComputation(); return; } var textChange = item.CompletionProvider.GetTextChange(item); this.Commit(item, textChange, this.sessionOpt.Computation.InitialUnfilteredModel, null); }
private bool IsFilterCharacter(char ch) { AssertIsForeground(); // TODO(cyrusn): Find a way to allow the user to cancel out of this. var model = sessionOpt.WaitForModel(); if (model == null) { return(false); } var selectedItem = Controller.GetExternallyUsableCompletionItem(model.SelectedItem); if (selectedItem.IsBuilder) { return(char.IsLetterOrDigit(ch)); } var filterText = GetCurrentFilterText(model, selectedItem); return(selectedItem.CompletionProvider.IsFilterCharacter(selectedItem, ch, filterText)); }
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); } }