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); }
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); }
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); }
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); }
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); }