| /******************************************************************************* |
| * Copyright (c) 2005, 2017 IBM Corporation and others. |
| * 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 |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.tcl.internal.core.codeassist.completion; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.dltk.ast.ASTNode; |
| import org.eclipse.dltk.ast.ASTVisitor; |
| import org.eclipse.dltk.ast.declarations.MethodDeclaration; |
| import org.eclipse.dltk.ast.declarations.ModuleDeclaration; |
| import org.eclipse.dltk.ast.declarations.TypeDeclaration; |
| import org.eclipse.dltk.ast.expressions.Expression; |
| import org.eclipse.dltk.ast.expressions.StringLiteral; |
| import org.eclipse.dltk.ast.references.SimpleReference; |
| import org.eclipse.dltk.ast.statements.Block; |
| import org.eclipse.dltk.ast.statements.Statement; |
| import org.eclipse.dltk.codeassist.complete.CompletionNodeFound; |
| import org.eclipse.dltk.tcl.ast.TclStatement; |
| import org.eclipse.dltk.tcl.ast.expressions.TclBlockExpression; |
| import org.eclipse.dltk.tcl.ast.expressions.TclExecuteExpression; |
| import org.eclipse.dltk.tcl.core.TclKeywordsManager; |
| import org.eclipse.dltk.tcl.core.TclParseUtil; |
| import org.eclipse.dltk.tcl.core.ast.TclAdvancedExecuteExpression; |
| import org.eclipse.dltk.tcl.core.ast.TclPackageDeclaration; |
| import org.eclipse.dltk.tcl.core.extensions.ICompletionExtension; |
| import org.eclipse.dltk.tcl.internal.core.codeassist.TclASTUtil; |
| import org.eclipse.dltk.tcl.internal.core.codeassist.TclAssistParser; |
| import org.eclipse.dltk.tcl.internal.parser.OldTclParserUtils; |
| |
| public class TclCompletionParser extends TclAssistParser { |
| private static class TclEmptyCompleteStatement extends TclStatement { |
| public TclEmptyCompleteStatement(List expressions) { |
| super(expressions); |
| } |
| } |
| |
| private ICompletionExtension[] extensions; |
| |
| public TclCompletionParser(ICompletionExtension[] extensions) { |
| this.extensions = extensions; |
| } |
| |
| /** |
| * Called then element could not be found. |
| */ |
| @Override |
| public void handleNotInElement(ASTNode node, int position) { |
| if (node != null && node.sourceStart() <= position |
| && position <= node.sourceEnd()) { |
| // this is empty module case |
| if (node instanceof ModuleDeclaration) { |
| ModuleDeclaration unit = (ModuleDeclaration) node; |
| List exprs = new ArrayList(); |
| exprs.add(new SimpleReference(position, position, "")); |
| TclEmptyCompleteStatement statement = new TclEmptyCompleteStatement( |
| exprs); |
| unit.addStatement(statement); |
| this.parseBlockStatements(statement, unit, position); |
| } else if (node instanceof MethodDeclaration) { |
| // empty keyword like completion. |
| MethodDeclaration method = (MethodDeclaration) node; |
| List exprs = new ArrayList(); |
| exprs.add(new SimpleReference(position, position, "")); |
| TclEmptyCompleteStatement statement = new TclEmptyCompleteStatement( |
| exprs); |
| method.getStatements().add(statement); |
| this.parseBlockStatements(statement, method, position); |
| } else if (node instanceof TypeDeclaration) { |
| // empty keyword like completion. |
| TypeDeclaration type = (TypeDeclaration) node; |
| List exprs = new ArrayList(); |
| exprs.add(new SimpleReference(position, position, "")); |
| TclEmptyCompleteStatement statement = new TclEmptyCompleteStatement( |
| exprs); |
| type.getStatements().add(statement); |
| // this.assistNodeParent = node; |
| this.parseBlockStatements(statement, type, position); |
| } else if (node instanceof TclExecuteExpression) { |
| // empty keyword like completion. |
| List exprs = new ArrayList(); |
| exprs.add(new SimpleReference(position, position, "")); |
| TclEmptyCompleteStatement statement = new TclEmptyCompleteStatement( |
| exprs); |
| this.parseBlockStatements(statement, node, position); |
| } |
| } |
| } |
| |
| @Override |
| public void parseBlockStatements(ASTNode node, ASTNode inNode, |
| int position) { |
| if (node instanceof TclStatement) { |
| TclStatement statement = (TclStatement) node; |
| List<ASTNode> expressions = statement.getExpressions(); |
| int len = expressions.size(); |
| boolean first = false; |
| String completionToken = null; |
| ASTNode completionNode = null; |
| for (int i = 0; i < len; ++i) { |
| ASTNode n = expressions.get(i); |
| if (n.sourceStart() <= position && n.sourceEnd() >= position |
| || (node instanceof TclEmptyCompleteStatement)) { |
| if (i == 0) { |
| first = true; |
| } |
| completionNode = n; |
| } |
| } |
| if (completionNode == null) { |
| // TODO: Add inner completion here. |
| if (len > 0) { |
| // ASTNode firstNode = (ASTNode) expressions.get(0); |
| // if (position > firstNode.sourceEnd()) { |
| // // This could be variable completion. |
| // boolean provideDollar = |
| // !checkVariableWithoutDollarCompletion( |
| // statement, position); |
| // this.assistNodeParent = inNode; |
| // SimpleReference ref = new SimpleReference(position, |
| // position, ""); |
| // ASTNode nde = new CompletionOnVariable("", ref, node, |
| // inNode, true, provideDollar); |
| // throw new CompletionNodeFound(nde, null); |
| // } |
| String[] keywords = checkKeywords(completionToken, MODULE); |
| ASTNode nde = new CompletionOnKeywordArgumentOrFunctionArgument( |
| "", (TclStatement) node, keywords, position); |
| this.assistNodeParent = inNode; |
| throw new CompletionNodeFound(nde, |
| null/* |
| * ((TypeDeclaration) inNode).scope |
| */); |
| |
| } else { |
| completionToken = ""; |
| } |
| } else if (completionNode instanceof SimpleReference) { |
| int maxLen = position - completionNode.sourceStart(); |
| completionToken = ((SimpleReference) completionNode).getName(); |
| // We need to cut some sumbols if node is begger then position. |
| if (completionToken.length() > maxLen && maxLen > 0) { |
| completionToken = completionToken.substring(0, maxLen); |
| } |
| } |
| if (completionNode instanceof StringLiteral) { |
| int maxLen = position - completionNode.sourceStart(); |
| int pos = maxLen; |
| SimpleReference tok = OldTclParserUtils |
| .extractVariableFromString( |
| (StringLiteral) completionNode, pos); |
| if (tok != null) { |
| this.assistNodeParent = inNode; |
| ASTNode nde = new CompletionOnVariable(tok.getName(), tok, |
| node, inNode, false); |
| throw new CompletionNodeFound(nde, null); |
| } else { |
| this.assistNodeParent = inNode; |
| SimpleReference ref = new SimpleReference(position, |
| position, ""); |
| ASTNode nde = new CompletionOnVariable("", ref, node, |
| inNode, true); |
| throw new CompletionNodeFound(nde, null); |
| } |
| } |
| if (completionNode instanceof TclExecuteExpression) { |
| TclExecuteExpression expr = (TclExecuteExpression) completionNode; |
| List exprs = expr.parseExpression(); |
| for (int i = 0; i < exprs.size(); ++i) { |
| ASTNode n = (ASTNode) exprs.get(i); |
| if (n.sourceStart() <= position |
| && n.sourceEnd() >= position) { |
| parseBlockStatements(n, expr, position); |
| } |
| } |
| handleNotInElement(expr, position); |
| } |
| if (completionNode instanceof TclAdvancedExecuteExpression) { |
| TclAdvancedExecuteExpression expr = (TclAdvancedExecuteExpression) completionNode; |
| List exprs = expr.getStatements(); |
| for (int i = 0; i < exprs.size(); ++i) { |
| ASTNode n = (ASTNode) exprs.get(i); |
| if (n.sourceStart() <= position |
| && n.sourceEnd() >= position) { |
| parseBlockStatements(n, expr, position); |
| } |
| } |
| } |
| if (completionNode instanceof TclBlockExpression) { |
| TclBlockExpression block = (TclBlockExpression) completionNode; |
| |
| List s = block.parseBlockSimple(); |
| if (s != null) { |
| int slen = s.size(); |
| for (int u = 0; u < slen; ++u) { |
| ASTNode n = (ASTNode) s.get(u); |
| n.setStart(n.sourceStart() - block.sourceStart()); |
| n.setEnd(n.sourceEnd() - block.sourceStart()); |
| TclASTUtil.extendStatement(n, block.getBlock()); |
| n.setStart(n.sourceStart() + block.sourceStart()); |
| n.setEnd(n.sourceEnd() + block.sourceStart()); |
| if (n.sourceStart() <= position |
| && n.sourceEnd() >= position) { |
| parseBlockStatements(n, inNode, position); |
| } |
| } |
| } |
| handleNotInElement(inNode, position); |
| } |
| |
| if (completionToken != null && completionToken.startsWith("$")) { |
| // Argument name completion... |
| this.assistNodeParent = inNode; |
| ASTNode nde = new CompletionOnVariable(completionToken, |
| completionNode, node, inNode, false); |
| throw new CompletionNodeFound(nde, null); |
| } else { |
| // This is keyword or function completion. |
| if (inNode instanceof ModuleDeclaration |
| && completionNode != null && first) { |
| String[] keywords = checkKeywords(completionToken, MODULE); |
| ASTNode nde = new CompletionOnKeywordOrFunction( |
| completionToken, completionNode, node, keywords); |
| this.assistNodeParent = inNode; |
| throw new CompletionNodeFound(nde, |
| ((ModuleDeclaration) inNode).scope); |
| } else if (inNode instanceof MethodDeclaration |
| && completionNode != null && first) { |
| String[] keywords = checkKeywords(completionToken, |
| FUNCTION); |
| ASTNode nde = new CompletionOnKeywordOrFunction( |
| completionToken, completionNode, |
| TclParseUtil.getScopeParent(getModule(), node), |
| keywords); |
| this.assistNodeParent = inNode; |
| throw new CompletionNodeFound(nde, |
| ((MethodDeclaration) inNode).scope); |
| } else if (inNode instanceof TypeDeclaration |
| && completionNode != null && first) { |
| String[] keywords = checkKeywords(completionToken, |
| NAMESPACE); |
| ASTNode nde = new CompletionOnKeywordOrFunction( |
| completionToken, completionNode, node, keywords); |
| this.assistNodeParent = inNode; |
| throw new CompletionNodeFound(nde, |
| null/* |
| * ((TypeDeclaration) inNode).scope |
| */); |
| } else if (inNode instanceof TclExecuteExpression |
| && completionNode != null && first) { |
| String[] keywords = checkKeywords(completionToken, |
| EXEC_EXPRESSION); |
| ASTNode nde = new CompletionOnKeywordOrFunction( |
| completionToken, completionNode, node, keywords); |
| this.assistNodeParent = inNode; |
| throw new CompletionNodeFound(nde, |
| null/* |
| * ((TypeDeclaration) inNode).scope |
| */); |
| } else { |
| if (completionNode != null) { |
| String[] keywords = checkKeywords(completionToken, |
| MODULE); |
| |
| ASTNode nde = new CompletionOnKeywordArgumentOrFunctionArgument( |
| completionToken, completionNode, |
| (TclStatement) node, keywords); |
| this.assistNodeParent = inNode; |
| throw new CompletionNodeFound(nde, |
| null/* |
| * ((TypeDeclaration ) inNode).scope |
| */); |
| } else { |
| String[] keywords = checkKeywords(completionToken, |
| MODULE); |
| if (completionToken == null) { |
| completionToken = ""; |
| } |
| ASTNode nde = new CompletionOnKeywordArgumentOrFunctionArgument( |
| completionToken, (TclStatement) node, keywords, |
| position); |
| this.assistNodeParent = inNode; |
| throw new CompletionNodeFound(nde, |
| null/* |
| * ((TypeDeclaration ) inNode).scope |
| */); |
| } |
| } |
| } |
| // if (checkVariableWithoutDollarCompletion(statement, position) |
| // && completionToken != null) { |
| // this.assistNodeParent = inNode; |
| // SimpleReference ref = new SimpleReference(completionNode |
| // .sourceStart(), completionNode.sourceEnd(), |
| // completionToken); |
| // ASTNode nde = new CompletionOnVariable(completionToken, ref, |
| // node, inNode, true); |
| // throw new CompletionNodeFound(nde, null); |
| // } |
| |
| } else if (node instanceof MethodDeclaration) { |
| MethodDeclaration method = (MethodDeclaration) node; |
| List statements = method.getStatements(); |
| boolean inStatement = false; |
| if (statements != null) { |
| int length = statements.size(); |
| for (int i = 0; i < length; i++) { |
| ASTNode nde = (ASTNode) statements.get(i); |
| if (nde.sourceStart() <= position |
| && nde.sourceEnd() >= position) { |
| inStatement = true; |
| parseBlockStatements(nde, method, position); |
| } |
| } |
| } |
| if (!inStatement) { |
| this.handleNotInElement(method, position); |
| } |
| } else { |
| visitElements(node, position); |
| } |
| } |
| |
| public class CompletionVisitor extends ASTVisitor { |
| protected int position; |
| protected ModuleDeclaration module; |
| |
| public CompletionVisitor(int position, ModuleDeclaration module) { |
| this.position = position; |
| this.module = module; |
| } |
| |
| @Override |
| public boolean visit(Statement s) throws Exception { |
| if (s.sourceStart() <= position && s.sourceEnd() >= position) { |
| for (int i = 0; i < extensions.length; i++) { |
| extensions[i].visit(s, TclCompletionParser.this, position); |
| } |
| if (s instanceof TclStatement) { |
| ASTNode inNode = TclParseUtil.getScopeParent(module, s); |
| TclCompletionParser.this.parseBlockStatements(s, inNode, |
| position); |
| } |
| if (s instanceof TclPackageDeclaration) { |
| TclPackageDeclaration decl = (TclPackageDeclaration) s; |
| if (decl.getNameStart() <= position |
| && position <= decl.getNameEnd()) { |
| ASTNode inNode = TclParseUtil.getScopeParent(module, s); |
| assistNodeParent = inNode; |
| throw new CompletionNodeFound(decl, |
| null/* |
| * ((TypeDeclaration ) inNode).scope |
| */); |
| } |
| } |
| } |
| return super.visit(s); |
| } |
| |
| @Override |
| public boolean visit(Expression s) throws Exception { |
| if (s.sourceStart() <= position && s.sourceEnd() >= position) { |
| if (s instanceof TclBlockExpression) { |
| ASTNode inNode = TclParseUtil.getScopeParent(module, s); |
| TclBlockExpression block = (TclBlockExpression) s; |
| List ss = block.parseBlockSimple(); |
| if (ss != null) { |
| int slen = ss.size(); |
| for (int u = 0; u < slen; ++u) { |
| ASTNode n = (ASTNode) ss.get(u); |
| if (n != null && n.sourceStart() <= position |
| && n.sourceEnd() >= position) { |
| parseBlockStatements(n, inNode, position); |
| } |
| } |
| } |
| } |
| for (int i = 0; i < extensions.length; i++) { |
| extensions[i].visit(s, TclCompletionParser.this, position); |
| } |
| } |
| return super.visit(s); |
| } |
| |
| @Override |
| public boolean endvisit(Expression s) throws Exception { |
| if (s instanceof Block && s.sourceStart() <= position |
| && s.sourceEnd() >= position) { |
| // We are in block, and no in node completion are done. |
| String[] keywords = checkKeywords("", MODULE); |
| ASTNode inNode = TclParseUtil.getScopeParent(module, s); |
| ASTNode nde = new CompletionOnKeywordOrFunction("", inNode, s, |
| keywords); |
| assistNodeParent = inNode; |
| throw new CompletionNodeFound(nde, null); |
| } |
| return super.endvisit(s); |
| } |
| }; |
| |
| protected CompletionVisitor createCompletionVisitor(int position) { |
| return new CompletionVisitor(position, this.getModule()); |
| } |
| |
| private void visitElements(ASTNode node, int position) { |
| if (!(node instanceof TclStatement)) { |
| CompletionVisitor visitor = createCompletionVisitor(position); |
| try { |
| node.traverse(visitor); |
| } catch (CompletionNodeFound e) { |
| throw e; |
| } catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| private boolean checkVariableWithoutDollarCompletion(TclStatement statement, |
| int position) { |
| // TODO: Add more compecated check. |
| Expression e = statement.getAt(0); |
| if (e instanceof SimpleReference) { |
| SimpleReference ref = (SimpleReference) e; |
| String name = ref.getName(); |
| if (name.equals("set")) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public String[] checkKeywords(String completionToken, int type) { |
| String[] keywords = TclKeywordsManager.getKeywords(type); |
| // TODO: Possible require cases. |
| if (type == MODULE || type == FUNCTION || type == NAMESPACE |
| || type == EXEC_EXPRESSION) { |
| // Suppose we can handle all keywords. |
| String[] kw = new String[keywords.length]; |
| for (int i = 0; i < keywords.length; ++i) { |
| kw[i] = keywords[i]; |
| } |
| return kw; |
| } |
| return null; |
| } |
| |
| public void setAssistNodeParent(ASTNode prevParent) { |
| this.assistNodeParent = prevParent; |
| } |
| } |