/// <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> /// Gets all the <see cref="ClassificationSpan"/> objects that intersect with the given range of text. /// </summary> /// <param name="span">The span currently being classified.</param> /// <returns>A list of ClassificationSpans that represent spans identified to be of this classification.</returns> public IList <ClassificationSpan> GetClassificationSpans(SnapshotSpan span) { // Initialize result list List <ClassificationSpan> spans = new List <ClassificationSpan>(); // Get tokens beginning with the start of the first line of the given span (to avoid starting within a word) foreach (AiToken token in _aiParser.GetTokens(span.Start.GetContainingLine().Start)) { // Finished? if (token.StartPoint >= span.End) { break; } // Classify depending on token type string classifierType = AiClassifierTypes.Default; switch (token.Type) { case AiTokenTypes.Comment: classifierType = AiClassifierTypes.Comment; break; case AiTokenTypes.Defrule: case AiTokenTypes.BooleanFactName: case AiTokenTypes.RuleArrow: case AiTokenTypes.Defconst: case AiTokenTypes.Load: case AiTokenTypes.LoadRandom: classifierType = AiClassifierTypes.Keyword; break; case AiTokenTypes.OpeningBrace: case AiTokenTypes.ClosingBrace: classifierType = AiClassifierTypes.Delimiter; break; case AiTokenTypes.String: classifierType = AiClassifierTypes.String; break; case AiTokenTypes.Number: classifierType = AiClassifierTypes.Number; break; case AiTokenTypes.FactName: classifierType = AiClassifierTypes.RuleFactName; break; case AiTokenTypes.ActionName: classifierType = AiClassifierTypes.RuleActionName; break; case AiTokenTypes.Word: if (Constants.AiOperators.Contains(token.Content)) { classifierType = AiClassifierTypes.Operator; } else if (Constants.AiIdentifiers.Contains(token.Content)) { classifierType = AiClassifierTypes.Identifier; } else { classifierType = AiClassifierTypes.Default; } break; } spans.Add(new ClassificationSpan(new SnapshotSpan(token.StartPoint, token.Length), _classificationTypeRegistry.GetClassificationType(classifierType))); } /*// Get first and last line, use these to get the whole text in between (including line breaks) * var startLine = span.Start.GetContainingLine(); * var endLine = (span.End - 1).GetContainingLine(); * var text = span.Snapshot.GetText(new SnapshotSpan(startLine.Start, endLine.End)); * * // Step through text char by char * int lastClassificationEndIndex = 0; * for(int charIndex = 0; charIndex < text.Length; ++charIndex) * { * // Check current character * if(text[charIndex] == ';') * { * // Move unrecognized content or whitespace before this position into default class * if(charIndex > lastClassificationEndIndex) * spans.Add(CreateClassificationSpan(span, lastClassificationEndIndex, charIndex - lastClassificationEndIndex, AiClassifierTypes.Default)); * * // The remainder of the line is a comment * var currentLine = span.Snapshot.GetLineFromPosition(startLine.Start.Position + charIndex); * int remainingCharCount = currentLine.EndIncludingLineBreak.Position - (startLine.Start.Position + charIndex); * spans.Add(CreateClassificationSpan(span, charIndex, remainingCharCount, AiClassifierTypes.Comment)); * charIndex += remainingCharCount; * lastClassificationEndIndex = charIndex + 1; * } * else if(text[charIndex] == '\r' || text[charIndex] == '\n') * { * // Update indices to next line (maybe with another intermediate line break character) * lastClassificationEndIndex = charIndex + 1; * } * else if(Constants.AiDelimiters.Any(c => c == text[charIndex])) * { * // Move unrecognized content or whitespace before this position into default class * if(charIndex > lastClassificationEndIndex) * spans.Add(CreateClassificationSpan(span, lastClassificationEndIndex, charIndex - lastClassificationEndIndex, AiClassifierTypes.Default)); * * // The current char is a delimiter * spans.Add(CreateClassificationSpan(span, charIndex, 1, AiClassifierTypes.Delimiter)); * lastClassificationEndIndex = charIndex + 1; * } * else if(text[charIndex] == '"') * { * // Move unrecognized content or whitespace before this position into default class * if(charIndex > lastClassificationEndIndex) * spans.Add(CreateClassificationSpan(span, lastClassificationEndIndex, charIndex - lastClassificationEndIndex, AiClassifierTypes.Default)); * * // Search for string termination character * int stringBeginIndex = charIndex; * var currentLine = span.Snapshot.GetLineFromPosition(startLine.Start.Position + charIndex); * while(startLine.Start.Position + charIndex < currentLine.End.Position - 1) * if(text[++charIndex] == '"') * break; * * // Set string class * spans.Add(CreateClassificationSpan(span, stringBeginIndex, charIndex - stringBeginIndex + 1, AiClassifierTypes.String)); * lastClassificationEndIndex = charIndex + 1; * } * else if(char.IsDigit(text[charIndex])) * { * // Move unrecognized content or whitespace before this position into default class * if(charIndex > lastClassificationEndIndex) * spans.Add(CreateClassificationSpan(span, lastClassificationEndIndex, charIndex - lastClassificationEndIndex, AiClassifierTypes.Default)); * * // Search for last number character * int numberBeginIndex = charIndex; * var currentLine = span.Snapshot.GetLineFromPosition(startLine.Start.Position + charIndex); * while(startLine.Start.Position + charIndex < currentLine.End.Position - 1) * if(!char.IsDigit(text[++charIndex])) * break; * * // Ignore delimiters * if(Constants.AiDelimiters.Any(c => c == text[charIndex])) * --charIndex; * * // The last character must be a digit (occurs if a delimiter follows), whitespace or span end * if(char.IsDigit(text[charIndex]) || char.IsWhiteSpace(text[charIndex]) || startLine.Start.Position + charIndex == currentLine.End.Position - 1) * { * // Set number class * spans.Add(CreateClassificationSpan(span, numberBeginIndex, charIndex - numberBeginIndex + 1, AiClassifierTypes.Number)); * } * else * { * // Use default class * spans.Add(CreateClassificationSpan(span, numberBeginIndex, charIndex - numberBeginIndex + 1, AiClassifierTypes.Default)); * } * lastClassificationEndIndex = charIndex + 1; * } * else if(text[charIndex] == '=' && charIndex < text.Length-1 && text[charIndex + 1] == '>') * { * // Move unrecognized content or whitespace before this position into default class * if(charIndex > lastClassificationEndIndex) * spans.Add(CreateClassificationSpan(span, lastClassificationEndIndex, charIndex - lastClassificationEndIndex, AiClassifierTypes.Default)); * * // Set as keyword (must be handled separately from the other keywords, since this one might conflict with the operators) * spans.Add(CreateClassificationSpan(span, charIndex, 2, AiClassifierTypes.Keyword)); ++charIndex; * lastClassificationEndIndex = charIndex + 1; * } * else if(!char.IsWhiteSpace(text[charIndex])) * { * // Move unrecognized content or whitespace before this position into default class * if(charIndex > lastClassificationEndIndex) * spans.Add(CreateClassificationSpan(span, lastClassificationEndIndex, charIndex - lastClassificationEndIndex, AiClassifierTypes.Default)); * * // Search for whitespace or delimiter * int wordBeginIndex = charIndex; * var currentLine = span.Snapshot.GetLineFromPosition(startLine.Start.Position + charIndex); * while(startLine.Start.Position + charIndex < currentLine.End.Position - 1) * if(char.IsWhiteSpace(text[++charIndex]) || Constants.AiDelimiters.Any(c => c == text[charIndex])) * { * // Ignore this char * --charIndex; * break; * } * * // Classify this word * string word = text.Substring(wordBeginIndex, charIndex - wordBeginIndex + 1); * if(Constants.AiKeywords.Contains(word)) * spans.Add(CreateClassificationSpan(span, wordBeginIndex, charIndex - wordBeginIndex + 1, AiClassifierTypes.Keyword)); * else if(Constants.AiOperators.Contains(word)) * spans.Add(CreateClassificationSpan(span, wordBeginIndex, charIndex - wordBeginIndex + 1, AiClassifierTypes.Operator)); * else if(Constants.AiCommands.Contains(word)) * spans.Add(CreateClassificationSpan(span, wordBeginIndex, charIndex - wordBeginIndex + 1, AiClassifierTypes.Command)); * else if(Constants.AiIdentifiers.Contains(word)) * spans.Add(CreateClassificationSpan(span, wordBeginIndex, charIndex - wordBeginIndex + 1, AiClassifierTypes.Identifier)); * else * { * // Use default class * spans.Add(CreateClassificationSpan(span, wordBeginIndex, charIndex - wordBeginIndex + 1, AiClassifierTypes.Default)); * } * lastClassificationEndIndex = charIndex + 1; * } * } * * // Classify remaining whitespace * int remainingCharCountTillEnd = endLine.End.Position - (startLine.Start.Position + lastClassificationEndIndex); * if(remainingCharCountTillEnd > 0) * spans.Add(CreateClassificationSpan(span, lastClassificationEndIndex, remainingCharCountTillEnd, AiClassifierTypes.Default));*/ // Return classication list return(spans); }
/// <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; } }