package tk.eclipse.plugin.jseditor.editors;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ContextInformation;
import org.eclipse.jface.text.contentassist.ContextInformationValidator;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.templates.TemplateContextType;
import org.eclipse.swt.graphics.Image;
import tk.eclipse.plugin.htmleditor.HTMLPlugin;
import tk.eclipse.plugin.htmleditor.HTMLProjectParams;
import tk.eclipse.plugin.htmleditor.HTMLUtil;
import tk.eclipse.plugin.htmleditor.template.HTMLTemplateAssistProcessor;
import tk.eclipse.plugin.htmleditor.template.HTMLTemplateManager;
import tk.eclipse.plugin.htmleditor.template.JavaScriptContextType;
import tk.eclipse.plugin.jseditor.launch.JavaScriptLibraryTable;
/**
* IContentAssistProcessor implementation for JavaScriptEditor.
*
* @author Naoki Takezoe
*/
public class JavaScriptAssistProcessor extends HTMLTemplateAssistProcessor { /* implements IContentAssistProcessor {*/
private static List<AssistInfo> staticAssistInfo = new ArrayList<AssistInfo>();
// assist proposal types
private static final int VARIABLE = 0;
private static final int FUNCTION = 1;
private static final int KEYWORD = 2;
private static final int OBJECT = 3;
private static final int UNDEF = 99;
private static final String[] GLOBAL_PROPERTIES = {
"Infinity", "NaN", "undefined"
};
private static final String[] GLOBAL_FUNCTIONS = {
"decodeURI()", "decodeURIComponent()",
"encodeURI()", "encodeURIComponent()",
"escape()", "unescape()", "eval()", "isNaN()",
"parseFloat()", "parseInt()", "taint()", "untaint()",
};
private static final String[] CLASSES = {
"Arguments", "Array", "Boolean", "Date", "Error",
"Function", "Math", "NativeError", "Number", "Object", "RegExp", "String"
};
private static Map<String, AssistInfo[]> STATIC_MEMBERS = new HashMap<String, AssistInfo[]>();
static {
// // add keyword to static assist informations
// for(int i=0;i<JavaScriptScanner.KEYWORDS.length;i++){
// staticAssistInfo.add(new AssistInfo(KEYWORD, JavaScriptScanner.KEYWORDS[i]));
// }
for(int i=0;i<GLOBAL_PROPERTIES.length;i++){
staticAssistInfo.add(new AssistInfo(VARIABLE, GLOBAL_PROPERTIES[i], GLOBAL_PROPERTIES[i].length()));
}
for(int i=0;i<GLOBAL_FUNCTIONS.length;i++){
staticAssistInfo.add(new AssistInfo(FUNCTION, GLOBAL_FUNCTIONS[i], GLOBAL_FUNCTIONS[i].length()-1));
}
for(int i=0;i<CLASSES.length;i++){
staticAssistInfo.add(new AssistInfo(OBJECT, CLASSES[i], CLASSES[i], CLASSES[i].length()));
}
AssistInfo[] math = {
new AssistInfo(VARIABLE, "E - Math", "E"),
new AssistInfo(VARIABLE, "LN10 - Math", "LN10"),
new AssistInfo(VARIABLE, "LN2 - Math", "LN2"),
new AssistInfo(VARIABLE, "LOG10E - Math", "LOG10E"),
new AssistInfo(VARIABLE, "LOG2E - Math", "LOG2E"),
new AssistInfo(VARIABLE, "PI - Math", "PI"),
new AssistInfo(VARIABLE, "SQRT1_2 - Math", "SQRT1_2"),
new AssistInfo(VARIABLE, "SQRT2 - Math", "SQRT2"),
new AssistInfo(FUNCTION, "abs() - Math", "abs()"),
new AssistInfo(FUNCTION, "acos() - Math", "acos()"),
new AssistInfo(FUNCTION, "asin() - Math", "asin()"),
new AssistInfo(FUNCTION, "atan() - Math", "atan()"),
new AssistInfo(FUNCTION, "atan2() - Math", "atan2()"),
new AssistInfo(FUNCTION, "ceil() - Math", "ceil()"),
new AssistInfo(FUNCTION, "cos() - Math", "cos()"),
new AssistInfo(FUNCTION, "exp() - Math", "exp()"),
new AssistInfo(FUNCTION, "floor() - Math", "floor()"),
new AssistInfo(FUNCTION, "log() - Math", "log()"),
new AssistInfo(FUNCTION, "max() - Math", "max()"),
new AssistInfo(FUNCTION, "min() - Math", "min()"),
new AssistInfo(FUNCTION, "pow() - Math", "pow()"),
new AssistInfo(FUNCTION, "random() - Math", "random()"),
new AssistInfo(FUNCTION, "round() - Math", "round()"),
new AssistInfo(FUNCTION, "sin() - Math", "sin()"),
new AssistInfo(FUNCTION, "sqrt() - Math", "sqrt()"),
new AssistInfo(FUNCTION, "tan() - Math", "tan()"),
};
STATIC_MEMBERS.put("Math", math);
AssistInfo[] object = {
new AssistInfo(VARIABLE, "constructor - Object", "constructor"),
new AssistInfo(VARIABLE, "prototype - Object", "prototype"),
new AssistInfo(FUNCTION, "hasOwnProperty() - Object", "hasOwnProperty()"),
// new AssistInfo(FUNCTION, "eval() - Object", "eval()"),
new AssistInfo(FUNCTION, "hasOwnProperty() - Object", "hasOwnProperty()"),
new AssistInfo(FUNCTION, "isPrototyprOf() - Object", "isPrototyprOf()"),
new AssistInfo(FUNCTION, "propertyIsEnumerable() - Object", "propertyIsEnumerable()"),
new AssistInfo(FUNCTION, "toLocaleString() - Object", "toLocaleString()"),
new AssistInfo(FUNCTION, "toSource() - Object", "toSource()"),
new AssistInfo(FUNCTION, "toString() - Object", "toString()"),
new AssistInfo(FUNCTION, "unwatch() - Object", "unwatch()"),
new AssistInfo(FUNCTION, "valueOf() - Object", "valueOf()"),
new AssistInfo(FUNCTION, "watch() - Object","watch()"),
};
STATIC_MEMBERS.put("Object", object);
}
private List<AssistInfo> functions = new ArrayList<AssistInfo>();
/**
* Returns source code to parse from <code>ITextViewer</code>.
* <p>
* If you want to use this class with the your own editor
* which supports the document contains JavaScript such as HTML/JSP,
* override this method as returns only JavaScript code.
*
* @param viewer <code>ITextViewer</code>
* @return JavaScript source code
*/
protected String getSource(ITextViewer viewer){
return viewer.getDocument().get();
}
@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) {
List<ICompletionProposal> proposal = new ArrayList<ICompletionProposal>();
String source = getSource(viewer);
String[] words = getLastWord(viewer, offset);
String last = words[0];
String word = words[1];
List<String> addedStrings = new ArrayList<String>();
if(last.endsWith(".")){
String objName = last.substring(0, last.length()-1);
AssistInfo[] info = STATIC_MEMBERS.get(objName);
if(info==null && !isNumeric(objName)){
info = STATIC_MEMBERS.get("Object");
}
if(info!=null){
for(int i=0;i<info.length;i++){
if(info[i].replaceString.startsWith(word)){
proposal.add(info[i].createCompletionProposal(offset, word));
}
}
}
} else {
for(int i=0;i<staticAssistInfo.size();i++){
AssistInfo info = staticAssistInfo.get(i);
if(info.replaceString.startsWith(word)){
proposal.add(info.createCompletionProposal(offset, word));
}
}
JavaScriptModel model = new JavaScriptModel(source);
JavaScriptContext context = model.getContextFromOffset(offset);
if(context!=null){
JavaScriptElement[] children = context.getVisibleElements();
for(int i=0;i<children.length;i++){
if(children[i].getName().startsWith(word)){
int type = UNDEF;
int position = 0;
String replace = null;
if(children[i] instanceof JavaScriptFunction){
type = FUNCTION;
replace = children[i].getName() + "()";
if(((JavaScriptFunction)children[i]).getArguments().length()==0){
position = replace.length();
} else{
position = replace.length() - 1;
}
} else if(children[i] instanceof JavaScriptVariable){
type = VARIABLE;
replace = children[i].getName();
position = replace.length();
}
if(!addedStrings.contains(replace)){
proposal.add(new CompletionProposal(
replace, offset - word.length(), word.length(),
position, getImageFromType(type),
children[i].toString(), null, null));
addedStrings.add(replace);
}
}
}
}
}
for(int i=0;i<functions.size();i++){
AssistInfo info = functions.get(i);
if(info.replaceString.startsWith(word) && !addedStrings.contains(info.replaceString)){
proposal.add(info.createCompletionProposal(offset, word));
addedStrings.add(info.replaceString);
}
}
ICompletionProposal[] templates = super.computeCompletionProposals(viewer, offset);
for(int i=0;i<templates.length;i++){
proposal.add(templates[i]);
}
// sort
HTMLUtil.sortCompilationProposal(proposal);
ICompletionProposal[] prop = proposal.toArray(new ICompletionProposal[proposal.size()]);
return prop;
}
/**
* Returns <code>Image</code> from the assist proposal type.
*
* @param type KEYWORD, VARIABLE or FUNCTION
* @return Image from the type
*/
private static Image getImageFromType(int type){
switch(type){
case KEYWORD:
return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_VALUE);
case VARIABLE:
return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_VARIABLE);
case FUNCTION:
return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_FUNCTION);
case OBJECT:
return HTMLPlugin.getDefault().getImageRegistry().get(HTMLPlugin.ICON_CLASS);
default:
return null;
}
}
private boolean isNumeric(String value){
for(int i=0;i<value.length();i++){
char c = value.charAt(i);
if(c < '0' || c > '9'){
return false;
}
}
return true;
}
/**
* Cuts out the last word of caret position.
*
* @param viewer ITextViewer
* @param offset the caret offset
* @return the last word of caret position
*/
protected String[] getLastWord(ITextViewer viewer, int offset){
String source = viewer.getDocument().get();
source = source.substring(0, offset);
String last = "";
int start = source.lastIndexOf('\n');
if(start==-1){
start = source.lastIndexOf('\r');
}
if(start==-1){
start = 0;
}
StringBuffer sb = new StringBuffer();
for(int i=start;i < offset;i++){
char c = source.charAt(i);
if(Character.isWhitespace(c) || !Character.isJavaIdentifierPart(c)){
if(sb.length()!=0){
if(c=='.'){
sb.append(c);
}
last = sb.toString();
sb.setLength(0);
}
} else {
sb.append(c);
}
}
return new String[]{last, sb.toString()};
}
/**
* The structure for assist informations.
*/
private static class AssistInfo {
private int type;
private int position;
private String displayString;
private String replaceString;
public AssistInfo(int type, String string){
this(type, string, string);
}
public AssistInfo(int type, String string, int position){
this(type, string, string, position);
}
public AssistInfo(int type, String displayString, String replaceString){
this(type, displayString, replaceString,
replaceString.endsWith("()") ? replaceString.length()-1 : replaceString.length());
}
public AssistInfo(int type, String displayString, String replaceString, int position){
this.type = type;
this.displayString = displayString;
this.replaceString = replaceString;
this.position = position;
}
public CompletionProposal createCompletionProposal(int offset, String word){
return new CompletionProposal(
replaceString, offset - word.length(), word.length(),
position, getImageFromType(type),
displayString, null, null);
}
}
@Override
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) {
return new ContextInformation[0];
}
@Override
public char[] getCompletionProposalAutoActivationCharacters() {
return new char[0];
}
@Override
public char[] getContextInformationAutoActivationCharacters() {
return new char[0];
}
@Override
public String getErrorMessage() {
return "error";
}
@Override
public IContextInformationValidator getContextInformationValidator() {
return new ContextInformationValidator(this);
}
@Override
protected TemplateContextType getContextType(ITextViewer viewer, IRegion region) {
HTMLTemplateManager manager = HTMLTemplateManager.getInstance();
return manager.getContextTypeRegistry().getContextType(JavaScriptContextType.CONTEXT_TYPE);
}
/**
* Updates internal informations.
*
* @param file the editing file
*/
public void update(IFile file){
try {
HTMLProjectParams params = new HTMLProjectParams(file.getProject());
String[] javaScripts = params.getJavaScripts();
IWorkspaceRoot wsroot = ResourcesPlugin.getWorkspace().getRoot();
functions.clear();
for(int i=0;i<javaScripts.length;i++){
InputStream in = null;
if(javaScripts[i].startsWith(JavaScriptLibraryTable.PREFIX)){
IResource resource = wsroot.findMember(javaScripts[i].substring(JavaScriptLibraryTable.PREFIX.length()));
if(resource!=null && resource instanceof IFile && resource.exists()){
in = ((IFile)resource).getContents();
}
} else {
in = new FileInputStream(javaScripts[i]);
}
String source = new String(HTMLUtil.readStream(in));
JavaScriptModel model = new JavaScriptModel(source);
JavaScriptElement[] elements = model.getChildren();
for(int j=0;j<elements.length;j++){
if(elements[j] instanceof JavaScriptFunction){
String replace = elements[j].getName() + "()";
int position = 0;
if(((JavaScriptFunction)elements[j]).getArguments().length()==0){
position = replace.length();
} else{
position = replace.length() - 1;
}
functions.add(new AssistInfo(
FUNCTION, elements[j].toString(), replace, position));
}
}
}
} catch(Exception ex){
HTMLPlugin.logException(ex);
}
}
}