package org.netbeans.freemarker.hyperlink;
import java.io.File;
import java.util.EnumSet;
import java.util.Set;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType;
import org.openide.awt.StatusDisplayer;
import org.openide.cookies.OpenCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.util.Exceptions;
import freemarker.core.FMParserConstants;
@MimeRegistration(mimeType = "text/x-ftl", service = HyperlinkProviderExt.class)
public class FTLHyperlinkProvider implements HyperlinkProviderExt {
private int startOffset, endOffset;
@Override
public Set<HyperlinkType> getSupportedHyperlinkTypes() {
return EnumSet.of(HyperlinkType.GO_TO_DECLARATION);
}
@Override
public boolean isHyperlinkPoint(Document doc, int offset, HyperlinkType type) {
return getHyperlinkSpan(doc, offset, type) != null;
}
@Override
public int[] getHyperlinkSpan(Document doc, int offset, HyperlinkType type) {
return getIdentifierSpan(doc, offset);
}
@Override
public String getTooltipText(Document doc, int offset, HyperlinkType type) {
String text = null;
try {
int idx = doc.getText(startOffset, endOffset - startOffset).lastIndexOf("/") + 1;
text = doc.getText(startOffset, endOffset - startOffset).substring(idx);
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
return "Click to open " + text;
}
@Override
public void performClickAction(Document doc, int offset, HyperlinkType ht) {
try {
String text = doc.getText(startOffset, endOffset - startOffset);
FileObject fo = getFileObject(doc);
String pathToFileToOpen = fo.getParent().getPath() + "/" + text;
File fileToOpen = FileUtil.normalizeFile(new File(pathToFileToOpen));
if (fileToOpen.exists()) {
try {
FileObject foToOpen = FileUtil.toFileObject(fileToOpen);
DataObject.find(foToOpen).getLookup().lookup(OpenCookie.class).open();
} catch (DataObjectNotFoundException ex) {
Exceptions.printStackTrace(ex);
}
} else {
StatusDisplayer.getDefault().setStatusText(fileToOpen.getPath() + " doesn't exist!");
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
private static FileObject getFileObject(Document doc) {
DataObject od = (DataObject) doc.getProperty(Document.StreamDescriptionProperty);
return od != null ? od.getPrimaryFile() : null;
}
private int[] getIdentifierSpan(Document doc, int offset) {
int[] result = null;
if (doc instanceof AbstractDocument) {
((AbstractDocument) doc).readLock();
}
TokenHierarchy<?> th = TokenHierarchy.get(doc);
TokenSequence ts = th.tokenSequence();
if (ts != null) {
ts.move(offset);
if (ts.moveNext() || ts.movePrevious()) {
Token t = ts.token();
if (t.id().ordinal() == FMParserConstants.STRING_LITERAL) {
//Correction for quotation marks around the token:
startOffset = ts.offset() + 1;
endOffset = ts.offset() + t.length() - 1;
//Check that the previous token was an import or include statement
ts.movePrevious();
Token prevToken = ts.token();
if (prevToken.id().ordinal() == FMParserConstants.IMPORT || prevToken.id().ordinal() == FMParserConstants._INCLUDE) {
result = new int[]{startOffset, endOffset};
}
}
}
}
if (doc instanceof AbstractDocument) {
((AbstractDocument) doc).readUnlock();
}
return result;
}
}