예제 #1
0
        /// <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();
            }
        }