public virtual async Task TriggerCompletion(CompletionTriggerReason reason) { if (Editor.SelectionMode == SelectionMode.Block) { return; } if (CompletionWindowManager.IsVisible) { CompletionWindowManager.ToggleCategoryMode(); return; } Editor.EnsureCaretIsNotVirtual(); ICompletionDataList completionList = null; int cpos, wlen; if (!GetCompletionCommandOffset(out cpos, out wlen)) { cpos = Editor.CaretOffset; wlen = 0; } completionTokenSrc.Cancel(); completionTokenSrc = new CancellationTokenSource(); var token = completionTokenSrc.Token; CurrentCompletionContext = CompletionWidget.CreateCodeCompletionContext(cpos); CurrentCompletionContext.TriggerWordLength = wlen; var timer = CurrentCompletionContext.BeginTiming(); bool failure = false; try { completionList = await DoHandleCodeCompletionAsync(CurrentCompletionContext, new CompletionTriggerInfo (reason), token); if (completionList != null && completionList.TriggerWordStart >= 0) { CurrentCompletionContext.TriggerOffset = completionList.TriggerWordStart; CurrentCompletionContext.TriggerWordLength = completionList.TriggerWordLength; } if (completionList == null || !CompletionWindowManager.ShowWindow(this, (char)0, completionList, CompletionWidget, CurrentCompletionContext)) { CurrentCompletionContext = null; } } catch (Exception) { failure = true; throw; } finally { timer.End(); if (failure) { completionStats.OnFailure(timer.Duration); } else { completionStats.OnSuccess(timer.Duration); } } }
/// <summary> /// Does a loose scan of the surrounding code to determine the completion context, and returns a list of meaningful completions. /// </summary> /// <param name="completionSession">The current completion session.</param> /// <param name="completionSets">The target list to store the generated completion sets into.</param> public void AugmentCompletionSession(ICompletionSession completionSession, IList <CompletionSet> completionSets) { // Run text parser, skip all tokens behind the current word SnapshotPoint cursorPoint = (completionSession.TextView.Caret.Position.BufferPosition) - 1; var tokens = _aiParser.GetTokens(cursorPoint, true).SkipWhile(t => t.StartPoint > cursorPoint); // Check whether we are in a comment or in a string; skip the current word AiToken firstToken = tokens.First(); if (firstToken.Type == AiTokenTypes.Comment || firstToken.Type == AiTokenTypes.String) { return; } if (firstToken.Type != AiTokenTypes.OpeningBrace) { tokens = tokens.Skip(1); } // Go backwards and find "(" with known keyword or "=>" CurrentCompletionContext completionContext = CurrentCompletionContext.Unknown; int closingBraceCount = 0; List <AiToken> tokensTillFirstOpeningBrace = new List <AiToken>(); Constants.CommandDefinition commandDefinition = null; // Used for storing the parameter type list of a fact/action. bool encounteredOpeningBrace = false; foreach (AiToken token in tokens) { // Check token type switch (token.Type) { case AiTokenTypes.OpeningBrace: { // Found an opening brace, set flag to stop copying tokens if (!encounteredOpeningBrace) { encounteredOpeningBrace = true; } else { --closingBraceCount; } break; } case AiTokenTypes.ClosingBrace: { // If a closing brace is encountered before an opening one, an error must have occured if (encounteredOpeningBrace) { ++closingBraceCount; } else { completionContext = CurrentCompletionContext.Error; } break; } case AiTokenTypes.Defconst: case AiTokenTypes.Load: case AiTokenTypes.LoadRandom: { // These commands hint a top level context, but completion within one of them is not supported if (encounteredOpeningBrace) { completionContext = CurrentCompletionContext.TopLevel; } else { completionContext = CurrentCompletionContext.NotSupported; } break; } case AiTokenTypes.Defrule: case AiTokenTypes.BooleanFactName: { // If an opening brace was found, we have a fact if (encounteredOpeningBrace) { completionContext = CurrentCompletionContext.RuleFacts; } else { completionContext = CurrentCompletionContext.Error; // "defrule" has no parameters } break; } case AiTokenTypes.RuleArrow: { // If an opening brace was found, we have an action if (encounteredOpeningBrace && closingBraceCount == 0) { completionContext = CurrentCompletionContext.RuleActions; } else if (encounteredOpeningBrace && closingBraceCount == 1) { completionContext = CurrentCompletionContext.TopLevel; } else { completionContext = CurrentCompletionContext.Error; // "=>" has no parameters } break; } case AiTokenTypes.FactName: { // We probably have a fact/action with parameter list if (!encounteredOpeningBrace) { // Get parameter list commandDefinition = Constants.AiRuleFacts[token.Content]; completionContext = CurrentCompletionContext.Parameters; } // TODO error state if directly in front of a brace break; } case AiTokenTypes.ActionName: { // We probably have a fact/action with parameter list if (!encounteredOpeningBrace) { // Get parameter list commandDefinition = Constants.AiRuleActions[token.Content]; completionContext = CurrentCompletionContext.Parameters; } // TODO error state if directly in front of a brace break; } case AiTokenTypes.Word: case AiTokenTypes.Number: case AiTokenTypes.String: { // Put this token into the list to determine the current parameter position of a given fact or action if (!encounteredOpeningBrace) { tokensTillFirstOpeningBrace.Add(token); } // TODO error state if directly in front of a brace break; } } // Stop if a context was deduced or an error occured if (completionContext != CurrentCompletionContext.Unknown) { break; } } // Handle input at document start if (completionContext == CurrentCompletionContext.Unknown && encounteredOpeningBrace) { completionContext = CurrentCompletionContext.TopLevel; } // Show completion set depending on current context switch (completionContext) { case CurrentCompletionContext.TopLevel: completionSets.Add(new CompletionSet("Commands", "Commands", GetTrackingSpan(firstToken.StartPoint, cursorPoint), _aiTopLevelKeywordCompletions, null)); break; case CurrentCompletionContext.RuleFacts: completionSets.Add(new CompletionSet("Facts", "Facts", GetTrackingSpan(firstToken.StartPoint, cursorPoint), _aiFactCompletions, null)); break; case CurrentCompletionContext.RuleActions: completionSets.Add(new CompletionSet("Actions", "Actions", GetTrackingSpan(firstToken.StartPoint, cursorPoint), _aiActionCompletions, null)); break; case CurrentCompletionContext.Parameters: { // Compare scanned parameters with parameter list tokensTillFirstOpeningBrace.Reverse(); if (tokensTillFirstOpeningBrace.Count > commandDefinition.Parameters.Count()) { break; } int i = 0; foreach (string parameterType in commandDefinition.Parameters) { // Reached current parameter? if (i == tokensTillFirstOpeningBrace.Count) { // Show completion list, if there is one if (Constants.AiCommandParameters.ContainsKey(parameterType)) { completionSets.Add(new CompletionSet("Parameter", "Parameter values", GetTrackingSpan(firstToken.StartPoint, cursorPoint), _aiCommandParameterCompletions[parameterType], null)); } break; } else { // Check type AiToken token = tokensTillFirstOpeningBrace[i]; if (parameterType == "string" && token.Type != AiTokenTypes.String) { goto case CurrentCompletionContext.Error; } else if (parameterType == "value" && token.Type != AiTokenTypes.Number) { goto case CurrentCompletionContext.Error; } else if (Constants.AiCommandParameters.ContainsKey(parameterType) && !Constants.AiCommandParameters[parameterType].PossibleValues.Contains(token.Content)) { goto case CurrentCompletionContext.Error; } } ++i; } break; } case CurrentCompletionContext.Error: // Do nothing break; } }
// When a key is pressed, and before the key is processed by the editor, this method will be invoked. // Return true if the key press should be processed by the editor. public override bool KeyPress(KeyDescriptor descriptor) { if (!IsActiveExtension()) { return(base.KeyPress(descriptor)); } bool res; if (CurrentCompletionContext != null && CompletionWindowManager.Wnd != null) { if (CompletionWindowManager.PreProcessKeyEvent(descriptor)) { CompletionWindowManager.PostProcessKeyEvent(descriptor); autoHideCompletionWindow = true; // in named parameter case leave the parameter window open. autoHideParameterWindow = descriptor.KeyChar != ':'; if (!autoHideParameterWindow && ParameterInformationWindowManager.IsWindowVisible) { ParameterInformationWindowManager.PostProcessKeyEvent(this, CompletionWidget, descriptor); } return(false); } autoHideCompletionWindow = autoHideParameterWindow = false; } if (ParameterInformationWindowManager.IsWindowVisible) { if (ParameterInformationWindowManager.ProcessKeyEvent(this, CompletionWidget, descriptor)) { return(false); } autoHideCompletionWindow = autoHideParameterWindow = false; } // int oldPos = Editor.CursorPosition; // int oldLen = Editor.TextLength; char deleteOrBackspaceTriggerChar = '\0'; if (descriptor.SpecialKey == SpecialKey.Delete && Editor.CaretOffset < Editor.Length) { deleteOrBackspaceTriggerChar = Editor.GetCharAt(Editor.CaretOffset); } if (descriptor.SpecialKey == SpecialKey.BackSpace && Editor.CaretOffset > 0) { deleteOrBackspaceTriggerChar = Editor.GetCharAt(Editor.CaretOffset - 1); } var impl = Editor.GetContent <ITextEditorImpl> (); if (CompletionWindowManager.IsVisible) { impl.LockFixIndentation = true; } try { res = base.KeyPress(descriptor); } finally { impl.LockFixIndentation = false; } if (Editor.EditMode == EditMode.TextLink && Editor.TextLinkPurpose == TextLinkPurpose.Rename) { return(res); } if (descriptor.KeyChar == (char)16 || descriptor.KeyChar == (char)17) { return(res); } CompletionWindowManager.PostProcessKeyEvent(descriptor); var ignoreMods = ModifierKeys.Control | ModifierKeys.Alt | ModifierKeys.Command; // Handle parameter completion if (ParameterInformationWindowManager.IsWindowVisible) { ParameterInformationWindowManager.PostProcessKeyEvent(this, CompletionWidget, descriptor); } if ((descriptor.ModifierKeys & ignoreMods) != 0) { return(res); } // don't complete on block selection if (!IdeApp.Preferences.EnableAutoCodeCompletion || Editor.SelectionMode == MonoDevelop.Ide.Editor.SelectionMode.Block) { return(res); } // Handle code completion if (descriptor.KeyChar != '\0' && CompletionWidget != null && !CompletionWindowManager.IsVisible) { completionTokenSrc.Cancel(); CurrentCompletionContext = CompletionWidget.CurrentCodeCompletionContext; completionTokenSrc = new CancellationTokenSource(); var caretOffset = Editor.CaretOffset; var token = completionTokenSrc.Token; ITimeTracker timer = null; try { timer = CurrentCompletionContext.BeginTiming(); var task = DoHandleCodeCompletionAsync(CurrentCompletionContext, new CompletionTriggerInfo(CompletionTriggerReason.CharTyped, descriptor.KeyChar), token); if (task != null) { // Show the completion window in two steps. The call to PrepareShowWindow creates the window but // it doesn't show it. It is used only to process the keys while the completion data is being retrieved. CompletionWindowManager.PrepareShowWindow(this, descriptor.KeyChar, CompletionWidget, CurrentCompletionContext); EventHandler windowClosed = delegate(object o, EventArgs a) { completionTokenSrc.Cancel(); }; CompletionWindowManager.WindowClosed += windowClosed; task.ContinueWith(t => { try { CompletionWindowManager.WindowClosed -= windowClosed; if (token.IsCancellationRequested) { return; } var result = t.Result; if (result != null) { int triggerWordLength = result.TriggerWordLength + (Editor.CaretOffset - caretOffset); if (triggerWordLength > 0 && (triggerWordLength < Editor.CaretOffset || (triggerWordLength == 1 && Editor.CaretOffset == 1))) { CurrentCompletionContext = CompletionWidget.CreateCodeCompletionContext(Editor.CaretOffset - triggerWordLength); if (result.TriggerWordStart >= 0) { CurrentCompletionContext.TriggerOffset = result.TriggerWordStart; } CurrentCompletionContext.TriggerWordLength = triggerWordLength; } // Now show the window for real. if (!CompletionWindowManager.ShowWindow(result, CurrentCompletionContext)) { CurrentCompletionContext = null; } } else { CompletionWindowManager.HideWindow(); CurrentCompletionContext = null; } } finally { timer.End(); if (token.IsCancellationRequested) { completionStats.OnUserCanceled(timer.Duration); } else { completionStats.OnSuccess(timer.Duration); } } }, Runtime.MainTaskScheduler); } else { CurrentCompletionContext = null; timer.End(); completionStats.OnSuccess(timer.Duration); } } catch (TaskCanceledException) { timer.End(); completionStats.OnUserCanceled(timer.Duration); } catch (AggregateException) { timer.End(); completionStats.OnFailure(timer.Duration); } catch { timer.End(); completionStats.OnFailure(timer.Duration); throw; } } if ((descriptor.SpecialKey == SpecialKey.Delete || descriptor.SpecialKey == SpecialKey.BackSpace) && CompletionWidget != null && !CompletionWindowManager.IsVisible) { if (!char.IsLetterOrDigit(deleteOrBackspaceTriggerChar) && deleteOrBackspaceTriggerChar != '_') { return(res); } CurrentCompletionContext = CompletionWidget.CurrentCodeCompletionContext; int cpos, wlen; if (!GetCompletionCommandOffset(out cpos, out wlen)) { cpos = Editor.CaretOffset; wlen = 0; } CurrentCompletionContext.TriggerOffset = cpos; CurrentCompletionContext.TriggerWordLength = wlen; completionTokenSrc.Cancel(); completionTokenSrc = new CancellationTokenSource(); var caretOffset = Editor.CaretOffset; var token = completionTokenSrc.Token; ITimeTracker timer = null; try { timer = CurrentCompletionContext.BeginTiming(); var task = DoHandleCodeCompletionAsync(CurrentCompletionContext, new CompletionTriggerInfo(CompletionTriggerReason.BackspaceOrDeleteCommand, deleteOrBackspaceTriggerChar), token); if (task != null) { // Show the completion window in two steps. The call to PrepareShowWindow creates the window but // it doesn't show it. It is used only to process the keys while the completion data is being retrieved. CompletionWindowManager.PrepareShowWindow(this, descriptor.KeyChar, CompletionWidget, CurrentCompletionContext); EventHandler windowClosed = delegate(object o, EventArgs a) { completionTokenSrc.Cancel(); }; CompletionWindowManager.WindowClosed += windowClosed; task.ContinueWith(t => { try { CompletionWindowManager.WindowClosed -= windowClosed; if (token.IsCancellationRequested) { return; } var result = t.Result; if (result != null) { int triggerWordLength = result.TriggerWordLength + (Editor.CaretOffset - caretOffset); if (triggerWordLength > 0 && (triggerWordLength < Editor.CaretOffset || (triggerWordLength == 1 && Editor.CaretOffset == 1))) { CurrentCompletionContext = CompletionWidget.CreateCodeCompletionContext(Editor.CaretOffset - triggerWordLength); if (result.TriggerWordStart >= 0) { CurrentCompletionContext.TriggerOffset = result.TriggerWordStart; } CurrentCompletionContext.TriggerWordLength = triggerWordLength; } // Now show the window for real. if (!CompletionWindowManager.ShowWindow(result, CurrentCompletionContext)) { CurrentCompletionContext = null; } else { CompletionWindowManager.Wnd.StartOffset = CurrentCompletionContext.TriggerOffset; } } else { CompletionWindowManager.HideWindow(); CurrentCompletionContext = null; } } finally { timer.End(); if (token.IsCancellationRequested) { completionStats.OnUserCanceled(timer.Duration); } else { completionStats.OnSuccess(timer.Duration); } } }, Runtime.MainTaskScheduler); } else { CurrentCompletionContext = null; } } catch (TaskCanceledException) { CurrentCompletionContext = null; timer.End(); completionStats.OnUserCanceled(timer.Duration); } catch (AggregateException) { CurrentCompletionContext = null; timer.End(); completionStats.OnFailure(timer.Duration); } catch { timer.End(); completionStats.OnFailure(timer.Duration); throw; } } if (CompletionWidget != null && ParameterInformationWindowManager.CurrentMethodGroup == null) { CodeCompletionContext ctx = CompletionWidget.CurrentCodeCompletionContext; var newparameterHintingSrc = new CancellationTokenSource(); var token = newparameterHintingSrc.Token; try { var task = HandleParameterCompletionAsync(ctx, new SignatureHelpTriggerInfo(SignatureHelpTriggerReason.TypeCharCommand, descriptor.KeyChar), token); if (task != null) { parameterHintingSrc.Cancel(); parameterHintingSrc = newparameterHintingSrc; parameterHingtingCursorPositionChanged = false; task.ContinueWith(t => { if (!token.IsCancellationRequested && t.Result != null) { ParameterInformationWindowManager.ShowWindow(this, CompletionWidget, ctx, t.Result); if (parameterHingtingCursorPositionChanged) { ParameterInformationWindowManager.UpdateCursorPosition(this, CompletionWidget); } } }, token, TaskContinuationOptions.None, Runtime.MainTaskScheduler); } else { //Key was typed that was filtered out, no heavy processing will be performed(task==null) //but we still want to update ParameterInfo window to avoid displaying it outside method call parameterHingtingCursorPositionChanged = true; } } catch (TaskCanceledException) { } catch (AggregateException) { } } return(res); }