/*******************************************************************************
* Copyright (c) 2009, 2011 Sierra Wireless 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
*
* Contributors:
* Sierra Wireless - initial API and implementation
* Kevin KIN-FOO <kkinfoo@sierrawireless.com>
*******************************************************************************/
package org.eclipse.koneki.ldt.ui.internal.editor.completion;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.eclipse.dltk.codeassist.ScriptCompletionEngine;
import org.eclipse.dltk.compiler.env.IModuleSource;
import org.eclipse.dltk.compiler.util.Util;
import org.eclipse.dltk.core.CompletionProposal;
import org.eclipse.dltk.core.IField;
import org.eclipse.dltk.core.IMember;
import org.eclipse.dltk.core.IMethod;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.koneki.ldt.core.LuaConstants;
import org.eclipse.koneki.ldt.core.internal.ast.models.LuaASTModelUtils;
import org.eclipse.koneki.ldt.core.internal.ast.models.LuaASTUtils;
import org.eclipse.koneki.ldt.core.internal.ast.models.LuaASTUtils.Definition;
import org.eclipse.koneki.ldt.core.internal.ast.models.LuaASTUtils.TypeResolution;
import org.eclipse.koneki.ldt.core.internal.ast.models.api.Item;
import org.eclipse.koneki.ldt.core.internal.ast.models.api.RecordTypeDef;
import org.eclipse.koneki.ldt.core.internal.ast.models.common.LuaSourceRoot;
import org.eclipse.koneki.ldt.ui.internal.Activator;
public class LuaCompletionEngine extends ScriptCompletionEngine {
@Override
public void complete(IModuleSource module, int position, int k) {
// extract source module
final IModelElement modelElement = module.getModelElement();
if (!(modelElement instanceof ISourceModule)) {
Activator.logWarning("Unable to perform completion proposal. Module [" + module.getFileName() + "] has not source module associated."); //$NON-NLS-1$//$NON-NLS-2$
return;
}
ISourceModule sourceModule = (ISourceModule) modelElement;
// Retrieve start position of word current user is typing
String start = getWordStarting(module.getSourceContents(), position);
this.requestor.beginReporting();
if (start.contains(".") || start.contains(":")) { //$NON-NLS-1$//$NON-NLS-2$
// Select between module fields if completion is asked after a module reference
final List<String> ids = new ArrayList<String>();
Character lastOperator = getExpressionIdentifiers(start, ids);
addFields(sourceModule, ids, position, lastOperator, start);
} else {
// Search local declaration in AST
addLocalDeclarations(sourceModule, start, position);
// Search global declaration in DLTK model
addGlobalDeclarations(sourceModule, start, position);
// Add keywords
addKeywords(start, position);
}
}
private void addGlobalDeclarations(ISourceModule sourceModule, String start, int cursorPosition) {
// get all global variable which start by the string "start"
List<Definition> globalvars = LuaASTUtils.getAllGlobalVarsDefinition(sourceModule, start);
// for each global var, get the corresponding model element and create the proposal
for (Definition definition : globalvars) {
IMember member = LuaASTModelUtils.getIMember(definition.getModule(), definition.getItem());
if (member != null)
createMemberProposal(member, cursorPosition - start.length(), cursorPosition);
}
}
private void addKeywords(String start, int cursorPosition) {
// TODO key word should be define in a static attribute
String[] keywords = new String[] { "and", "break", "do", "else", "elseif", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
"end", "false", "for", "function", "if",//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
"in", "local", "nil", "not", "or",//$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
"repeat", "return", "then", "true", "until", "while" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
// create proposal for each keyword
for (int j = 0; j < keywords.length; j++) {
if (start.isEmpty() || keywords[j].startsWith(start)) {
createKeyWordProposal(keywords[j], cursorPosition - start.length(), cursorPosition);
}
}
}
private void addLocalDeclarations(ISourceModule sourceModule, String start, int cursorPosition) {
// get lua source root
LuaSourceRoot luaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(sourceModule);
if (luaSourceRoot == null)
return;
// find all local vars and create corresponding proposal
Collection<Item> localVars = LuaASTUtils.getLocalVars(luaSourceRoot, cursorPosition - start.length(), start);
for (Item var : localVars) {
IMember member = LuaASTModelUtils.getIMember(sourceModule, var);
if (member != null)
createMemberProposal(member, cursorPosition - start.length(), cursorPosition);
}
}
private void addFields(final ISourceModule initialSourceModule, final List<String> ids, int position, Character lastOperator, String start) {
if (ids.size() < 2)
return;
// get the closest definition with the name of the first element of ids
// we support only Identifier root for now.
final String rootIdentifierName = ids.get(0);
final LuaSourceRoot luaSourceRoot = LuaASTModelUtils.getLuaSourceRoot(initialSourceModule);
Item rootItem = LuaASTUtils.getClosestLocalVar(luaSourceRoot, rootIdentifierName, position - start.length());
ISourceModule itemSourceModule = initialSourceModule;
if (rootItem == null) {
// try to find a global
Definition globalVarDefinition = LuaASTUtils.getGlobalVarDefinition(initialSourceModule, rootIdentifierName);
if (globalVarDefinition == null)
return;
rootItem = globalVarDefinition.getItem();
itemSourceModule = globalVarDefinition.getModule();
}
// resolve Item Type
TypeResolution typeResolution = LuaASTUtils.resolveType(itemSourceModule, rootItem.getType());
if (typeResolution == null || !(typeResolution.getTypeDef() instanceof RecordTypeDef))
return;
// found type of the last bigger complete index
// (e.g. for identifier.field1.field2.f, get the type of identifier.field1.field2)
RecordTypeDef currentRecordTypeDef = (RecordTypeDef) typeResolution.getTypeDef();
ISourceModule currentSourceModule = typeResolution.getModule();
for (int i = 1; i < ids.size() - 1; i++) {
// check if the current type
String fieldname = ids.get(i);
Item item = currentRecordTypeDef.getFields().get(fieldname);
// we could resolve the type of this field we stop the research
if (item == null)
return;
// resolve the type
typeResolution = LuaASTUtils.resolveType(currentSourceModule, item.getType());
// we are interested only by record type
if (typeResolution == null || !(typeResolution.getTypeDef() instanceof RecordTypeDef))
return;
currentRecordTypeDef = (RecordTypeDef) typeResolution.getTypeDef();
currentSourceModule = typeResolution.getModule();
}
// get all the field of the complete index
try {
IType iType = LuaASTModelUtils.getIType(currentSourceModule, currentRecordTypeDef);
IModelElement[] moduleFields = iType.getChildren();
// get field name
final String fieldName = ids.get(ids.size() - 1);
// search field
for (final IModelElement field : moduleFields) {
if ((field instanceof IField && lastOperator == '.') || field instanceof IMethod) {
final boolean goodStart = field.getElementName().toLowerCase().startsWith(fieldName.toLowerCase());
final boolean nostart = fieldName.isEmpty();
if (goodStart || nostart) {
createMemberProposal((IMember) field, position - fieldName.length(), position, lastOperator);
}
}
}
} catch (ModelException e) {
Activator.logWarning("Unable to get model element.", e); //$NON-NLS-1$
}
}
// I'm unable to handle spaces in composed identifiers like 'someTable . someField'
private String getWordStarting(final String content, final int position) {
// manage inconsistent parameters
if (position <= 0 || position > content.length())
return Util.EMPTY_STRING;
// search the begin on the string sequence to autocomplete
int currentPosition = position;
int lastValidPosition = position;
boolean lastCharIsIndex = false;
boolean finish = false;
do {
currentPosition--;
final char currentChar = content.charAt(currentPosition);
final boolean isInvokeChar = currentChar == ':';
final boolean isIndexChar = currentChar == '.';
final boolean isIdentifierPart = Character.isLetterOrDigit(currentChar) || currentChar == '_';
// we stop if we found a character which is neiter a identifier part or an operator
// or if we found the concatenation character (..)
if (lastCharIsIndex && isIndexChar) { // we found a the .. char
lastValidPosition = lastValidPosition + 1;
finish = true;
} else if (isIdentifierPart || isIndexChar || isInvokeChar) { // we found a valid char
lastValidPosition = currentPosition;
lastCharIsIndex = isIndexChar;
} else {
finish = true;
}
// if we are at the end of the file it's finish too
} while (!finish && currentPosition > 0);
if (lastValidPosition >= position)
return Util.EMPTY_STRING;
return content.substring(lastValidPosition, position);
}
private Character getExpressionIdentifiers(final String composedId, List<String> result) {
StringBuffer stringToParse = new StringBuffer(composedId);
StringBuffer nextId = new StringBuffer();
Character lastOperator = '\0'; // we support only if invoke is the last operator
for (int i = 0; i < stringToParse.length(); i++) {
Character character = stringToParse.charAt(i);
if (!(character == '.') && !(character == ':')) {
// if it's not an operator then append the next char
nextId.append(character);
} else {
// we have an operator
// don't allow 2 sucesssive operator
if (nextId.length() == 0)
return null;
// we support only if invoke is the last operator
if (lastOperator == ':' && character == ':')
return null;
// store value to next validation
lastOperator = character;
// store previous value
result.add(nextId.toString());
nextId = new StringBuffer();
}
}
result.add(nextId.toString());
return lastOperator;
}
private void createKeyWordProposal(String keyword, int startIndex, int endIndex) {
CompletionProposal proposal = CompletionProposal.create(CompletionProposal.KEYWORD, 0);
proposal.setRelevance(1);
proposal.setName(keyword);
proposal.setCompletion(keyword);
proposal.setReplaceRange(startIndex, endIndex);
this.requestor.accept(proposal);
}
private void createMemberProposal(IMember member, int startIndex, int endIndex) {
createMemberProposal(member, startIndex, endIndex, '\0');
}
private void createMemberProposal(IMember member, int startIndex, int endIndex, char operator) {
try {
CompletionProposal proposal = null;
switch (member.getElementType()) {
case IModelElement.METHOD:
// create method proposal
proposal = CompletionProposal.create(CompletionProposal.METHOD_REF, 0);
IMethod method = (IMethod) member;
if (operator == ':') {
// manage the invoke case
String[] parameterNames = method.getParameterNames();
if (parameterNames.length == 0)
return;
if (!parameterNames[0].equals(LuaConstants.SELF_PARAMETER))
return;
String[] parameterNamesWithoutFirstOne = Arrays.copyOfRange(parameterNames, 1, parameterNames.length);
proposal.setParameterNames(parameterNamesWithoutFirstOne);
} else {
proposal.setParameterNames(method.getParameterNames());
}
break;
case IModelElement.FIELD:
proposal = CompletionProposal.create(CompletionProposal.FIELD_REF, 0);
break;
case IModelElement.TYPE:
proposal = CompletionProposal.create(CompletionProposal.TYPE_REF, 0);
break;
default:
return;
}
proposal.setFlags(member.getFlags());
proposal.setModelElement(member);
proposal.setName(member.getElementName());
proposal.setCompletion(member.getElementName());
proposal.setReplaceRange(startIndex, endIndex);
proposal.setRelevance(2);
this.requestor.accept(proposal);
} catch (ModelException e) {
Activator.logWarning(Messages.LuaCompletionEngineProblemProcessingGlobals, e);
return;
}
}
}