static BTLGUILine GenerateCommentLineGui(string lineContent, int indent, int lineNumber)
        {
            var commentGUILine = new BTLGUILine();

            commentGUILine.indent     = indent;
            commentGUILine.lineNumber = lineNumber;
            string tabs = "";

            for (int i = 0; i < indent; ++i)
            {
                tabs += "\t";
            }

            string cleaned = lineContent;

            if (tabs != "")
            {
                cleaned = cleaned.Replace(tabs, "");
            }

            cleaned = cleaned.Replace("\t", "    ");

            var token = GenerateCommentToken(cleaned, lineNumber);

            commentGUILine.tokens.Add(token);
            return(commentGUILine);
        }
        private void GUI_tokens(BTLGUILine line)
        {
            GUIStyle style = BTLSyntaxHighlight.style_label;

            for (int i = 0; i < line.tokens.Count; ++i)
            {
                BTNode node = null;
                if (i < line.btnodes.Count)
                {
                    node = line.btnodes[i];
                }

                var token = line.tokens[i];
                style = BTLSyntaxHighlight.GetTokenStyle(token);

                if (bt.program == null || bt.program.codemaps == null)
                {
                    style = BTLSyntaxHighlight.style_comment;
                }

                if (bt.exceptions.Length > 0)
                {
                    style = BTLSyntaxHighlight.style_comment;
                }

                if (line.hasErrors)
                {
                    style = BTLSyntaxHighlight.style_failed;
                }

                GUI_token(style, node, token);
            }// for tokens
        }
        // Map each token to its corresponding node.
        public static void MapNodes(BTLGUILine line, CodeMap codemap)
        {
            line.btnodes.Clear();
            for (int t = 0; t < line.tokens.Count; ++t)
            {
                line.btnodes.Add(null);
            }

            for (int t = 0; t < line.tokens.Count; ++t)
            {
                var token = line.tokens[t];

                if (token.type == BTLTokenizer.TokenType.Comment)
                {
                    continue;
                }

                for (int n = 0; n < codemap.nodes.Length; ++n)
                {
                    var node    = codemap.nodes[n];
                    var nodeLoc = codemap.substringLocations[n];

                    if (
                        nodeLoc.start <= token.substring_start &&
                        (token.substring_start + token.substring_length) <= (nodeLoc.start + nodeLoc.length)
                        )
                    {
                        line.btnodes[t] = node;
                        break;
                    }
                }
            }
        }
        void RenderLine(BTLGUILine line)
        {
            GUILayout.BeginHorizontal();
            GUI_lineNumber(line);
            GUI_indentation(line);

            if (bt.program != null && bt.program.codemaps != null && SourceDisplay.isPlaying && bt.program.exceptions.Length == 0)
            {
                GUI_tokens_live(line);
            }
            else
            {
                GUI_tokens(line);
            }

            GUILayout.EndHorizontal();
        }
        private void GUI_tokens_live(BTLGUILine line)
        {
            GUIStyle style = BTLSyntaxHighlight.style_label;

            for (int i = 0; i < line.tokens.Count; ++i)
            {
                BTNode node = null;
                if (i < line.btnodes.Count)
                {
                    node = line.btnodes[i];
                }

                var token = line.tokens[i];
                style = BTLSyntaxHighlight.GetTokenStyle(token);

                if (node != null)
                {
                    var nodeStyle = GetNodeStyle(node);
                    if (nodeStyle != null)
                    {
                        style = nodeStyle;
                    }
                }

                if (bt.exceptions.Length > 0)
                {
                    style = BTLSyntaxHighlight.style_comment;
                }

                if (line.hasErrors)
                {
                    style = BTLSyntaxHighlight.style_failed;
                }

                GUI_token(style, node, token);

                // debug info
                bool isLastNodeToken = node != null && (i + 1 >= line.btnodes.Count || node != line.btnodes[i + 1]);
                if (isLastNodeToken && node.debugInfo != null && node.debugInfo != "")
                {
                    GUILayout.Label(string.Format("[{0}]", node.debugInfo.Replace("\t", "   ")), BTLSyntaxHighlight.style_comment);
                }
            }// for tokens
        }
        private bool HasBeenTickedOnThisFrame(BTLGUILine line)
        {
            bool hasBeenTickedOnThisFrame = false;

            if (line.btnodes != null)
            {
                foreach (var n in line.btnodes)
                {
                    hasBeenTickedOnThisFrame = n != null && bt.program != null &&
                                               bt.status != Status.Ready &&
                                               n.lastTick == bt.program.tickCount;
                    if (hasBeenTickedOnThisFrame)
                    {
                        break;
                    }
                }
            }
            return(hasBeenTickedOnThisFrame);
        }
        private static bool ContainsLeafNodes(BTLGUILine line)
        {
            bool containsLeafNode = false;

            if (line.btnodes != null)
            {
                foreach (var n in line.btnodes)
                {
                    if (n == null)
                    {
                        continue;
                    }
                    if (n.GetType() == typeof(BTTask) || n.GetType() == typeof(BTTreeProxy))
                    {
                        containsLeafNode = true;
                        break;
                    }
                }
            }

            return(containsLeafNode);
        }
        private static void GUI_indentation(BTLGUILine line)
        {
            string strIndent = "";

            for (int i = 0; i < line.indent + (line.isFoldable ? 0 : 1); i++)
            {
                strIndent += "    ";
            }
            {
                //style.normal.textColor = hiddenColor;
                GUILayout.Label(strIndent, BTLSyntaxHighlight.style_label);
            }

            //style.normal.textColor = BTLSyntaxHighlight.guiColor;
            if (line.isFoldable)
            {
                // line.isFoldout = GUILayout.Toggle(line.isFoldout, line.isFoldout ? "[+]" : "[-]", style);
                if (GUILayout.Button("    ", line.isFoldout ? BTLSyntaxHighlight.style_INFoldout : BTLSyntaxHighlight.style_INFoldin))
                {
                    line.isFoldout = !line.isFoldout;
                }
            }
        }
        private static void ParentOrAddToLines(List <BTLGUILine> lines, Stack <BTLGUILine> parentStack, BTLGUILine current)
        {
            if (current.tokens.Count > 0)
            {
                while (parentStack.Count > 0 && parentStack.Peek().indent >= current.indent)
                {
                    parentStack.Pop();
                }
            }

            if (parentStack.Count > 0 && !parentStack.Peek().children.Contains(current))
            {
                parentStack.Peek().children.Add(current);
            }
            else
            {
                if (!lines.Contains(current))
                {
                    lines.Add(current);
                }
            }
        }
        public static SourceDisplay Analyse(BTLTokenizer.Token[] tokens, int sourceIndex)
        {
            if (tokens == null)
            {
                return(null);
            }

            SourceDisplay sourceDisplay = new SourceDisplay(sourceIndex);
            int           lineNumber    = 1;
            var           lines         = new List <BTLGUILine>();
            var           orphans       = new List <BTLGUILine>();
            var           parentStack   = new Stack <BTLGUILine>();

            BTLGUILine current = new BTLGUILine();

            current.lineNumber = lineNumber;
            foreach (var t in tokens)
            {
                bool isComment = current.tokens.Count > 0 && current.tokens[0].type == BTLTokenizer.TokenType.Comment;

                if (t.type == BTLTokenizer.TokenType.EOL && current.tokens.Count == 0)
                {// Empty line
                    orphans.Add(current);
                    ++lineNumber;
                    current            = new BTLGUILine();
                    current.indent     = 0;
                    current.lineNumber = lineNumber;
                }
                else if (t.type == BTLTokenizer.TokenType.EOL && isComment)
                {// End of comments
                    ++lineNumber;
                    orphans.Add(current);
                    current            = new BTLGUILine();
                    current.indent     = 0;
                    current.lineNumber = lineNumber;
                }
                else if (t.type == BTLTokenizer.TokenType.EOL && !isComment)
                {// End of line containing nodes
                    ++lineNumber;
                    ParentOrAddToLines(lines, parentStack, current);

                    if (current.tokens.Count > 0)
                    {
                        parentStack.Push(current);
                    }


                    current            = new BTLGUILine();
                    current.indent     = 0;
                    current.lineNumber = lineNumber;
                }
                else if (t.type == BTLTokenizer.TokenType.Indent && current.tokens.Count == 0)
                {
                    current.indent++;
                }
                else if (t.type == BTLTokenizer.TokenType.Comment)
                {
                    var commentLines = t.content.Split('\n');
                    if (commentLines.Length > 0)
                    {
                        current.tokens.Add(GenerateCommentToken(commentLines[0], current.lineNumber));
                        for (int i = 1; i < commentLines.Length; ++i)
                        {
                            ++lineNumber;
                            var commentline    = commentLines[i];
                            var commentGUILine = GenerateCommentLineGui(commentline, current.indent, lineNumber);
                            current.children.Add(commentGUILine);
                        }
                    }
                }
                else
                {
                    current.tokens.Add(t);
                }
            }

            ParentOrAddToLines(lines, parentStack, current);


            sourceDisplay.lines   = lines.ToArray();
            sourceDisplay.orphans = orphans.ToArray();

            ProcessFoldable(sourceDisplay.lines);
            ProcessFoldable(sourceDisplay.orphans);


            return(sourceDisplay);
        }
        public static SourceDisplay[] MapGUILines(BTSource[] btlSources, BTProgram program, PandaScriptException[] pandaExceptions)
        {
            if (btlSources == null || program == null)
            {
                return(null);
            }

            var sourceDisplays = new SourceDisplay[btlSources.Length];

            for (int i = 0; i < btlSources.Length; i++)
            {
                if (btlSources[i] == null)
                {
                    continue;
                }

                SourceDisplay sourceDisplay = null;
                var           tokens        = BTLAssetManager.GetBTLTokens(btlSources, btlSources[i]);
                sourceDisplay = BTLGUILine.Analyse(tokens, i);

                sourceDisplays[i] = sourceDisplay;

                CodeMap codemap = null;
                if (program.codemaps != null && i < program.codemaps.Length)
                {
                    codemap = program.codemaps[i];
                }

                if (codemap != null)
                {
                    BTLGUILine.MapNodes(sourceDisplay.lines, codemap);

                    var lines = sourceDisplay.flattenLines;
                    foreach (var line in lines)
                    {
                        foreach (var n in line.btnodes)
                        {
                            var task = n as BTTask;
                            if (task != null && task.boundState != BoundState.Bound)
                            {
                                line.hasErrors = true;
                            }
                        }
                    }
                }

                if (sourceDisplay != null)
                {
                    var lines = sourceDisplay.flattenLines;
                    foreach (var line in lines)
                    {
                        foreach (var pandaException in pandaExceptions)
                        {
                            if (pandaException != null)
                            {
                                if (pandaException.filePath == btlSources[i].url && line.lineNumber == pandaException.lineNumber)
                                {
                                    line.hasErrors = true;
                                }
                            }
                        }
                    }
                }
            }

            return(sourceDisplays);
        }
        private void GUI_lineNumber(BTLGUILine line)
        {
            // Determine whether the lines contains a node that has been ticked on this frame.
            bool hasBeenTickedOnThisFrame = HasBeenTickedOnThisFrame(line);
            bool containsLeafNode         = hasBeenTickedOnThisFrame ? ContainsLeafNodes(line) : false;

            var lineNumberStyle = BTLSyntaxHighlight.style_lineNumber;

#if !PANDA_BT_FREE
            bool isActive = hasBeenTickedOnThisFrame && (containsLeafNode || line.isFoldout) && !line.isBreakPointEnable;
#else
            bool isActive = hasBeenTickedOnThisFrame && (containsLeafNode || line.isFoldout);
#endif


            if (isActive)
            {
                lineNumberStyle = BTLSyntaxHighlight.style_lineNumber_active;
            }
#if !PANDA_BT_FREE
            if (line.isBreakPointEnable)
            {
                if (hasBeenTickedOnThisFrame && isPaused && line.isBreakPointActive)
                {
                    lineNumberStyle = BTLSyntaxHighlight.style_breakpoint_active;
                }
                else
                {
                    switch (line.breakPointStatus)
                    {
                    case Status.Running:
                        lineNumberStyle = BTLSyntaxHighlight.style_breakpoint_set_running;
                        break;

                    case Status.Succeeded:
                        lineNumberStyle = BTLSyntaxHighlight.style_breakpoint_set_succeeded;
                        break;

                    case Status.Failed:
                        lineNumberStyle = BTLSyntaxHighlight.style_breakpoint_set_failed;
                        break;

                    default:
                        lineNumberStyle = BTLSyntaxHighlight.style_breakpoint_set_running;
                        break;
                    }
                }
            }
#endif
            if (line.hasErrors)
            {
                lineNumberStyle = BTLSyntaxHighlight.style_lineNumber_error;
            }



#if UNITY_EDITOR && !PANDA_BT_FREE
            if (line.btnodes != null && line.btnodes.Count > 0)
            {
                if (GUILayout.Button(line.lineNumberText, lineNumberStyle))
                {
                    if (Event.current.button == 0)
                    {
                        line.ToggleBreakPoint();
                    }
                    else if (Event.current.button == 1)
                    {
                        line.ClearBreakPoint();
                    }

                    var  breakPoints        = bt.sourceInfos[scriptIndex].breakPoints;
                    var  breakPointStatuses = bt.sourceInfos[scriptIndex].breakPointStatuses;
                    bool isDirty            = true;
                    while (isDirty)
                    {
                        isDirty = false;
                        for (int i = 0; i < breakPoints.Count; ++i)
                        {
                            if (breakPoints[i] == line.lineNumber)
                            {
                                breakPoints.RemoveAt(i);
                                breakPointStatuses.RemoveAt(i);
                                isDirty = true;
                                break;
                            }
                        }
                    }

                    if (line.isBreakPointEnable)
                    {
                        breakPoints.Add(line.lineNumber);
                        breakPointStatuses.Add(line.breakPointStatus);
                    }
                }
            }
            else
            {
                GUILayout.Label(line.lineNumberText, lineNumberStyle);
            }
#else
            GUILayout.Label(line.lineNumberText, lineNumberStyle);
#endif
        }