| /******************************************************************************* |
| * Copyright (c) 2015 David Green. |
| * 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 |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.wikitext.commonmark.inlines; |
| |
| import com.google.common.base.CharMatcher; |
| import com.google.common.base.Optional; |
| |
| public class PotentialEmphasisSpan extends SourceSpan { |
| |
| @Override |
| public Optional<? extends Inline> createInline(Cursor cursor) { |
| char c = cursor.getChar(); |
| if ((c == '_' || c == '*') && !currentPositionIsEscaped(cursor)) { |
| int length = lengthMatching(cursor, c); |
| |
| boolean leftFlanking = isLeftFlanking(cursor, length); |
| boolean rightFlanking = isRightFlanking(cursor, length); |
| |
| boolean canOpen = leftFlanking; |
| boolean canClose = rightFlanking; |
| if (c == '_') { |
| canOpen = leftFlanking && (!rightFlanking || isPunctuation(charBefore(cursor))); |
| canClose = rightFlanking && (!leftFlanking || isPunctuation(charAfter(cursor, length))); |
| } |
| return Optional.of(new PotentialEmphasisDelimiter(cursor.getLineAtOffset(), cursor.getOffset(), length, |
| cursor.getTextAtOffset(length), canOpen, canClose)); |
| } |
| return Optional.absent(); |
| } |
| |
| boolean isLeftFlanking(Cursor cursor, int length) { |
| char charBefore = charBefore(cursor); |
| char charAfter = charAfter(cursor, length); |
| return !isWhitespace(charAfter) |
| && !(isPunctuation(charAfter) && !isWhitespace(charBefore) && !isPunctuation(charBefore)); |
| } |
| |
| private char charAfter(Cursor cursor, int length) { |
| return cursor.hasNext(length) ? cursor.getNext(length) : '\n'; |
| } |
| |
| private char charBefore(Cursor cursor) { |
| return cursor.hasPrevious() ? cursor.getPrevious() : '\n'; |
| } |
| |
| boolean isRightFlanking(Cursor cursor, int length) { |
| char charBefore = charBefore(cursor); |
| char charAfter = charAfter(cursor, length); |
| return !isWhitespace(charBefore) |
| && !(isPunctuation(charBefore) && !isWhitespace(charAfter) && !isPunctuation(charAfter)); |
| |
| } |
| |
| private boolean isWhitespace(char c) { |
| return CharMatcher.WHITESPACE.matches(c); |
| } |
| |
| private boolean isPunctuation(char c) { |
| String punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`,{|}~"; |
| return punctuation.indexOf(c) >= 0; |
| } |
| |
| private boolean currentPositionIsEscaped(Cursor cursor) { |
| int backslashCount = 0; |
| for (int x = 1; cursor.hasPrevious(x) && cursor.getPrevious(x) == '\\'; ++x) { |
| ++backslashCount; |
| } |
| return backslashCount % 2 == 1; |
| } |
| |
| private int lengthMatching(Cursor cursor, char c) { |
| int x = 1; |
| while (cursor.hasNext(x) && cursor.getNext(x) == c) { |
| ++x; |
| } |
| return x; |
| } |
| |
| static boolean isLetterOrDigit(char previous) { |
| return (previous >= '0' && previous <= '9') || (previous >= 'A' && previous <= 'Z') |
| || (previous >= 'a' && previous <= 'z'); |
| } |
| } |