/*******************************************************************************
* Copyright (c) 2013, 2014 Stefan Seelmann and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Stefan Seelmann - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.wikitext.markdown.internal.block;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.mylyn.wikitext.markdown.internal.util.LookAheadReader;
import org.eclipse.mylyn.wikitext.parser.Attributes;
import org.eclipse.mylyn.wikitext.parser.DocumentBuilder.BlockType;
/**
* Markdown lists.
*
* @author Stefan Seelmann
*/
public class ListBlock extends NestableBlock {
private static final Pattern itemStartPattern = Pattern.compile(" {0,3}(?:([\\*\\+\\-])|([0-9]+\\.))\\s+(.+?)"); //$NON-NLS-1$
private static final Pattern nestedItemStartPattern = Pattern.compile("( +)(?:([\\*\\+\\-])|([0-9]+\\.))\\s+(.+?)"); //$NON-NLS-1$
private static final Pattern indentedParagraphPattern = Pattern.compile("( +).*"); //$NON-NLS-1$
private int blockLineCount = 0;
private ListBlock nestedBlock = null;
private ParagraphBlock nestedParagraph = null;
private int thisIndentation = 0;
private boolean nextLineStartsNewParagraph = false;
@Override
public boolean canStart(String line, int lineOffset) {
Matcher matcher = itemStartPattern.matcher(line.substring(lineOffset));
return matcher.matches();
}
@Override
protected int processLineContent(String line, int offset) {
String text = line.substring(offset);
// check start of block/item
Matcher itemStartMatcher = itemStartPattern.matcher(text);
Matcher nestedItemStartMatcher = nestedItemStartPattern.matcher(text);
boolean thisLineStartsNewParagraph = nextLineStartsNewParagraph;
nextLineStartsNewParagraph = false;
if (itemStartMatcher.matches()) {
handleItem(text, itemStartMatcher);
} else if (nestedItemStartMatcher.matches()) {
handleNestedItem(text, nestedItemStartMatcher);
} else if (getMarkupLanguage().isEmptyLine(text) && canContinueWithNextLine()) {
closeNestedParagraph();
// next line will start a new paragraph
nextLineStartsNewParagraph = true;
} else if (!getMarkupLanguage().isEmptyLine(text)) {
handleText(text, thisLineStartsNewParagraph);
} else {
setClosed(true);
return offset;
}
blockLineCount++;
return -1;
}
private void handleItem(String text, Matcher itemStartMatcher) {
if (blockLineCount == 0) {
// start list block
BlockType blockType = itemStartMatcher.group(1) != null ? BlockType.BULLETED_LIST : BlockType.NUMERIC_LIST;
builder.beginBlock(blockType, new Attributes());
// start item
builder.beginBlock(BlockType.LIST_ITEM, new Attributes());
} else {
if (nestedBlock != null) {
nestedBlock.setClosed(true);
nestedBlock = null;
} else {
// end list item
builder.endBlock();
}
// start item
builder.beginBlock(BlockType.LIST_ITEM, new Attributes());
}
int contentStart = itemStartMatcher.start(3);
thisIndentation = contentStart;
getMarkupLanguage().emitMarkupLine(getParser(), getState(), text, contentStart);
}
private void handleNestedItem(String text, Matcher nestedItemStartMatcher) {
if (nestedBlock == null) {
// end list item
builder.endBlock();
int nestedOffset = nestedItemStartMatcher.end(1);
nestedBlock = new ListBlock();
nestedBlock.setParser(getParser());
nestedBlock.setState(getState());
nestedBlock.processLine(text, nestedOffset);
} else {
nestedBlock.processLine(text, thisIndentation);
}
}
private boolean canContinueWithNextLine() {
String nextLine = getNextLine();
if (nextLine == null) {
return false;
}
Matcher indentedParagraph = indentedParagraphPattern.matcher(nextLine);
Matcher nextItem = itemStartPattern.matcher(nextLine);
Matcher nestedItem = nestedItemStartPattern.matcher(nextLine);
return ((indentedParagraph.matches() && indentedParagraph.end(1) == thisIndentation) // paragraph starts
|| nextItem.matches() || nestedItem.matches()); // or (nested) list continues
}
private void handleText(String text, boolean startsNewParagraph) {
if (nestedParagraph == null) {
builder.characters("\n"); //$NON-NLS-1$
if (startsNewParagraph) {
nestedParagraph = new ParagraphBlock();
nestedParagraph.setParser(getParser());
nestedParagraph.setState(getState());
nestedParagraph.processLine(text, thisIndentation);
startsNewParagraph = false;
} else {
Matcher matcher = indentedParagraphPattern.matcher(text);
if (matcher.matches() && matcher.end(1) == thisIndentation) {
getMarkupLanguage().emitMarkupLine(getParser(), getState(), text, thisIndentation);
} else {
getMarkupLanguage().emitMarkupLine(getParser(), getState(), text, 0);
}
}
} else {
Matcher matcher = indentedParagraphPattern.matcher(text);
if (matcher.matches() && matcher.end(1) == thisIndentation) {
nestedParagraph.processLine(text, thisIndentation);
} else {
nestedParagraph.processLine(text, 0);
}
}
}
private void closeNestedParagraph() {
if (nestedParagraph != null) {
nestedParagraph.setClosed(true);
nestedParagraph = null;
}
}
private String getNextLine() {
LookAheadReader lookAhead = new LookAheadReader();
lookAhead.setContentState(getState());
return lookAhead.lookAhead();
}
@Override
public void setClosed(boolean closed) {
if (closed && !isClosed()) {
closeNestedParagraph();
if (nestedBlock != null && !nestedBlock.isClosed()) {
nestedBlock.setClosed(closed);
nestedBlock = null;
} else {
// end list item
builder.endBlock();
}
// end list block
builder.endBlock();
}
super.setClosed(closed);
}
}