private void StartSession(ITrackingPoint triggerPoint) { Log.Debug("Creating new completion session..."); _broker.DismissAllSessions(_textView); _activeSession = _broker.CreateCompletionSession(_textView, triggerPoint, true); _activeSession.Properties.AddProperty(BufferProperties.SessionOriginIntellisense, "Intellisense"); _activeSession.Dismissed += CompletionSession_Dismissed; _activeSession.Start(); if (_startTabComplete == true) { var completions = _activeSession.SelectedCompletionSet.Completions; if (completions != null && completions.Count > 0) { var startPoint = _activeSession.SelectedCompletionSet.ApplicableTo.GetStartPoint(_textView.TextBuffer.CurrentSnapshot).Position; _tabCompleteSession = new TabCompleteSession(completions, _activeSession.SelectedCompletionSet.SelectionStatus, startPoint); _activeSession.Commit(); } _startTabComplete = false; } }
/// <summary> /// Main method used to determine how to handle keystrokes within a ITextBuffer. /// </summary> /// <param name="pguidCmdGroup">The GUID of the command group.</param> /// <param name="nCmdId">The command ID.</param> /// <param name="nCmdexecopt"> /// Specifies how the object should execute the command. Possible values are taken from the /// Microsoft.VisualStudio.OLE.Interop.OLECMDEXECOPT and Microsoft.VisualStudio.OLE.Interop.OLECMDID_WINDOWSTATE_FLAG /// enumerations. /// </param> /// <param name="pvaIn">The input arguments of the command.</param> /// <param name="pvaOut">The output arguments of the command.</param> /// <returns></returns> public int Exec(ref Guid pguidCmdGroup, uint nCmdId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { bool?isInStringArea = null; var command = (VSConstants.VSStd2KCmdID)nCmdId; if (VsShellUtilities.IsInAutomationFunction(_serviceProvider) || IsUnhandledCommand(pguidCmdGroup, command) || Utilities.IsCaretInCommentArea(_textView)) { Log.DebugFormat("Non-VSStd2K command: '{0}'", ToCommandName(pguidCmdGroup, nCmdId)); return(NextCommandHandler.Exec(ref pguidCmdGroup, nCmdId, nCmdexecopt, pvaIn, pvaOut)); } //make a copy of this so we can look at it after forwarding some commands var typedChar = char.MinValue; // Exit tab complete session if command is any recognized command other than tab if (_tabCompleteSession != null && command != VSConstants.VSStd2KCmdID.TAB && command != VSConstants.VSStd2KCmdID.BACKTAB) { _tabCompleteSession = null; _startTabComplete = false; } //make sure the input is a char before getting it if (command == VSConstants.VSStd2KCmdID.TYPECHAR) { typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn); Log.DebugFormat("Typed Character: '{0}'", (typedChar == char.MinValue) ? "<null>" : typedChar.ToString()); if (_activeSession == null && IsNotIntelliSenseTriggerWhenInStringLiteral(typedChar)) { isInStringArea = this.IsInStringArea(isInStringArea); if (isInStringArea == true) { return(NextCommandHandler.Exec(ref pguidCmdGroup, nCmdId, nCmdexecopt, pvaIn, pvaOut)); } } } else { Log.DebugFormat("Non-TypeChar command: '{0}'", ToCommandName(pguidCmdGroup, nCmdId)); } switch (command) { case VSConstants.VSStd2KCmdID.RETURN: //check for a a selection if (_activeSession != null && !_activeSession.IsDismissed) { //if the selection is fully selected, commit the current session if (_activeSession.SelectedCompletionSet.SelectionStatus.IsSelected) { Log.Debug("Commit"); _activeSession.Commit(); //also, don't add the character to the buffer return(VSConstants.S_OK); } else { Log.Debug("Dismiss"); //if there is no selection, dismiss the session _activeSession.Dismiss(); } } break; case VSConstants.VSStd2KCmdID.TAB: case VSConstants.VSStd2KCmdID.BACKTAB: if (_activeSession != null && !_activeSession.IsDismissed) { var completions = _activeSession.SelectedCompletionSet.Completions; if (completions != null && completions.Count > 0) { var startPoint = _activeSession.SelectedCompletionSet.ApplicableTo.GetStartPoint(_textView.TextBuffer.CurrentSnapshot).Position; _tabCompleteSession = new TabCompleteSession(_activeSession.SelectedCompletionSet.Completions, _activeSession.SelectedCompletionSet.SelectionStatus, startPoint); _activeSession.Commit(); //also, don't add the character to the buffer return(VSConstants.S_OK); } else { Log.Debug("Dismiss"); //If there are no completions, dismiss the session _activeSession.Dismiss(); } } else if (_tabCompleteSession != null) { if (command == VSConstants.VSStd2KCmdID.TAB) { _tabCompleteSession.ReplaceWithNextCompletion(_textView.TextBuffer, _textView.Caret.Position.BufferPosition.Position); } else { _tabCompleteSession.ReplaceWithPreviousCompletion(_textView.TextBuffer, _textView.Caret.Position.BufferPosition.Position); } //don't add the character to the buffer return(VSConstants.S_OK); } else if (!Utilities.IsPrecedingTextInLineEmpty(_textView.Caret.Position.BufferPosition) && _textView.Selection.IsEmpty) { _startTabComplete = true; TriggerCompletion(); //don't add the character to the buffer return(VSConstants.S_OK); } break; case VSConstants.VSStd2KCmdID.COMPLETEWORD: isInStringArea = this.IsInStringArea(isInStringArea); if (isInStringArea == true) { return(NextCommandHandler.Exec(ref pguidCmdGroup, nCmdId, nCmdexecopt, pvaIn, pvaOut)); } TriggerCompletion(); return(VSConstants.S_OK); default: break; } //check for a commit character if (char.IsWhiteSpace(typedChar) && _activeSession != null && !_activeSession.IsDismissed) { // If user is typing a variable, SPACE shouldn't commit the selection. // If the selection is fully matched with user's input, commit the current session and add the commit character to text buffer. if (_activeSession.SelectedCompletionSet.SelectionStatus.IsSelected && !_activeSession.SelectedCompletionSet.SelectionStatus.Completion.InsertionText.StartsWith("$", StringComparison.InvariantCulture)) { Log.Debug("Commit"); _activeSession.Commit(); bool isCompletionFullyMatched = false; _textView.TextBuffer.Properties.TryGetProperty(BufferProperties.SessionCompletionFullyMatchedStatus, out isCompletionFullyMatched); if (isCompletionFullyMatched) { // If user types all characters in a completion and click Space, then we should commit the selection and add the Space into text buffer. return(NextCommandHandler.Exec(ref pguidCmdGroup, nCmdId, nCmdexecopt, pvaIn, pvaOut)); } //Don't add the character to the buffer if this commits the selection. return(VSConstants.S_OK); } else { Log.Debug("Dismiss"); //if there is no selection, dismiss the session _activeSession.Dismiss(); } } bool justCommitIntelliSense = false; if (IsIntelliSenseTriggerDot(typedChar) && _activeSession != null && !_activeSession.IsDismissed) { var selectionStatus = _activeSession.SelectedCompletionSet.SelectionStatus; if (selectionStatus.IsSelected) { // If user types the full completion text, which ends with a dot, then we should just commit IntelliSense session ignore user's last typing. ITrackingSpan lastWordSpan; var currentSnapshot = _textView.TextBuffer.CurrentSnapshot; _textView.TextBuffer.Properties.TryGetProperty <ITrackingSpan>(BufferProperties.LastWordReplacementSpan, out lastWordSpan); if (lastWordSpan != null) { string lastWordText = lastWordSpan.GetText(currentSnapshot); int completionSpanStart = lastWordSpan.GetStartPoint(currentSnapshot); int completionSpanEnd = _textView.Caret.Position.BufferPosition; var completionText = currentSnapshot.GetText(completionSpanStart, completionSpanEnd - completionSpanStart); completionText += typedChar; Log.DebugFormat("completionSpanStart: {0}", completionSpanStart); Log.DebugFormat("completionSpanEnd: {0}", completionSpanEnd); Log.DebugFormat("completionText: {0}", completionText); if (selectionStatus.Completion.InsertionText.Equals(completionText, StringComparison.OrdinalIgnoreCase)) { Log.Debug(String.Format("Commited by {0}", typedChar)); _activeSession.Commit(); return(VSConstants.S_OK); } } Log.Debug("Commit"); _activeSession.Commit(); justCommitIntelliSense = true; } else { Log.Debug("Dismiss"); //if there is no selection, dismiss the session _activeSession.Dismiss(); } } // Check the char at caret before pass along the command // If command is backspace and completion session is active, then we need to see if the char to be deleted is an IntelliSense triggering char // If yes, then after deleting the char, we also dismiss the completion session // Otherwise, just filter the completion lists char charAtCaret = char.MinValue; if (command == VSConstants.VSStd2KCmdID.BACKSPACE && _activeSession != null && !_activeSession.IsDismissed) { int caretPosition = _textView.Caret.Position.BufferPosition.Position - 1; if (caretPosition >= 0) { // caretPosition == -1 means caret is at the beginning of a file, which means no characters before it. ITrackingPoint caretCharPosition = _textView.TextSnapshot.CreateTrackingPoint(caretPosition, PointTrackingMode.Positive); charAtCaret = caretCharPosition.GetCharacter(_textView.TextSnapshot); } } // pass along the command so the char is added to the buffer int retVal = NextCommandHandler.Exec(ref pguidCmdGroup, nCmdId, nCmdexecopt, pvaIn, pvaOut); bool handled = false; if (IsIntellisenseTrigger(typedChar) || (justCommitIntelliSense || (IsIntelliSenseTriggerDot(typedChar) && IsPreviousTokenVariable())) || // If dot just commit a session or previous token before a dot was a variable, trigger intellisense (char.IsWhiteSpace(typedChar) && IsPreviousTokenParameter())) // If the previous token before a space was a parameter, trigger intellisense { isInStringArea = this.IsInStringArea(isInStringArea); if (isInStringArea == false) { TriggerCompletion(); } } if (!typedChar.Equals(char.MinValue) && IsFilterTrigger(typedChar)) { if (_activeSession != null) { if (_activeSession.IsStarted) { try { Log.Debug("Filter"); _activeSession.Filter(); } catch (Exception ex) { Log.Debug("Failed to filter session.", ex); } } } } else if (command == VSConstants.VSStd2KCmdID.BACKSPACE || command == VSConstants.VSStd2KCmdID.DELETE) //redo the filter if there is a deletion { if (_activeSession != null && !_activeSession.IsDismissed) { try { if (_textView.Caret.Position.BufferPosition <= _completionCaretPosition) { Log.Debug("Dismiss"); _activeSession.Dismiss(); } else { Log.Debug("Filter"); _activeSession.Filter(); } } catch (Exception ex) { Log.Debug("Failed to filter session.", ex); } } handled = true; } if (handled) { return(VSConstants.S_OK); } return(retVal); }