/// <summary> /// Returns the indentation for the given line. /// </summary> /// <param name="indentLine">The line to be indented.</param> /// <returns></returns> public int?GetDesiredIndentation(ITextSnapshotLine indentLine) { // Make sure there is a line before the requested line if (indentLine.LineNumber == 0 || indentLine.Start.Position < 1) { return(0); } // Get indentation of previous line string previousLineText = indentLine.Snapshot.GetLineFromLineNumber(indentLine.LineNumber - 1).GetText(); int previousLineIndentation = 0; while (previousLineIndentation < previousLineText.Length && char.IsWhiteSpace(previousLineText[previousLineIndentation])) { ++previousLineIndentation; } // Find first token before this line var tokens = _aiParser.GetTokens(indentLine.Start - 1, true); if (!tokens.Any()) { return(0); } AiToken firstToken = tokens.First(); switch (firstToken.Type) { case AiTokenTypes.ClosingBrace: return(previousLineIndentation); default: return(previousLineIndentation + (_useTabs ? 4 : _indentSize)); // TODO _useTabs is always true, 4 is hardcoded as long as this is not fixed } }
/// <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; } }
/// <summary> /// Formats the given span. /// </summary> /// <param name="span">The span to be formatted.</param> private void FormatSpan(SnapshotSpan span) { // TODO this is hardcoded for spaces (not tabs) // Always format whole lines ITextSnapshot snapshot = span.Snapshot; SnapshotPoint currentPoint = span.Start.GetContainingLine().Start; SnapshotPoint endPoint = span.End.GetContainingLine().End; // Get indentation of previous line, if there is one int currentIndentLevel = 0; if (currentPoint.GetContainingLine().LineNumber > 0) { // Count whitespace at line start string previousLineText = snapshot.GetLineFromLineNumber(currentPoint.GetContainingLine().LineNumber - 1).GetText(); while (currentIndentLevel < previousLineText.Length && char.IsWhiteSpace(previousLineText[currentIndentLevel])) { ++currentIndentLevel; } } // Get tokens var tokens = _aiParser.GetTokens(currentPoint); if (!tokens.Any()) { return; } tokens.SkipWhile(t => t.Type == AiTokenTypes.Comment); // Keep track of all modifications List <SnapshotModification> modifications = new List <SnapshotModification>(); // Set indentation of first token AiToken lastNonCommentToken = tokens.First(); AiToken lastToken = tokens.First(); foreach (AiToken currentToken in tokens.Skip(1)) { // Compute decision for the current token FormatDecision decision = FormatDecision.Keep; int indentOffset = 0; // The number of levels the indentation shall be changed switch (currentToken.Type) { case AiTokenTypes.OpeningBrace: { // Always put into a new line decision = FormatDecision.NewLineIndent; if (lastNonCommentToken.Type == AiTokenTypes.OpeningBrace || lastNonCommentToken.Type == AiTokenTypes.Defrule || lastNonCommentToken.Type == AiTokenTypes.BooleanFactName || lastNonCommentToken.Type == AiTokenTypes.RuleArrow) { indentOffset = 4; } break; } case AiTokenTypes.ClosingBrace: { // If the last token was a closing brace, then break and indent one level less than the last one, else remove any space if (lastNonCommentToken.Type == AiTokenTypes.ClosingBrace) { decision = FormatDecision.NewLineIndent; indentOffset = -4; } else if (lastToken.Type != AiTokenTypes.Comment) { decision = FormatDecision.NoSpace; } else { decision = FormatDecision.NewLineIndent; } break; } case AiTokenTypes.Defrule: case AiTokenTypes.Defconst: case AiTokenTypes.Load: case AiTokenTypes.LoadRandom: { // These tokens are always at line begin, so manipulate current indentation level variable accordingly // This works around errors like forgotten closing braces indentOffset = -currentIndentLevel; // If there is no brace before the current token (skipping comments), an error must have occured, or the selected area starts with comments if (lastNonCommentToken.Type != AiTokenTypes.OpeningBrace) { decision = FormatDecision.NewLineIndent; } else { decision = FormatDecision.NoSpace; } break; } case AiTokenTypes.BooleanFactName: case AiTokenTypes.FactName: case AiTokenTypes.ActionName: { // Attach the fact name to the leading brace, or add a space after a preceding UserPatch fact name if (lastNonCommentToken.Type == AiTokenTypes.OpeningBrace) { decision = FormatDecision.NoSpace; } else if (lastNonCommentToken.Type == AiTokenTypes.FactName) { decision = FormatDecision.Space; } else { decision = FormatDecision.NewLineIndent; } break; } case AiTokenTypes.RuleArrow: { // Remove any indentation indentOffset = -currentIndentLevel; decision = FormatDecision.NewLineIndent; break; } case AiTokenTypes.Number: case AiTokenTypes.String: case AiTokenTypes.Word: { // Use new line if there is a comment before, else insert just a space // If we have some unrecognized fact or action with an opening brace in the front, then do not insert any space if (lastToken.Type == AiTokenTypes.Comment) { decision = FormatDecision.NewLineIndent; } else if (lastToken.Type == AiTokenTypes.OpeningBrace) { decision = FormatDecision.NoSpace; } else { decision = FormatDecision.Space; } break; } case AiTokenTypes.Comment: { // Do not touch comments decision = FormatDecision.Keep; break; } } // Use formatting decision to create a modification int lastTokenEndPosition = lastToken.StartPoint.Position + lastToken.Length; Span spanBetweenLastAndCurrentToken = new Span(lastTokenEndPosition, currentToken.StartPoint.Position - lastTokenEndPosition); switch (decision) { case FormatDecision.Keep: { // Do not do anything break; } case FormatDecision.NewLineIndent: { // Create new line and indentation between the last and the current token currentIndentLevel += indentOffset; if (currentIndentLevel < 0) { currentIndentLevel = 0; } modifications.Add(new SnapshotModification(spanBetweenLastAndCurrentToken, "\r\n" + new string(' ', currentIndentLevel))); break; } case FormatDecision.NoSpace: { // Set no space modifications.Add(new SnapshotModification(spanBetweenLastAndCurrentToken, "")); break; } case FormatDecision.Space: { // Set exactly one space modifications.Add(new SnapshotModification(spanBetweenLastAndCurrentToken, " ")); break; } } // Remember current token if (currentToken.Type != AiTokenTypes.Comment) { lastNonCommentToken = currentToken; } lastToken = currentToken; } // Apply changes using (var edit = _textView.TextBuffer.CreateEdit()) { // Go through modifications and apply them foreach (SnapshotModification modification in modifications) { edit.Replace(modification.ReplacedSpan, modification.Replacement); } // Execute edit edit.Apply(); } }