/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.goide;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiComment;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.URLUtil;
import com.intellij.xml.util.XmlStringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* See https://golang.org/src/go/doc/comment.go
*/
public class GoCommentsConverter {
private static final Pattern LEADING_TAB = Pattern.compile("^\\t", Pattern.MULTILINE);
@NotNull
public String toText(@NotNull List<PsiComment> comments) {
return StringUtil.join(getStrings(comments), "\n");
}
@NotNull
public String toHtml(@NotNull List<PsiComment> comments) {
return textToHtml(getStrings(comments));
}
@NotNull
private static List<String> getStrings(@NotNull List<PsiComment> comments) {
List<String> strings = ContainerUtil.newArrayList();
for (PsiComment comment : comments) {
IElementType type = comment.getTokenType();
if (type == GoParserDefinition.LINE_COMMENT) {
strings.add(StringUtil.trimStart(StringUtil.trimStart(comment.getText(), "//"), " "));
}
else if (type == GoParserDefinition.MULTILINE_COMMENT) {
String text = StringUtil.trimEnd(comment.getText(), "*/");
text = StringUtil.trimStart(text, "/*");
text = LEADING_TAB.matcher(text).replaceAll("");
Collections.addAll(strings, StringUtil.splitByLines(text, false));
}
}
return strings;
}
@NotNull
public String textToHtml(@NotNull List<String> strings) {
State state = new State(strings.iterator());
while (state.hasNext()) {
state.next();
if (state.indented()) {
state.flushBlock("p");
processIndentedBlock(state);
}
processSimpleBlock(state);
}
state.flushBlock("p");
return state.result();
}
private static void processSimpleBlock(@NotNull State state) {
if (state.isBlank()) {
state.flushBlock("p");
}
else {
state.append(state.text, true);
state.append("\n"); // just for prettier testdata
}
}
private static void processIndentedBlock(@NotNull State state) {
state.append(state.text);
int emptyLines = 1;
String text;
while ((text = state.next()) != null) {
if (state.isBlank()) {
emptyLines++;
}
else if (state.indented()) {
state.append(StringUtil.repeatSymbol('\n', emptyLines)).append(text);
emptyLines = 1;
}
else {
break;
}
}
state.flushBlock("pre");
}
private static class State {
@NotNull private final StringBuilder currentBlock = new StringBuilder();
@NotNull private final StringBuilder result = new StringBuilder();
@NotNull private final Iterator<String> iterator;
@Nullable private String text;
public State(@NotNull Iterator<String> iterator) {
this.iterator = iterator;
}
String next() {
return text = iterator.hasNext() ? iterator.next() : null;
}
boolean isBlank() {
return text != null && text.trim().isEmpty();
}
boolean hasNext() {
return iterator.hasNext();
}
boolean indented() {
return text != null && (StringUtil.startsWithChar(text, ' ') || StringUtil.startsWithChar(text, '\t'));
}
State append(@Nullable String text, boolean nice) {
if (StringUtil.isNotEmpty(text)) {
currentBlock.append(emphasize(text, nice));
}
return this;
}
State append(@Nullable String text) {
return append(text, false);
}
/**
* Escape comment text for HTML. If nice is set, also turn `` into “ and '' into ”.
*/
private static String emphasize(@NotNull String text, boolean nice) {
text = XmlStringUtil.escapeString(text);
StringBuilder textWithLinks = null;
Matcher matcher = URLUtil.URL_PATTERN.matcher(text);
while (matcher.find()) {
if (textWithLinks == null) {
textWithLinks = new StringBuilder();
}
textWithLinks.append(text.substring(0, matcher.start()))
.append("<a href=\"").append(matcher.group()).append("\">").append(matcher.group()).append("</a>");
}
if (textWithLinks != null) {
text = textWithLinks.toString();
}
return nice ? StringUtil.replace(text, new String[]{"``", "''"}, new String[]{"“", "”"}) : text;
}
void flushBlock(@NotNull String wrapTag) {
if (currentBlock.length() > 0) {
result.append('<').append(wrapTag).append(">").append(currentBlock).append("</").append(wrapTag).append(">\n");
currentBlock.setLength(0);
}
}
String result() {
return result.toString();
}
}
}