/*=============================================================================#
# Copyright (c) 2015-2016 David Green 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:
# David Green - initial API and implementation in Mylyn
# Stephan Wahlbrink (WalWare.de) - revised API and implementation
#=============================================================================*/
package de.walware.docmlet.wikitext.internal.commonmark.core.blocks;
import static com.google.common.base.Preconditions.checkState;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.mylyn.wikitext.core.parser.Attributes;
import org.eclipse.mylyn.wikitext.core.parser.DocumentBuilder;
import org.eclipse.mylyn.wikitext.core.parser.DocumentBuilder.BlockType;
import de.walware.jcommons.collections.ImList;
import de.walware.docmlet.wikitext.internal.commonmark.core.CommonmarkLocator;
import de.walware.docmlet.wikitext.internal.commonmark.core.Line;
import de.walware.docmlet.wikitext.internal.commonmark.core.LineSequence;
import de.walware.docmlet.wikitext.internal.commonmark.core.ProcessingContext;
import de.walware.docmlet.wikitext.internal.commonmark.core.SourceBlock;
import de.walware.docmlet.wikitext.internal.commonmark.core.SourceBlockItem;
import de.walware.docmlet.wikitext.internal.commonmark.core.SourceBlocks.SourceBlockBuilder;
public class FencedCodeBlock extends SourceBlock {
private static final Pattern START_PATTERN= Pattern.compile(
"(?:`{3,}|~{3,})[^`]*",
Pattern.DOTALL );
private static final Pattern OPEN_PATTERN= Pattern.compile(
"(`{3,}|~{3,})[ \t]*([^ \t`]+)?.*",
Pattern.DOTALL );
private static final Pattern CLOSE_BACKTICK_PATTERN= Pattern.compile(
"(`{3,})[ \t]*",
Pattern.DOTALL );
private static final Pattern CLOSE_TILDE_PATTERN= Pattern.compile(
"(~{3,})[ \t]*",
Pattern.DOTALL );
static final class CodeBlockItem extends SourceBlockItem<FencedCodeBlock> {
private String infoText;
private boolean isClosed;
public CodeBlockItem(final FencedCodeBlock type, final SourceBlockBuilder builder) {
super(type, builder);
}
}
private final Matcher startMatcher= START_PATTERN.matcher("");
private Matcher openMatcher;
private Matcher closeBacktickMatcher;
private Matcher closeTildeMatcher;
public FencedCodeBlock() {
}
@Override
public boolean canStart(final LineSequence lineSequence, final SourceBlockItem<?> currentBlockItem) {
final Line currentLine= lineSequence.getCurrentLine();
return (currentLine != null
&& !currentLine.isBlank() && currentLine.getIndent() < 4
&& currentLine.setupIndent(this.startMatcher).matches() );
}
@Override
public void createItem(final SourceBlockBuilder builder, final LineSequence lineSequence) {
final CodeBlockItem codeBlockItem= new CodeBlockItem(this, builder);
final Line startLine= lineSequence.getCurrentLine();
lineSequence.advance();
final Matcher openMatcher= startLine.setup(getOpenMatcher(), true, false);
checkState(openMatcher.matches());
final int minCount= openMatcher.end(1) - openMatcher./*start(1)*/regionStart();
codeBlockItem.infoText= getInfoText(openMatcher);
final Matcher closeMatcher= getCloseMatcher(startLine, openMatcher);
while (true) {
final Line line= lineSequence.getCurrentLine();
if (line != null) {
lineSequence.advance();
if (!line.isBlank() && line.getIndent() < 4
&& (line.setupIndent(closeMatcher)).matches()
&& closeMatcher.end(1) - closeMatcher./*start(1)*/regionStart() >= minCount ) {
codeBlockItem.isClosed= true;
break;
}
continue;
}
break;
}
}
@Override
public void initializeContext(final ProcessingContext context, final SourceBlockItem<?> blockItem) {
}
@Override
public void emit(final ProcessingContext context, final SourceBlockItem<?> blockItem,
final CommonmarkLocator locator, final DocumentBuilder builder) {
final CodeBlockItem codeBlockItem= (CodeBlockItem) blockItem;
final ImList<Line> lines= blockItem.getLines();
final Line startLine= lines.get(0);
final Matcher openMatcher= startLine.setup(getOpenMatcher(), true, false);
checkState(openMatcher.matches());
final Attributes codeAttributes= new Attributes();
if (codeBlockItem.infoText != null) {
final String language= context.getHelper().replaceEscaping(codeBlockItem.infoText);
codeAttributes.setCssClass("language-" + language);
}
locator.setBlockBegin(blockItem);
builder.beginBlock(BlockType.CODE, codeAttributes);
final int startIndent= startLine.getIndent();
for (final Line line : lines.subList(1, (codeBlockItem.isClosed) ? lines.size() - 1 : lines.size())) {
final Line codeSegment;
if (startIndent > 0 && line.getIndent() > 0) {
codeSegment= line.segmentByIndent(Math.min(startIndent, line.getIndent()));
}
else {
codeSegment= line;
}
locator.setLine(codeSegment);
builder.characters(codeSegment.getText());
builder.characters("\n");
}
locator.setBlockEnd(blockItem);
builder.endBlock();
}
private String getInfoText(final Matcher matcher) {
final String infoText= matcher.group(2);
if (infoText != null && !infoText.isEmpty()) {
return infoText;
}
return null;
}
private Matcher getOpenMatcher() {
if (this.openMatcher == null) {
this.openMatcher= OPEN_PATTERN.matcher("");
}
return this.openMatcher;
}
private Matcher getCloseMatcher(final Line line, final Matcher matcher) {
switch (line.getText().charAt(matcher.start(1))) {
case '`':
if (this.closeBacktickMatcher == null) {
this.closeBacktickMatcher= CLOSE_BACKTICK_PATTERN.matcher("");
}
return this.closeBacktickMatcher;
case '~':
if (this.closeTildeMatcher == null) {
this.closeTildeMatcher= CLOSE_TILDE_PATTERN.matcher("");
}
return this.closeTildeMatcher;
default:
throw new IllegalStateException();
}
}
}