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); } } }