public void ExecuteCommand(TypeCharCommandArgs args, Action nextHandler, CommandExecutionContext context) { var originalCaretPosition = args.TextView.GetCaretPoint(args.SubjectBuffer) ?? -1; // Ensure the character is actually typed in the editor nextHandler(); if (args.TypedChar != TriggerCharacter) { return; } CompleteComment(args.SubjectBuffer, args.TextView, originalCaretPosition, InsertOnCharacterTyped, CancellationToken.None); }
public void ExecuteCommand(TypeCharCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext) { AssertIsForeground(); if (args.TypedChar == ';' && AreSnippetsEnabled(args) && args.TextView.Properties.TryGetProperty(typeof(AbstractSnippetExpansionClient), out AbstractSnippetExpansionClient snippetExpansionClient) && snippetExpansionClient.IsFullMethodCallSnippet) { // Commit the snippet. Leave the caret in place, but clear the selection. Subsequent handlers in the // chain will handle the remaining Complete Statement (';' insertion) operations only if there is no // active selection. snippetExpansionClient.CommitSnippet(leaveCaret: true); args.TextView.Selection.Clear(); } nextCommandHandler(); }
public void Verify(string initialMarkup, string expectedMarkup, char typeChar) { using (var workspace = CreateTestWorkspace(initialMarkup)) { var testDocument = workspace.Documents.Single(); var view = testDocument.GetTextView(); view.Caret.MoveTo( new SnapshotPoint(view.TextSnapshot, testDocument.CursorPosition.Value) ); var commandHandler = CreateCommandHandler(workspace); var args = new TypeCharCommandArgs(view, view.TextBuffer, typeChar); var nextHandler = CreateInsertTextHandler(view, typeChar.ToString()); commandHandler.ExecuteCommand( args, nextHandler, TestCommandExecutionContext.Create() ); MarkupTestFile.GetPosition( expectedMarkup, out var expectedCode, out int expectedPosition ); Assert.Equal(expectedCode, view.TextSnapshot.GetText()); var caretPosition = view.Caret.Position.BufferPosition.Position; Assert.True( expectedPosition == caretPosition, string.Format( "Caret positioned incorrectly. Should have been {0}, but was {1}.", expectedPosition, caretPosition ) ); } }
protected void VerifyTypingCharacter( string initialMarkup, string expectedMarkup, bool useTabs = false, bool autoGenerateXmlDocComments = true, string newLine = "\r\n" ) { Verify( initialMarkup, expectedMarkup, useTabs, autoGenerateXmlDocComments, newLine: newLine, execute: (workspace, view, editorOperationsFactoryService) => { var commandHandler = CreateCommandHandler(workspace); var commandArgs = new TypeCharCommandArgs( view, view.TextBuffer, DocumentationCommentCharacter ); var nextHandler = CreateInsertTextHandler( view, DocumentationCommentCharacter.ToString() ); commandHandler.ExecuteCommand( commandArgs, nextHandler, TestCommandExecutionContext.Create() ); } ); }
void IChainedCommandHandler <TypeCharCommandArgs> .ExecuteCommand(TypeCharCommandArgs args, Action nextHandler, CommandExecutionContext context) { AssertIsForeground(); var allProviders = GetProviders(); if (allProviders == null) { nextHandler(); return; } // Note: while we're doing this, we don't want to hear about buffer changes (since we // know they're going to happen). So we disconnect and reconnect to the event // afterwards. That way we can hear about changes to the buffer that don't happen // through us. this.TextView.TextBuffer.PostChanged -= OnTextViewBufferPostChanged; try { nextHandler(); } finally { this.TextView.TextBuffer.PostChanged += OnTextViewBufferPostChanged; } // We only want to process typechar if it is a normal typechar and no one else is // involved. i.e. if there was a typechar, but someone processed it and moved the caret // somewhere else then we don't want signature help. Also, if a character was typed but // something intercepted and placed different text into the editor, then we don't want // to proceed. // // Note: we do not want to pass along a text version here. It is expected that multiple // version changes may happen when we call 'nextHandler' and we will still want to // proceed. For example, if the user types "WriteL(", then that will involve two text // changes as completion commits that out to "WriteLine(". But we still want to provide // sig help in this case. if (this.TextView.TypeCharWasHandledStrangely(this.SubjectBuffer, args.TypedChar)) { // If we were computing anything, we stop. We only want to process a typechar // if it was a normal character. DismissSessionIfActive(); return; } // Separate the sig help providers into two buckets; one bucket for those that were triggered // by the typed character, and those that weren't. To keep our queries to a minimum, we first // check with the textually triggered providers. If none of those produced any sig help items // then we query the other providers to see if they can produce anything viable. This takes // care of cases where the filtered set of providers didn't provide anything but one of the // other providers could still be valid, but doesn't explicitly treat the typed character as // a trigger character. var(textuallyTriggeredProviders, untriggeredProviders) = FilterProviders(allProviders, args.TypedChar); var triggerInfo = new SignatureHelpTriggerInfo(SignatureHelpTriggerReason.TypeCharCommand, args.TypedChar); if (!IsSessionActive) { // No computation at all. If this is not a trigger character, we just ignore it and // stay in this state. Otherwise, if it's a trigger character, start up a new // computation and start computing the model in the background. if (textuallyTriggeredProviders.Any()) { // First create the session that represents that we now have a potential // signature help list. Then tell it to start computing. StartSession(textuallyTriggeredProviders, triggerInfo); return; } else { // No need to do anything. Just stay in the state where we have no session. return; } } else { var computed = false; if (allProviders.Any(p => p.IsRetriggerCharacter(args.TypedChar))) { // The user typed a character that might close the scope of the current model. // In this case, we should requery all providers. // // e.g. Math.Max(Math.Min(1,2)$$ sessionOpt.ComputeModel(allProviders, new SignatureHelpTriggerInfo(SignatureHelpTriggerReason.RetriggerCommand, triggerInfo.TriggerCharacter)); computed = true; } if (textuallyTriggeredProviders.Any()) { // The character typed was something like "(". It can both filter a list if // it was in a string like: Foo(bar, "( // // Or it can trigger a new list. Ask the computation to compute again. sessionOpt.ComputeModel( textuallyTriggeredProviders.Concat(untriggeredProviders), triggerInfo); computed = true; } if (!computed) { // A character was typed and we haven't updated our model; do so now. sessionOpt.ComputeModel(allProviders, new SignatureHelpTriggerInfo(SignatureHelpTriggerReason.RetriggerCommand)); } } }
void ICommandHandler <TypeCharCommandArgs> .ExecuteCommand(TypeCharCommandArgs args, Action nextHandler) { Trace.WriteLine("Entered completion command handler for typechar."); AssertIsForeground(); var initialCaretPosition = GetCaretPointInViewBuffer(); // When a character is typed it is *always* sent through to the editor. This way the // editor always represents what would have been typed had completion not been involved // at this point. After we send the character into the buffer we then decide what to do // with the completion set. If we decide to commit it then we will replace the // appropriate span (which will include the character just sent to the buffer) with the // appropriate insertion text *and* the character typed. This way, after we commit, the // editor has the insertion text of the selected item, and the character typed. It // also means that if we then undo that we'll see the text that would have been typed // had no completion been active. // Note: while we're doing this, we don't want to hear about buffer changes (since we // know they're going to happen). So we disconnect and reconnect to the event // afterwards. That way we can hear about changes to the buffer that don't happen // through us. // Automatic Brace Completion may also move the caret, so unsubscribe from that too this.TextView.TextBuffer.PostChanged -= OnTextViewBufferPostChanged; this.TextView.Caret.PositionChanged -= OnCaretPositionChanged; // In Venus/Razor, the user might be typing on the buffer's seam. This means that, // depending on the character typed, the character may not go into our buffer. var isOnSeam = IsOnSeam(); try { nextHandler(); } finally { this.TextView.TextBuffer.PostChanged += OnTextViewBufferPostChanged; this.TextView.Caret.PositionChanged += OnCaretPositionChanged; } // We only want to process typechar if it is a normal typechar and no one else is // involved. i.e. if there was a typechar, but someone processed it and moved the caret // somewhere else then we don't want completion. Also, if a character was typed but // something intercepted and placed different text into the editor, then we don't want // to proceed. if (this.TextView.TypeCharWasHandledStrangely(this.SubjectBuffer, args.TypedChar)) { Trace.WriteLine("typechar was handled by someone else, cannot have a completion session."); if (sessionOpt != null) { // If we're on a seam (razor) with a computation, and the user types a character // that goes into the other side of the seam, the character may be a commit character. // If it's a commit character, just commit without trying to check caret position, // since the caret is no longer in our buffer. if (isOnSeam && this.IsCommitCharacter(args.TypedChar)) { Trace.WriteLine("typechar was on seam and a commit char, cannot have a completion session."); this.CommitOnTypeChar(args.TypedChar); return; } else if (_autoBraceCompletionChars.Contains(args.TypedChar) && this.SubjectBuffer.GetOption(InternalFeatureOnOffOptions.AutomaticPairCompletion) && this.IsCommitCharacter(args.TypedChar)) { Trace.WriteLine("typechar was brace completion char and a commit char, cannot have a completion session."); // I don't think there is any better way than this. if typed char is one of auto brace completion char, // we don't do multiple buffer change check this.CommitOnTypeChar(args.TypedChar); return; } else { Trace.WriteLine("we stop model computation, cannot have a completion session."); // If we were computing anything, we stop. We only want to process a typechar // if it was a normal character. this.StopModelComputation(); } } return; } var completionService = this.GetCompletionService(); if (completionService == null) { Trace.WriteLine("handling typechar, completion service is null, cannot have a completion session."); return; } var options = GetOptions(); Contract.ThrowIfNull(options); var isTextuallyTriggered = IsTextualTriggerCharacter(completionService, args.TypedChar, options); var isPotentialFilterCharacter = IsPotentialFilterCharacter(args); var trigger = CompletionTrigger.CreateInsertionTrigger(args.TypedChar); if (sessionOpt == null) { // No computation at all. If this is not a trigger character, we just ignore it and // stay in this state. Otherwise, if it's a trigger character, start up a new // computation and start computing the model in the background. if (isTextuallyTriggered) { Trace.WriteLine("no completion session yet and this is a trigger char, starting model computation."); // First create the session that represents that we now have a potential // completion list. Then tell it to start computing. StartNewModelComputation(completionService, trigger, filterItems: true); return; } else { Trace.WriteLine("no completion session yet and this is NOT a trigger char, we won't have completion."); // No need to do anything. Just stay in the state where we have no session. return; } } else { Trace.WriteLine("we have a completion session."); sessionOpt.UpdateModelTrackingSpan(initialCaretPosition); // If the session is up, it may be in one of many states. It may know nothing // (because it is currently computing the list of completions). Or it may have a // list of completions that it has filtered. // If the user types something which is absolutely known to be a filter character // then we can just proceed without blocking. if (isPotentialFilterCharacter) { if (isTextuallyTriggered) { Trace.WriteLine("computing completion again and filtering..."); // The character typed was something like "a". It can both filter a list if // we have computed one, or it can trigger a new list. Ask the computation // to compute again. If nothing has been computed, then it will try to // compute again, otherwise it will just ignore this request. sessionOpt.ComputeModel(completionService, trigger, _roles, options); } // Now filter whatever result we have. sessionOpt.FilterModel(CompletionFilterReason.TypeChar); } else { // It wasn't a trigger or filter character. At this point, we make our // determination on what to do based on what has actually been computed and // what's being typed. This means waiting on the session and will effectively // block the user. // Again, from this point on we must block on the computation to decide what to // do. // What they type may end up filtering, committing, or else will dismiss. // // For example, we may filter in cases like this: "Color." // // "Color" will have already filtered the list down to some things like // "Color", "Color.Red", "Color.Blue", etc. When we process the 'dot', we // actually want to filter some more. But we can't know that ahead of time until // we have computed the list of completions. if (this.IsFilterCharacter(args.TypedChar)) { Trace.WriteLine("filtering the session..."); // Known to be a filter character for the currently selected item. So just // filter the session. sessionOpt.FilterModel(CompletionFilterReason.TypeChar); return; } // It wasn't a filter character. We'll either commit what's selected, or we'll // dismiss the completion list. First, ensure that what was typed is in the // buffer. // Now, commit if it was a commit character. if (this.IsCommitCharacter(args.TypedChar)) { Trace.WriteLine("committing the session..."); // Known to be a commit character for the currently selected item. So just // commit the session. this.CommitOnTypeChar(args.TypedChar); } else { Trace.WriteLine("dismissing the session..."); // Now dismiss the session. this.StopModelComputation(); } // The character may commit/dismiss and then trigger completion again. So check // for that here. if (isTextuallyTriggered) { Trace.WriteLine("the char commit/dismiss -ed a session and is trigerring completion again. starting model computation."); // First create the session that represents that we now have a potential // completion list. StartNewModelComputation(completionService, trigger, filterItems: true); return; } } } }
private void ExecuteCommand(TypeCharCommandArgs args, Action nextHandler, CancellationToken cancellationToken) { ExecuteReturnOrTypeCommand(args, nextHandler, cancellationToken); }
public void ExecuteCommand(TypeCharCommandArgs args, Action nextHandler, CommandExecutionContext context) { ExecuteCommand(args, nextHandler, CancellationToken.None); }
public VSCommanding.CommandState GetCommandState(TypeCharCommandArgs args, Func <VSCommanding.CommandState> nextHandler) { return(nextHandler()); }
public bool ExecuteCommand(TypeCharCommandArgs args, CommandExecutionContext executionContext) { return(false); }
void ICommandHandler <TypeCharCommandArgs> .ExecuteCommand(TypeCharCommandArgs args, System.Action nextHandler) { AssertIsForeground(); ExecuteCommandWorker(args, nextHandler); }
CommandState ICommandHandler <TypeCharCommandArgs> .GetCommandState(TypeCharCommandArgs args, System.Func <CommandState> nextHandler) { AssertIsForeground(); return(GetCommandStateWorker(args, nextHandler)); }
VSCommanding.CommandState IChainedCommandHandler <TypeCharCommandArgs> .GetCommandState(TypeCharCommandArgs args, Func <VSCommanding.CommandState> nextHandler) { AssertIsForeground(); // We just defer to the editor here. We do not interfere with typing normal characters. return(nextHandler()); }
void IChainedCommandHandler <TypeCharCommandArgs> .ExecuteCommand(TypeCharCommandArgs args, Action nextHandler, CommandExecutionContext context) { AssertIsForeground(); ExecuteCommandWorker(args, nextHandler, context); }
CommandState ICommandHandler <TypeCharCommandArgs> .GetCommandState(TypeCharCommandArgs args) { return(args.TypedChar == '\0' ? CommandState.Unavailable : AvailableInEditableView(args.TextView)); }
public CommandState GetCommandState(TypeCharCommandArgs args, Func <CommandState> nextHandler) => nextHandler();
void ICommandHandler <TypeCharCommandArgs> .ExecuteCommand(TypeCharCommandArgs args, Action nextHandler) { AssertIsForeground(); // When a character is typed it is *always* sent through to the editor. This way the // editor always represents what would have been typed had completion not been involved // at this point. That means that if we decide to commit, then undo'ing the commit will // return you to the code that you would have typed if completion was not up. // // The steps we follow for commit are as follows: // // 1) send the commit character through to the buffer. // 2) open a transaction. // 2a) roll back the text to before the text was sent through // 2b) commit the item. // 2c) send the commit character through again.* // 2d) commit the transaction. // // 2c is very important. it makes sure that post our commit all our normal features // run depending on what got typed. For example if the commit character was ( // then brace completion may run. If it was ; then formatting may run. But, importantly // this code doesn't need to know anything about that. Furthermore, because that code // runs within this transaction, then the user can always undo and get to what the code // would have been if completion was not involved. // // 2c*: note sending the commit character through to the buffer again can be controlled // by the completion item. For example, completion items that want to totally handle // what gets output into the buffer can ask for this not to happen. An example of this // is override completion. If the user types "override Method(" then we'll want to // spit out the entire method and *not* also spit out "(" again. // In order to support 2a (rolling back), we capture hte state of the buffer before // we send the character through. We then just apply the edits in reverse order to // roll us back. var initialTextSnapshot = this.SubjectBuffer.CurrentSnapshot; var initialCaretPosition = GetCaretPointInViewBuffer(); // Note: while we're doing this, we don't want to hear about buffer changes (since we // know they're going to happen). So we disconnect and reconnect to the event // afterwards. That way we can hear about changes to the buffer that don't happen // through us. // Automatic Brace Completion may also move the caret, so unsubscribe from that too this.TextView.TextBuffer.PostChanged -= OnTextViewBufferPostChanged; this.TextView.Caret.PositionChanged -= OnCaretPositionChanged; // In Venus/Razor, the user might be typing on the buffer's seam. This means that, // depending on the character typed, the character may not go into our buffer. var isOnSeam = IsOnSeam(); try { nextHandler(); } finally { this.TextView.TextBuffer.PostChanged += OnTextViewBufferPostChanged; this.TextView.Caret.PositionChanged += OnCaretPositionChanged; } // We only want to process typechar if it is a normal typechar and no one else is // involved. i.e. if there was a typechar, but someone processed it and moved the caret // somewhere else then we don't want completion. Also, if a character was typed but // something intercepted and placed different text into the editor, then we don't want // to proceed. if (this.TextView.TypeCharWasHandledStrangely(this.SubjectBuffer, args.TypedChar)) { if (sessionOpt != null) { // If we're on a seam (razor) with a computation, and the user types a character // that goes into the other side of the seam, the character may be a commit character. // If it's a commit character, just commit without trying to check caret position, // since the caret is no longer in our buffer. if (isOnSeam && this.IsCommitCharacter(args.TypedChar)) { this.CommitOnTypeChar(args.TypedChar, initialTextSnapshot, nextHandler); return; } else if (_autoBraceCompletionChars.Contains(args.TypedChar) && this.SubjectBuffer.GetOption(InternalFeatureOnOffOptions.AutomaticPairCompletion) && this.IsCommitCharacter(args.TypedChar)) { // I don't think there is any better way than this. if typed char is one of auto brace completion char, // we don't do multiple buffer change check this.CommitOnTypeChar(args.TypedChar, initialTextSnapshot, nextHandler); return; } else { // If we were computing anything, we stop. We only want to process a typechar // if it was a normal character. this.StopModelComputation(); } } return; } var completionService = this.GetCompletionService(); if (completionService == null) { return; } var options = GetOptions(); Contract.ThrowIfNull(options); var isTextuallyTriggered = IsTextualTriggerCharacter(completionService, args.TypedChar, options); var isPotentialFilterCharacter = IsPotentialFilterCharacter(args); var trigger = CompletionTrigger.CreateInsertionTrigger(args.TypedChar); if (sessionOpt == null) { // No computation at all. If this is not a trigger character, we just ignore it and // stay in this state. Otherwise, if it's a trigger character, start up a new // computation and start computing the model in the background. if (isTextuallyTriggered) { // First create the session that represents that we now have a potential // completion list. Then tell it to start computing. StartNewModelComputation(completionService, trigger, filterItems: true, dismissIfEmptyAllowed: true); return; } else { // No need to do anything. Just stay in the state where we have no session. return; } } else { sessionOpt.UpdateModelTrackingSpan(initialCaretPosition); // If the session is up, it may be in one of many states. It may know nothing // (because it is currently computing the list of completions). Or it may have a // list of completions that it has filtered. // If the user types something which is absolutely known to be a filter character // then we can just proceed without blocking. if (isPotentialFilterCharacter) { if (isTextuallyTriggered) { // The character typed was something like "a". It can both filter a list if // we have computed one, or it can trigger a new list. Ask the computation // to compute again. If nothing has been computed, then it will try to // compute again, otherwise it will just ignore this request. sessionOpt.ComputeModel(completionService, trigger, _roles, options); } // Now filter whatever result we have. sessionOpt.FilterModel( CompletionFilterReason.TypeChar, recheckCaretPosition: false, dismissIfEmptyAllowed: true, filterState: null); } else { // It wasn't a trigger or filter character. At this point, we make our // determination on what to do based on what has actually been computed and // what's being typed. This means waiting on the session and will effectively // block the user. // Again, from this point on we must block on the computation to decide what to // do. // What they type may end up filtering, committing, or else will dismiss. // // For example, we may filter in cases like this: "Color." // // "Color" will have already filtered the list down to some things like // "Color", "Color.Red", "Color.Blue", etc. When we process the 'dot', we // actually want to filter some more. But we can't know that ahead of time until // we have computed the list of completions. if (this.IsFilterCharacter(args.TypedChar)) { // Known to be a filter character for the currently selected item. So just // filter the session. sessionOpt.FilterModel(CompletionFilterReason.TypeChar, recheckCaretPosition: false, dismissIfEmptyAllowed: true, filterState: null); return; } // It wasn't a filter character. We'll either commit what's selected, or we'll // dismiss the completion list. First, ensure that what was typed is in the // buffer. // Now, commit if it was a commit character. if (this.IsCommitCharacter(args.TypedChar)) { // Known to be a commit character for the currently selected item. So just // commit the session. this.CommitOnTypeChar(args.TypedChar, initialTextSnapshot, nextHandler); } else { // Now dismiss the session. this.StopModelComputation(); } // The character may commit/dismiss and then trigger completion again. So check // for that here. if (isTextuallyTriggered) { // First create the session that represents that we now have a potential // completion list. StartNewModelComputation( completionService, trigger, filterItems: true, dismissIfEmptyAllowed: true); return; } } } }
public void ExecuteCommand(TypeCharCommandArgs args, Action nextHandler, CommandExecutionContext context) { ExecuteReturnOrTypeCommand(args, nextHandler, context.OperationContext.UserCancellationToken); }
public CommandState GetCommandState(TypeCharCommandArgs args, Func <CommandState> nextCommandHandler) { return(nextCommandHandler()); }
public CommandState GetCommandState(TypeCharCommandArgs args) => CommandState.Unspecified;