package org.rascalmpl.eclipse.editor.proposer;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.swt.graphics.Point;
import org.rascalmpl.ast.Module;
import org.rascalmpl.parser.ASTBuilder;
import io.usethesource.vallang.ISourceLocation;
import org.rascalmpl.values.uptr.ITree;
import io.usethesource.impulse.editor.ErrorProposal;
import io.usethesource.impulse.editor.SourceProposal;
import io.usethesource.impulse.parser.IParseController;
import io.usethesource.impulse.services.IContentProposer;
/**
* Content proposer for Rascal.
*/
public class ContentProposer implements IContentProposer {
private class ProposalComposer extends SymbolVisitor<Boolean> {
private final List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
private Prefix prefix;
public List<ICompletionProposal> compose(ISymbol symbolTree, Prefix prefix) {
if (symbolTree != null) {
this.prefix = prefix;
symbolTree.accept(this);
}
return proposals;
}
@Override
public Boolean visitScope(Scope scope) {
ISymbol scopeSymbol = scope.getScopeSymbol();
if (scopeSymbol != null) {
scopeSymbol.accept(this);
}
for (ISymbol childSymbol : scope.getSymbols()) {
childSymbol.accept(this);
}
return true;
}
@Override
public Boolean visitSymbol(Symbol symbol) {
String type = symbol.getType();
String name = symbol.getName();
String label = symbol.getLabel();
if (name.toLowerCase().contains(prefix.getText().toLowerCase())) {
if (type.equals(Symbol.symbol_type_function) || type.equals(Symbol.symbol_type_constructor)) {
proposals.add(new SourceProposal(label, name + "()", prefix.getText(), prefix.getOffset()));
} else if (!type.equals(Symbol.symbol_type_module)) {
proposals.add(new SourceProposal(label, name, prefix.getText(), prefix.getOffset()));
}
}
return true;
}
}
private class ScopeFilter extends SymbolVisitor<ISymbol> {
private int offset = 0;
public ISymbol filterTree(ISymbol tree, int offset) {
if (tree != null && offset >= 0) {
this.offset = offset;
return tree.accept(this);
}
return tree;
}
private boolean isOutsideModuleDeclaration(Scope scope) {
ISymbol scopeSymbol = scope.getScopeSymbol();
ISourceLocation scopeLocation = scope.getLocation();
if (scopeSymbol != null
&& scopeSymbol.getType() == Symbol.symbol_type_module
&& scopeLocation != null
&& (scopeLocation.getOffset() + scopeLocation.getLength()) < offset) {
return true;
}
return false;
}
private boolean isWithin(ISymbol tree) {
if (tree == null || tree.getLocation() == null || offset < 0) {
return false;
}
int begin = tree.getLocation().getOffset();
int end = begin + tree.getLocation().getLength();
return begin <= offset && end > offset;
}
@Override
public ISymbol visitScope(Scope scope) {
if (isOutsideModuleDeclaration(scope)) {
// In case of parse errors and the request is "outside of the file",
// return everything instead of nothing for convinience.
return scope;
}
if (isWithin(scope)) {
Scope filteredScope = new Scope(scope.getScopeSymbol(), scope.getLocation());
for (ISymbol childSymbol : scope.getSymbols()) {
filteredScope.addSymbol(childSymbol.accept(this));
}
return filteredScope;
} else if (scope.getScopeSymbol() != null) {
return scope.getScopeSymbol();
}
return null;
}
@Override
public ISymbol visitSymbol(Symbol symbol) {
return symbol;
}
}
private class SymbolLabeler extends SymbolVisitor<ISymbol> {
List<ISymbol> scopeChildren = null;
public ISymbol generate(ISymbol symbolTree) {
return symbolTree.accept(this);
}
private String getArgumentLabel() {
String argumentList = "";
if (scopeChildren != null) {
for (ISymbol scopeChild : scopeChildren) {
if (scopeChild.getType().equals(Symbol.symbol_type_arg)) {
if (!argumentList.isEmpty()) {
argumentList += ", ";
}
argumentList += scopeChild.getAttribute(Symbol.symbol_attribute_datatype) + " " + scopeChild.getName();
}
}
}
return argumentList;
}
@Override
public ISymbol visitScope(Scope scope) {
scopeChildren = scope.getSymbols();
if (scope.getScopeSymbol() != null) {
scope.getScopeSymbol().accept(this);
}
scopeChildren = null;
for (ISymbol childSymbol : scope.getSymbols()) {
childSymbol.accept(this);
}
return scope;
}
@Override
public ISymbol visitSymbol(Symbol symbol) {
String type = symbol.getType();
String name = symbol.getName();
String datatype = symbol.getAttribute(Symbol.symbol_attribute_datatype);
if (datatype == null) datatype = Symbol.symbol_datatype_unknown;
if (type.equals(Symbol.symbol_type_function) || type.equals(Symbol.symbol_type_constructor)) {
String argumentList = getArgumentLabel();
symbol.setLabel(name + "(" + argumentList + ") - " + datatype + " - " + type);
} else if (type.equals(Symbol.symbol_type_adt)) {
symbol.setLabel(name + " - " + type);
} else {
symbol.setLabel(name + " - " + datatype + " - " + type);
}
return symbol;
}
}
ISymbol cachedSymbolTree;
int previousDocumentHash = 0;
private final String identifier_allowed_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\\_-";
private void createProposals(int offset, List<ICompletionProposal> proposals, Prefix prefix, ISymbol symbolTree, boolean filterScope) {
ISymbol filteredSymbolTree = symbolTree;
if (filterScope) {
ScopeFilter filter = new ScopeFilter();
filteredSymbolTree = filter.filterTree(symbolTree, offset);
}
proposals.addAll(new ProposalComposer().compose(filteredSymbolTree, prefix));
}
@Override
public ICompletionProposal[] getContentProposals(IParseController parseController, int requestOffset, ITextViewer textViewer) {
int currentDocumentHash = parseController.getDocument().get().hashCode();
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
ITree tree = (ITree) parseController.getCurrentAst();
Point selection = textViewer.getSelectedRange();
Prefix prefix = Prefix.getPrefix(parseController.getDocument(), selection.x, selection.y, identifier_allowed_chars);
if (tree != null) {
ISymbol symbolTree = null;
if (currentDocumentHash != previousDocumentHash) {
ASTBuilder builder = new ASTBuilder();
Module moduleAST = builder.buildModule(tree);
symbolTree = SymbolTreeCreator.create(moduleAST);
if (symbolTree != null) {
SymbolLabeler labeler = new SymbolLabeler();
symbolTree = labeler.generate(symbolTree);
cachedSymbolTree = symbolTree;
}
} else {
symbolTree = cachedSymbolTree;
}
if (symbolTree != null) {
createProposals(requestOffset, proposals, prefix, symbolTree, true);
}
} else {
if (cachedSymbolTree != null) {
createProposals(requestOffset, proposals, prefix, cachedSymbolTree, false);
} else {
proposals.add(new ErrorProposal("No proposals available: syntax errors.", requestOffset));
}
}
if (proposals.size() == 0) {
proposals.add(new ErrorProposal("No proposals available.", requestOffset));
}
previousDocumentHash = currentDocumentHash;
return proposals.toArray(new ICompletionProposal[proposals.size()]);
}
}