/*******************************************************************************
* MontiCore Language Workbench
* Copyright (c) 2015, 2016, MontiCore, All rights reserved.
*
* This project is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this project. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package de.se_rwth.langeditor.texteditor.hyperlinks;
import java.util.Collections;
import java.util.Optional;
import java.util.function.Supplier;
import org.antlr.v4.runtime.misc.Interval;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.Trees;
import org.eclipse.core.resources.IStorage;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.texteditor.ITextEditor;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import de.monticore.ast.ASTNode;
import de.se_rwth.langeditor.injection.TextEditorScoped;
import de.se_rwth.langeditor.language.Language;
import de.se_rwth.langeditor.modelstates.Nodes;
import de.se_rwth.langeditor.modelstates.ModelState;
import de.se_rwth.langeditor.modelstates.ObservableModelStates;
import de.se_rwth.langeditor.util.Misc;
import de.se_rwth.langeditor.util.antlr.ParseTrees;
@TextEditorScoped
public final class HyperlinkDetectorImpl extends AbstractHyperlinkDetector {
private final Language language;
private final Nodes nodes;
private final ObservableModelStates observableModelStates;
private final Supplier<Optional<ModelState>> currentModelState;
@Inject
HyperlinkDetectorImpl(Language language, Nodes nodes, IStorage storage,
ObservableModelStates observableModelStates) {
this.language = language;
this.nodes = nodes;
this.observableModelStates = observableModelStates;
this.currentModelState = () -> observableModelStates.findModelState(storage);
}
@Override
public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region,
boolean canShowMultipleHyperlinks) {
Optional<ASTNode> enclosingASTNode = getEnclosingASTNode(region);
Optional<Supplier<Optional<ASTNode>>> resolver = enclosingASTNode
.flatMap(language::createResolver);
if (enclosingASTNode.isPresent() && resolver.isPresent()) {
IHyperlink hyperlink = new ParseTreeHyperlinkAdapter(
enclosingASTNode.flatMap(nodes::getParseTree).get(), resolver.get());
return new IHyperlink[] { hyperlink };
}
return null;
}
private Optional<ASTNode> getEnclosingASTNode(IRegion region) {
return currentModelState.get()
.flatMap(currentModelState -> ParseTrees.getTerminalBySourceCharIndex(
currentModelState.getRootContext(), region.getOffset()))
.map(Trees::getAncestors)
.map(ancestors -> Lists.reverse(ancestors))
.orElse(Collections.emptyList())
.stream()
.filter(ParseTree.class::isInstance)
.map(ParseTree.class::cast)
.map(Nodes::getAstNode)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
}
private class ParseTreeHyperlinkAdapter implements IHyperlink {
private final ParseTree parseTree;
private final Supplier<Optional<ASTNode>> resolver;
private ParseTreeHyperlinkAdapter(ParseTree parseTree, Supplier<Optional<ASTNode>> resolver) {
this.parseTree = parseTree;
this.resolver = resolver;
}
@Override
public IRegion getHyperlinkRegion() {
Interval interval = ParseTrees.tokenInterval(parseTree);
return new Region(interval.a, interval.length() - 1);
}
@Override
public String getTypeLabel() {
return null;
}
@Override
public String getHyperlinkText() {
return null;
}
@Override
public void open() {
resolver.get()
.flatMap(nodes::getParseTree)
.ifPresent(resolved -> {
findStorage(resolved).ifPresent(storage -> openEditor(resolved, storage));
});
}
private Optional<IStorage> findStorage(ParseTree parseTree) {
return observableModelStates.getModelStates().stream()
.filter(modelState -> Trees.descendants(modelState.getRootContext()).stream()
.anyMatch(parseTree::equals))
.findFirst()
.map(ModelState::getStorage);
}
}
private void openEditor(ParseTree parseTree, IStorage storage) {
try {
IEditorDescriptor editorDescriptor = IDE.getEditorDescriptor(storage.getName());
ITextEditor textEditor = (ITextEditor) IDE.openEditor(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(),
Misc.getEditorInput(storage), editorDescriptor.getId());
int startIndex = ParseTrees.getFirstToken(parseTree).get().getStartIndex();
textEditor.selectAndReveal(startIndex, 0);
}
catch (PartInitException e) {
throw new RuntimeException(e);
}
}
}