public override BlockState TryOpen(BlockProcessor processor)
        {
            var result = TryContinue(processor, null);

            if (result == BlockState.Continue)
            {
                // Save the column where we need to go back
                var column = processor.Column;

                // Unwind all indents all spaces before in order to calculate correct span
                processor.UnwindAllIndents();

                processor.NewBlocks.Push(new CodeBlock(this)
                {
                    Column = processor.Column,
                    Span   = new SourceSpan(processor.Start, processor.Line.End)
                });

                // Go back to the correct column
                processor.GoToColumn(column);
            }
            return(result);
        }
Exemple #2
0
        private BlockState TryParseListItem(BlockProcessor state, Block block)
        {
            var currentListItem = block as ListItemBlock;
            var currentParent   = block as ListBlock ?? (ListBlock)currentListItem?.Parent;

            // We can early exit if we have a code indent and we are either (1) not in a ListItem, (2) preceded by a blank line, (3) in an unordered list
            if (state.IsCodeIndent && (currentListItem is null || currentListItem.LastChild is BlankLineBlock || !currentParent.IsOrdered))
            {
                return(BlockState.None);
            }

            var initColumnBeforeIndent = state.ColumnBeforeIndent;
            var initColumn             = state.Column;
            var sourcePosition         = state.Start;
            var sourceEndPosition      = state.Line.End;

            var  c          = state.CurrentChar;
            var  itemParser = mapItemParsers[c];
            bool isOrdered  = itemParser is OrderedListItemParser;

            if (itemParser == null)
            {
                return(BlockState.None);
            }

            // Try to parse the list item
            ListInfo listInfo;

            if (!itemParser.TryParse(state, currentParent?.BulletType ?? '\0', out listInfo))
            {
                // Reset to an a start position
                state.GoToColumn(initColumn);
                return(BlockState.None);
            }

            // Gets the current character after a successful parsing of the list information
            c = state.CurrentChar;

            // Item starting with a blank line
            int columnWidth;

            // Do we have a blank line right after the bullet?
            if (c == '\0')
            {
                // Use a negative number to store the number of expected chars
                columnWidth = -(state.Column - initColumnBeforeIndent + 1);
            }
            else
            {
                if (!c.IsSpaceOrTab())
                {
                    state.GoToColumn(initColumn);
                    return(BlockState.None);
                }

                // Parse the following indent
                state.RestartIndent();
                var columnBeforeIndent = state.Column;
                state.ParseIndent();

                // We expect at most 4 columns after
                // If we have more, we reset the position
                if (state.Indent > 4)
                {
                    state.GoToColumn(columnBeforeIndent + 1);
                }

                // Number of spaces required for the following content to be part of this list item
                // If the list item starts with a blank line, the number of spaces
                // following the list marker doesn't change the required indentation
                columnWidth = (state.IsBlankLine ? columnBeforeIndent : state.Column) - initColumnBeforeIndent;
            }

            // Starts/continue the list unless:
            // - an empty list item follows a paragraph
            // - an ordered list is not starting by '1'
            var isPreviousParagraph = (block ?? state.LastBlock) is ParagraphBlock;

            if (isPreviousParagraph)
            {
                var isOpen = state.IsOpen(block ?? state.LastBlock);
                if (state.IsBlankLine || (isOpen && listInfo.BulletType == '1' && listInfo.OrderedStart != "1"))
                {
                    state.GoToColumn(initColumn);
                    return(BlockState.None);
                }
            }

            int.TryParse(listInfo.OrderedStart, out int order);
            var newListItem = new ListItemBlock(this)
            {
                Column      = initColumn,
                ColumnWidth = columnWidth,
                Order       = order,
                Span        = new SourceSpan(sourcePosition, sourceEndPosition)
            };

            state.NewBlocks.Push(newListItem);

            if (currentParent != null)
            {
                // If we have a new list item, close the previous one
                if (currentListItem != null)
                {
                    state.Close(currentListItem);
                }

                // Reset the list if it is a new list or a new type of bullet
                if (currentParent.IsOrdered != isOrdered ||
                    currentParent.OrderedDelimiter != listInfo.OrderedDelimiter ||
                    currentParent.BulletType != listInfo.BulletType)
                {
                    state.Close(currentParent);
                    currentParent = null;
                }
            }

            if (currentParent == null)
            {
                var newList = new ListBlock(this)
                {
                    Column              = initColumn,
                    Span                = new SourceSpan(sourcePosition, sourceEndPosition),
                    IsOrdered           = isOrdered,
                    BulletType          = listInfo.BulletType,
                    OrderedDelimiter    = listInfo.OrderedDelimiter,
                    DefaultOrderedStart = listInfo.DefaultOrderedStart,
                    OrderedStart        = listInfo.OrderedStart,
                };
                state.NewBlocks.Push(newList);
            }

            return(BlockState.Continue);
        }
Exemple #3
0
        private BlockState TryContinueListItem(BlockProcessor state, ListItemBlock listItem)
        {
            var list = (ListBlock)listItem.Parent;

            // Allow all blanks lines if the last block is a fenced code block
            // Allow 1 blank line inside a list
            // If > 1 blank line, terminate this list
            var isBlankLine = state.IsBlankLine;

            var isCurrentBlockBreakable = state.CurrentBlock != null && state.CurrentBlock.IsBreakable;

            if (isBlankLine)
            {
                if (isCurrentBlockBreakable)
                {
                    if (!(state.NextContinue is ListBlock))
                    {
                        list.CountAllBlankLines++;
                        listItem.Add(new BlankLineBlock());
                    }
                    list.CountBlankLinesReset++;
                }

                if (list.CountBlankLinesReset == 1 && listItem.ColumnWidth < 0)
                {
                    state.Close(listItem);

                    // Leave the list open
                    list.IsOpen = true;
                    return(BlockState.Continue);
                }

                // Update list-item source end position
                listItem.UpdateSpanEnd(state.Line.End);

                return(BlockState.Continue);
            }

            list.CountBlankLinesReset = 0;

            int columnWidth = listItem.ColumnWidth;

            if (columnWidth < 0)
            {
                columnWidth = -columnWidth;
            }

            if (state.Indent >= columnWidth)
            {
                if (state.Indent > columnWidth && state.IsCodeIndent)
                {
                    state.GoToColumn(state.ColumnBeforeIndent + columnWidth);
                }

                // Update list-item source end position
                listItem.UpdateSpanEnd(state.Line.End);

                return(BlockState.Continue);
            }

            return(BlockState.None);
        }
Exemple #4
0
        public override BlockState TryOpen(BlockProcessor processor)
        {
            // If we are in a CodeIndent, early exit
            if (processor.IsCodeIndent)
            {
                return(BlockState.None);
            }

            // 4.2 ATX headings
            // An ATX heading consists of a string of characters, parsed as inline content,
            // between an opening sequence of 1–6(configurable) unescaped # characters and an optional
            // closing sequence of any number of unescaped # characters. The opening sequence
            // of # characters must be followed by a space or by the end of line. The optional
            // closing sequence of #s must be preceded by a space and may be followed by spaces
            // only. The opening # character may be indented 0-3 spaces. The raw contents of
            // the heading are stripped of leading and trailing spaces before being parsed as
            // inline content. The heading level is equal to the number of # characters in the
            // opening sequence.
            var column         = processor.Column;
            var line           = processor.Line;
            var sourcePosition = line.Start;
            var c            = line.CurrentChar;
            var matchingChar = c;

            Debug.Assert(MaxLeadingCount > 0);
            int leadingCount = 0;

            while (c != '\0' && leadingCount <= MaxLeadingCount)
            {
                if (c != matchingChar)
                {
                    break;
                }
                c = line.NextChar();
                leadingCount++;
            }

            // A space is required after leading #
            if (leadingCount > 0 && leadingCount <= MaxLeadingCount && (c.IsSpaceOrTab() || c == '\0'))
            {
                // Move to the content
                var headingBlock = new HeadingBlock(this)
                {
                    HeaderChar = matchingChar,
                    Level      = leadingCount,
                    Column     = column,
                    Span       = { Start = sourcePosition }
                };
                processor.NewBlocks.Push(headingBlock);
                processor.GoToColumn(column + leadingCount + 1);

                // Gives a chance to parse attributes
                if (TryParseAttributes != null)
                {
                    TryParseAttributes(processor, ref processor.Line, headingBlock);
                }

                // The optional closing sequence of #s must be preceded by a space and may be followed by spaces only.
                int endState         = 0;
                int countClosingTags = 0;
                for (int i = processor.Line.End; i >= processor.Line.Start - 1; i--)  // Go up to Start - 1 in order to match the space after the first ###
                {
                    c = processor.Line.Text[i];
                    if (endState == 0)
                    {
                        if (c.IsSpaceOrTab())
                        {
                            continue;
                        }
                        endState = 1;
                    }
                    if (endState == 1)
                    {
                        if (c == matchingChar)
                        {
                            countClosingTags++;
                            continue;
                        }

                        if (countClosingTags > 0)
                        {
                            if (c.IsSpaceOrTab())
                            {
                                processor.Line.End = i - 1;
                            }
                            break;
                        }
                        else
                        {
                            break;
                        }
                    }
                }

                // Setup the source end position of this element
                headingBlock.Span.End = processor.Line.End;

                // We expect a single line, so don't continue
                return(BlockState.Break);
            }

            // Else we don't have an header
            return(BlockState.None);
        }
Exemple #5
0
        private BlockState MatchEnd(BlockProcessor state, HtmlBlock htmlBlock)
        {
            state.GoToColumn(state.ColumnBeforeIndent);

            // Early exit if it is not starting by an HTML tag
            var line   = state.Line;
            var result = BlockState.Continue;
            int index;

            switch (htmlBlock.Type)
            {
            case HtmlBlockType.Comment:
                index = line.IndexOf(EndOfComment);
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + EndOfComment.Length);
                    result = BlockState.Break;
                }
                break;

            case HtmlBlockType.CData:
                index = line.IndexOf(EndOfCDATA);
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + EndOfCDATA.Length);
                    result = BlockState.Break;
                }
                break;

            case HtmlBlockType.ProcessingInstruction:
                index = line.IndexOf(EndOfProcessingInstruction);
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + EndOfProcessingInstruction.Length);
                    result = BlockState.Break;
                }
                break;

            case HtmlBlockType.DocumentType:
                index = line.IndexOf('>');
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + 1);
                    result = BlockState.Break;
                }
                break;

            case HtmlBlockType.ScriptPreOrStyle:
                index = line.IndexOf("</script>", 0, true);
                if (index >= 0)
                {
                    htmlBlock.UpdateSpanEnd(index + "</script>".Length);
                    result = BlockState.Break;
                }
                else
                {
                    index = line.IndexOf("</pre>", 0, true);
                    if (index >= 0)
                    {
                        htmlBlock.UpdateSpanEnd(index + "</pre>".Length);
                        result = BlockState.Break;
                    }
                    else
                    {
                        index = line.IndexOf("</style>", 0, true);
                        if (index >= 0)
                        {
                            htmlBlock.UpdateSpanEnd(index + "</style>".Length);
                            result = BlockState.Break;
                        }
                    }
                }
                break;

            case HtmlBlockType.InterruptingBlock:
                if (state.IsBlankLine)
                {
                    result = BlockState.BreakDiscard;
                }
                break;

            case HtmlBlockType.NonInterruptingBlock:
                if (state.IsBlankLine)
                {
                    result = BlockState.BreakDiscard;
                }
                break;
            }

            // Update only if we don't have a break discard
            if (result != BlockState.BreakDiscard)
            {
                htmlBlock.Span.End = line.End;
            }

            return(result);
        }