/** * This file Copyright (c) 2005-2008 Aptana, Inc. This program is * dual-licensed under both the Aptana Public License and the GNU General * Public license. You may elect to use one or the other of these licenses. * * This program is distributed in the hope that it will be useful, but * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or * NONINFRINGEMENT. Redistribution, except as permitted by whichever of * the GPL or APL you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or modify this * program under the terms of the GNU General Public License, * Version 3, as published by the Free Software Foundation. You should * have received a copy of the GNU General Public License, Version 3 along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Aptana provides a special exception to allow redistribution of this file * with certain other free and open source software ("FOSS") code and certain additional terms * pursuant to Section 7 of the GPL. You may view the exception and these * terms on the web at http://www.aptana.com/legal/gpl/. * * 2. For the Aptana Public License (APL), this program and the * accompanying materials are made available under the terms of the APL * v1.0 which accompanies this distribution, and is available at * http://www.aptana.com/legal/apl/. * * You may view the GPL, Aptana's exception and additional terms, and the * APL in the file titled license.html at the root of the corresponding * plugin containing this source file. * * Any modifications to this file must keep this entire header intact. */ package com.aptana.ide.editor.js; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import com.aptana.ide.core.ui.CoreUIUtils; import com.aptana.ide.editor.js.environment.LexemeBasedEnvironmentLoader; import com.aptana.ide.editor.js.lexing.JSTokenTypes; import com.aptana.ide.editor.js.runtime.Environment; import com.aptana.ide.editor.js.runtime.IFunction; import com.aptana.ide.editor.js.runtime.IObject; import com.aptana.ide.editor.js.runtime.IScope; import com.aptana.ide.editor.js.runtime.JSFunction; import com.aptana.ide.editor.js.runtime.JSScope; import com.aptana.ide.editor.js.runtime.ObjectBase; import com.aptana.ide.editor.js.runtime.OrderedObject; import com.aptana.ide.editor.js.runtime.OrderedObjectCollection; import com.aptana.ide.editor.js.runtime.Property; import com.aptana.ide.editor.scriptdoc.parsing.FunctionDocumentation; import com.aptana.ide.editor.scriptdoc.parsing.PropertyDocumentation; import com.aptana.ide.editors.managers.FileContextManager; import com.aptana.ide.editors.unified.ChildOffsetMapper; import com.aptana.ide.editors.unified.FileService; import com.aptana.ide.editors.unified.IChildOffsetMapper; import com.aptana.ide.editors.unified.IFileService; import com.aptana.ide.editors.unified.IParentOffsetMapper; import com.aptana.ide.editors.unified.IUnifiedEditor; import com.aptana.ide.lexer.Lexeme; import com.aptana.ide.lexer.LexemeList; import com.aptana.ide.lexer.TokenCategories; import com.aptana.ide.metadata.IDocumentation; import com.aptana.ide.parsing.CodeLocation; import com.aptana.ide.parsing.ICodeLocation; import com.aptana.ide.parsing.IParseState; /** * @author Robin Debreuil */ public class JSOffsetMapper extends ChildOffsetMapper implements IChildOffsetMapper { /** MODE_NORMAL for assist type */ public static final String MODE_NORMAL = "__mode_normal"; //$NON-NLS-1$ /** MODE_NEW for assist type */ public static final String MODE_NEW = "__mode_new"; //$NON-NLS-1$ /** MODE_INVOKING for assist type */ public static final String MODE_INVOKING = "__mode_invoking"; //$NON-NLS-1$ /** MODE_INVOKING for assist type */ public static final String MODE_STRING = "__mode_string"; //$NON-NLS-1$ /** NOT_AN_IDENTIFIER marker for code assist */ public static final String NOT_AN_IDENTIFIER = "__not_an_identifier"; //$NON-NLS-1$ /** NOT_INVOKING marker for code assist */ public static final String NOT_INVOKING = "__not_invoking"; //$NON-NLS-1$ /** An undefined object * */ private static IObject undef = ObjectBase.UNDEFINED; private String mode = MODE_NORMAL; private JSFileLanguageService fileLangService; private LexemeBasedEnvironmentLoader loader; /** * @param parent */ public JSOffsetMapper(IParentOffsetMapper parent) { super(parent); } private static Environment getEnvironment() { return (Environment) JSLanguageEnvironment.getInstance().getRuntimeEnvironment(); } /** * Looks up an object in the global table based on its full name and returns the final object (not its return type). * This uses the special notation (from getNameHash) for full names. * * @param fullname * Full name of the object to lookup * @param scope * The scope to start looking from - this allows lookup from inside a function scope. * @param offset * @param jsfe * @return Returns the 'return type' of the given name. */ public static Property lookupTypeFromNameHash(String fullname, IScope scope, int offset, JSOffsetMapper jsfe) { synchronized (getEnvironment()) { // TODO: combine environment lookup with docs from the environment, // which will cause this method to go away. if (fullname.length() == 0) { return null; } Property result = null; // split the full name, then look up each segment, find return types if // needed as we parse the name. String[] names = fullname.split("\\."); //$NON-NLS-1$ IObject obj = scope; for (int i = 0; i < names.length; i++) { String name = names[i]; boolean isMethodCall = false; if (name.endsWith("()")) //$NON-NLS-1$ { isMethodCall = true; name = name.substring(0, name.length() - 2); } if (i == 0 && names.length > 1) { obj = scope.getVariableValue(name, jsfe.getFileIndex(), offset).getInstance(getEnvironment(), jsfe.getFileIndex(), offset); } else if (i < names.length - 1 && names.length > 1) { obj = obj.getPropertyValue(name, jsfe.getFileIndex(), offset).getInstance(getEnvironment(), jsfe.getFileIndex(), offset); } else { result = getPropertyInScope(obj, name); } if (obj == undef) { return null; } // now we have the new object, check if we need to use the return // type or the declared type // if(i < names.length - 1) // { if (isMethodCall) { IDocumentation doc = obj.getDocumentation(); if (doc instanceof FunctionDocumentation) { FunctionDocumentation fdoc = (FunctionDocumentation) doc; String[] rettypes = fdoc.getReturn().getTypes(); if (rettypes.length > 0) { // todo: handle multiple return types in the future // todo: handle [optional] and ... (params) return types String rettype = rettypes[0]; // method calls always return an instance, so we look on prototype obj = jsfe.lookupReturnTypeFromNameHash(rettype, jsfe.getGlobal()); if (obj != null) { obj = obj.getPropertyValue("prototype", jsfe.getFileIndex(), offset); //$NON-NLS-1$ } } } else { // look up from environment // we can't use regular command nodes here because we are usually in error // state at this point // if(obj instanceof JSFunction) // { // TODO FunctionNode is in Pro...need to ask Kevin about this // JSFunction fnObj = (JSFunction)obj; // IRange range = fnObj.getRange(); // if(range != null && range instanceof FunctionNode) // { // FunctionNode fn = (FunctionNode)range; // obj = fn.invoke(jsfe.getEnvironment().environment, new IObject[0], // jsfe.getFileIndex(), fn); // } // // CommandNode body = fnObj.getBody(); // if(body != null) // { // CommandNode cn = body.getParentNode(); // if(cn != null && cn instanceof FunctionNode) // { // FunctionNode fn = (FunctionNode)cn; // // obj = fn.invoke(env, new IObject[0], jsfe.getFileIndex(), cn); // } // } // } } } // } } return result; } } /** * getIdentName * * @param position * @param lexemeList * @return String */ public static String getIdentName(int position, LexemeList lexemeList) { String name = ""; //$NON-NLS-1$ // backtrack over lexemes to find name - this can include parens and // dots and commas, but no args - eg - "Math.fn(,,).tostring()" while (position >= 0) { Lexeme curLexeme = lexemeList.get(position); // now continue to add to string switch (curLexeme.typeIndex) { case JSTokenTypes.IDENTIFIER: case JSTokenTypes.DOT: name = curLexeme.getText() + name; position--; break; // don't allow invokes on left for now // i.e. xx().document case JSTokenTypes.RPAREN: // case JSTokenTypes.LPAREN: // name = ""; position = -1; break; default: position = -1; // end backtrack loop break; } } return name; } /** * getPropertyInScope * * @param object * @param propName * @return Property */ public static Property getPropertyInScope(IObject object, String propName) { Property result = object.getProperty(propName); if (result != null || !(object instanceof IScope)) { return result; } IScope scope = ((IScope) object).getParentScope(); while (scope != null) { result = scope.getProperty(propName); if (result != null) { break; } scope = scope.getParentScope(); } return result; } /** * Returns the invocation mode (invoking, new, or normal). * * @param offset * The index to check the mode at. * @return Returns the invocation mode (invoking, new, or normal). */ public String getMode(int offset) { getArgIndexAndCalculateMode(); // need to call this to get mode in case // of fast parse sync issues return mode; } /** * Gets the full name of of an object that is at the passed offset. This is in the form "Math.abs().toString()", * even if there are arguments. * * @param lexemeIndex * - index of lexeme * @return Returns a hashed string name */ public String getNameHash(int lexemeIndex) { String name = NOT_AN_IDENTIFIER; int position = lexemeIndex; int parenCount = 0; int bracketCount = 0; boolean wasSeparator = true; int lastTokenType = -1; // backtrack over lexemes to find name - this can include parens and // dots and commas, but no args - eg - "Math.fn(,,).tostring()" while (position >= 0) { Lexeme curLexeme = getLexemeList().get(position); // reset name unless in a new stmt. if (name.equals(NOT_AN_IDENTIFIER)) { if (curLexeme.typeIndex == JSTokenTypes.NEW) { mode = MODE_NEW; name = MODE_NEW; break; } else if (curLexeme.typeIndex == JSTokenTypes.STRING) { name = MODE_STRING; } else { name = ""; //$NON-NLS-1$ } } // now continue to add to string switch (curLexeme.typeIndex) { case JSTokenTypes.NEW: mode = MODE_NEW; name = MODE_NEW + name; wasSeparator = true; position = 0; break; case JSTokenTypes.LPAREN: // this case only for opened but not yet closed parens // (so will need arg assist) // if(position == currentLexemeIndex) // { // return getArgAssistNameHash(); // } wasSeparator = true; if (parenCount == 0) // inside parens ( like "if(" ) doesn't trigger assist { if (position > 0) { Lexeme prevLex = getLexemeList().get(position - 1); if (prevLex.getCategoryIndex() == TokenCategories.KEYWORD && prevLex.typeIndex != JSTokenTypes.TYPEOF) { return name; } else { position = 0; break; } } } parenCount--; name = curLexeme.getText() + name; break; case JSTokenTypes.IDENTIFIER: if (lastTokenType != JSTokenTypes.IDENTIFIER) { name = curLexeme.getText() + name; wasSeparator = false; } else { // stop processing position = 0; } break; // case JSTokenTypes.COMMA: case JSTokenTypes.DOT: wasSeparator = true; name = curLexeme.getText() + name; break; case JSTokenTypes.RPAREN: wasSeparator = true; name = ")" + name; //$NON-NLS-1$ int startParenCount = parenCount; parenCount++; while (--position > 0) { Lexeme lx = getLexemeList().get(position); if (lx.typeIndex == JSTokenTypes.LPAREN) { parenCount--; } else if (lx.typeIndex == JSTokenTypes.RPAREN) { parenCount++; } if (startParenCount == parenCount) { name = "(" + name; //$NON-NLS-1$ break; } } break; case JSTokenTypes.LBRACKET: wasSeparator = true; if (bracketCount == 0 && position > 0) { position = 0; break; } bracketCount--; name = "[" + name; //$NON-NLS-1$ break; case JSTokenTypes.RBRACKET: wasSeparator = true; name = "]" + name; //$NON-NLS-1$ int startBracketCount = bracketCount; bracketCount++; while (--position > 0) { Lexeme lx = getLexemeList().get(position); if (lx.typeIndex == JSTokenTypes.LBRACKET) { bracketCount--; } else if (lx.typeIndex == JSTokenTypes.RBRACKET) { bracketCount++; } if (startBracketCount == bracketCount) { name = "[" + name; //$NON-NLS-1$ break; } } break; case JSTokenTypes.WHITESPACE: break; case JSTokenTypes.SEMICOLON: position = 0; break; default: // we need to add these as "do<ctrl space>" should show 'document' if (curLexeme.getCategoryIndex() == TokenCategories.KEYWORD) { if (wasSeparator) { name = curLexeme.getText() + name; } wasSeparator = false; } if (position > 0) { if (getLexemeList().get(position - 1).typeIndex != JSTokenTypes.DOT) { position = 0; } } else { position = 0; // end backtrack loop } break; } if (curLexeme.isAfterEOL()) { // FIXME if we start with a period we should backtrack (if an identifier or function is before it). // If we start with an identifier we should backtrack if a period is before it (ignoring // newline/whitespace) if (!name.startsWith(".")) //$NON-NLS-1$ break; } lastTokenType = curLexeme.typeIndex; position--; } // a leading dot is invalid if (name.startsWith(".")) //$NON-NLS-1$ { name = NOT_AN_IDENTIFIER; } return name; } /** * getArgAssistNameHash * * @return String */ public String getArgAssistNameHash() { if (this.getCurrentLexeme() == null) { return NOT_INVOKING; } String name = NOT_INVOKING; int position = getCurrentLexemeIndex(); int parenCount = 0; boolean wasSeparator = true; boolean foundSoloLParen = false; // find if we are in an invoke try { int curOffset = this.getCurrentLexeme().offset + 1; String src = this.getFileService().getSource(); int startLine = curOffset; if (startLine >= src.length()) { startLine = src.length() - 1; } // find startline without using doc (sometimes is null atm) for (; startLine > 0; startLine--) { if (src.charAt(startLine) == '\n') { startLine++; break; } } if (startLine < 0 || curOffset > src.length() - 1) { return NOT_INVOKING; // sanity check } if (startLine > curOffset) { return NOT_INVOKING; } char[] lineChars = src.substring(startLine, curOffset).toCharArray(); int left = 0; int right = 0; for (int i = 0; i < lineChars.length; i++) { if (lineChars[i] == '(') { left++; } else if (lineChars[i] == ')') { right++; } } if (left <= right) { return NOT_INVOKING; } } catch (Exception e) { return NOT_INVOKING; } // todo: this is new code just before alpha, remove later... // backtrack over lexemes to find name - this can include parens and // dots and commas, but no args - eg - "Math.fn(,,).tostring()" while (position >= 0) { Lexeme curLexeme = getLexemeList().get(position); // reset name unless in a new stmt. if (name.equals(NOT_INVOKING)) { name = ""; //$NON-NLS-1$ } // now continue to add to string switch (curLexeme.typeIndex) { case JSTokenTypes.LPAREN: // this case only for opened but not yet closed parens // (so will need arg assist) wasSeparator = true; if (parenCount == 0) // inside parens ( like "if(" ) doesn't trigger assist { foundSoloLParen = true; if (position > 0) { Lexeme prevLex = getLexemeList().get(position - 1); if (prevLex.getCategoryIndex() == TokenCategories.KEYWORD && prevLex.typeIndex != JSTokenTypes.TYPEOF) { return NOT_INVOKING; } } } else if (parenCount < 0) { position = 0; break; } name = curLexeme.getText() + name; parenCount--; break; case JSTokenTypes.IDENTIFIER: if (foundSoloLParen) { name = curLexeme.getText() + name; wasSeparator = false; } break; case JSTokenTypes.COMMA: if (foundSoloLParen) { position = 0; } else { wasSeparator = true; name = curLexeme.getText() + name; } break; case JSTokenTypes.DOT: if (foundSoloLParen) { wasSeparator = true; name = curLexeme.getText() + name; } break; case JSTokenTypes.RPAREN: if (foundSoloLParen) { wasSeparator = true; name = ")" + name; //$NON-NLS-1$ } int startParenCount = parenCount; parenCount++; while (--position > 0) { Lexeme lx = getLexemeList().get(position); if (lx.typeIndex == JSTokenTypes.LPAREN) { parenCount--; } else if (lx.typeIndex == JSTokenTypes.RPAREN) { parenCount++; } if (startParenCount == parenCount) { if (foundSoloLParen) { name = "(" + name; //$NON-NLS-1$ } break; } } break; case JSTokenTypes.WHITESPACE: break; default: // we need to add these as "do<ctrl space>" should show 'document' if (curLexeme.getCategoryIndex() == TokenCategories.KEYWORD) { if (wasSeparator && foundSoloLParen) { name = curLexeme.getText() + name; wasSeparator = false; } } else if (curLexeme.getCategoryIndex() == TokenCategories.LITERAL) { break; } position = 0; // end backtrack loop break; } if (curLexeme.isAfterEOL()) { break; } position--; } // a leading dot is invalid if (name.startsWith(".")) //$NON-NLS-1$ { name = NOT_INVOKING; } return name; } /** * Looks up an object in the global table based on its full name. This uses the special notation (from getNameHash) * for full names, and uses documentation return types and prototype/object lookup to discover return types. * * @param fullname * Full name of the object to lookup * @param scope * The scope to start looking from - this allows lookup from inside a function scope. * @return Returns the 'return type' of the given name. */ public IObject lookupReturnTypeFromNameHash(String fullname, IScope scope) { return lookupReturnTypeFromNameHash(fullname, scope, false); } /** * Looks up an object in the global table based on its full name. This uses the special notation (from getNameHash) * for full names, and uses documentation return types and prototype/object lookup to discover return types. * * @param fullname * Full name of the object to lookup * @param scope * The scope to start looking from - this allows lookup from inside a function scope. * @param searchForward * Looks forward in doc if part of name not found at current fileIndex/offset * @return Returns the 'return type' of the given name. */ public IObject lookupReturnTypeFromNameHash(String fullname, IScope scope, boolean searchForward) { synchronized (this.getFileService()) { // todo: combine environment lookup with docs from the environment, // which will cause this method to go away. if (fullname.length() == 0) { return null; } // split the full name, then look up each segment, find return types if // needed as we parse the name. String[] names = fullname.split("\\."); //$NON-NLS-1$ IObject obj = scope; int offset = 0; if (this.getCurrentLexeme() != null) { offset = this.getCurrentLexeme().offset; } int fileIndex = this.getFileIndex(); for (int i = 0; i < names.length; i++) { String name = names[i]; boolean isMethodCall = false; if (name.endsWith("()")) //$NON-NLS-1$ { isMethodCall = true; name = name.substring(0, name.length() - 2); } boolean isArrayCall = false; if (name.endsWith("[]")) //$NON-NLS-1$ { isArrayCall = true; name = name.substring(0, name.length() - 2); } if (i == 0) { if (name.equals("this") && obj instanceof IScope) //$NON-NLS-1$ { boolean hasDocReturn = false; IFunction enclFn = ((IScope) obj).getEnclosingFunction(); if (enclFn != null && enclFn instanceof JSFunction) { JSFunction fn = (JSFunction) enclFn; IDocumentation doc = fn.getDocumentation(); if (doc instanceof PropertyDocumentation) { PropertyDocumentation pdoc = (PropertyDocumentation) doc; if (pdoc instanceof FunctionDocumentation && ((FunctionDocumentation) pdoc).getIsConstructor()) { obj = fn.getPropertyValue("prototype", fileIndex, offset); //$NON-NLS-1$ } else { String rettype = fn.getMemberOf(); if (rettype != null && !rettype.equals("")) //$NON-NLS-1$ { hasDocReturn = true; if (rettype.indexOf(".") > -1) //$NON-NLS-1$ { obj = lookupNamespaceFromNameHash(rettype); } else { obj = lookupReturnTypeFromNameHash(rettype, getGlobal()); } if (obj != null) // new fix { obj = obj.getPropertyValue("prototype", fileIndex, offset); //$NON-NLS-1$ } } } } if (!hasDocReturn) { obj = fn.getGuessedMemberObject(); } } } else { obj = scope.getVariableValue(name, fileIndex, offset).getInstance(getEnvironment(), fileIndex, offset); if ((obj == null || obj == ObjectBase.UNDEFINED) && searchForward) { obj = scope.getVariableValue(name, Integer.MAX_VALUE, Integer.MAX_VALUE).getInstance( getEnvironment(), Integer.MAX_VALUE, Integer.MAX_VALUE); } // else // { // obj = scope.getPropertyValue(name, fileIndex, // offset).getInstance(environment, fileIndex, offset); // } } } else { IObject temp = obj.getPropertyValue(name, fileIndex, offset).getInstance(getEnvironment(), fileIndex, offset); if ((temp == null || temp == ObjectBase.UNDEFINED) && searchForward) { temp = obj.getPropertyValue(name, Integer.MAX_VALUE, Integer.MAX_VALUE).getInstance( getEnvironment(), Integer.MAX_VALUE, Integer.MAX_VALUE); } obj = temp; } if (obj == null || obj == ObjectBase.UNDEFINED) { return null; } // now we have the new object, check if we need to use the return // type or the declared type boolean isFunction = obj instanceof IFunction; if (isArrayCall || isMethodCall || (!isFunction && i == names.length - 1 && fullname.endsWith("."))) //$NON-NLS-1$ { IDocumentation doc = obj.getDocumentation(); if (doc instanceof PropertyDocumentation) { PropertyDocumentation pdoc = (PropertyDocumentation) doc; String[] rettypes = pdoc.getReturn().getTypes(); if (rettypes.length > 0) { // todo: handle multiple return types in the future // todo: handle [optional] and ... (params) return types String rettype = rettypes[0]; // arrays are ret type array, but (for now) store the guessed element // type on index 2 if (isArrayCall && rettypes.length > 1) { rettype = rettypes[1]; } // method calls always return an instance, so we look on prototype if (rettype.indexOf(".") > -1) //$NON-NLS-1$ { obj = lookupNamespaceFromNameHash(rettype); } else { obj = lookupReturnTypeFromNameHash(rettype, getGlobal()); } if (obj != null && !name.equals("Math")) // new fix //$NON-NLS-1$ { obj = obj.getPropertyValue("prototype", fileIndex, offset); //$NON-NLS-1$ } } } else { // look up from environment // we can't use regular command nodes here because we are usually in error // state at this point if (obj instanceof JSFunction) { JSFunction fnObj = (JSFunction) obj; obj = fnObj.invoke(getEnvironment(), new IObject[0], fileIndex, fnObj.getRange()); // JSFunction fnObj = (JSFunction)obj; // IRange range = fnObj.getRange(); // if(range != null && range instanceof FunctionNode) // { // FunctionNode fn = (FunctionNode)range; // obj = fn.getFunctionInstance().invoke(this.getEnvironment(), new // IObject[0], fileIndex, fn); // } } } } } return obj; } } private IObject lookupNamespaceFromNameHash(String fullname) { if (fullname.length() == 0) { return null; } // split the full name, then look up each segment, find return types if // needed as we parse the name. String[] names = fullname.split("\\."); //$NON-NLS-1$ IScope scope = getGlobal(); IObject obj = scope; int offset = this.getCurrentLexeme().offset; int fileIndex = this.getFileIndex(); for (int i = 0; i < names.length; i++) { String name = names[i]; if (i == 0) { obj = scope.getVariableValue(name, fileIndex, offset).getInstance(getEnvironment(), fileIndex, offset); } else { obj = obj.getPropertyValue(name, fileIndex, offset).getInstance(getEnvironment(), fileIndex, offset); } if (obj == ObjectBase.UNDEFINED) { return null; } } return obj; } /** * Gets the index of the arg from the current offset (arg0, arg1, ...), based on the number of commas viewed. This * also sets the 'mode' the current location is in (invoking, new, normal). This does both at once toab avoid * duplication, and caches the result. * * @return Returns the current arg count based on commas in the source. */ public int getArgIndexAndCalculateMode() { int commaCount = 0; mode = MODE_NORMAL; int pos = getCurrentLexemeIndex(); // backtrack over lexemes until we find a unmatched open paren. Count // commas as we go. int parenCount = 0; boolean wasNewline = false; while (pos >= 0 && pos < getLexemeList().size()) { Lexeme curLexeme = getLexemeList().get(pos); if (wasNewline) { break; } if (curLexeme.isAfterEOL()) { wasNewline = true; } switch (curLexeme.typeIndex) { case JSTokenTypes.COMMA: if (parenCount == 0) { commaCount++; } break; case JSTokenTypes.RPAREN: parenCount++; break; case JSTokenTypes.DOT: case JSTokenTypes.WHITESPACE: // case JSTokenTypes.LINE_TERMINATOR: // ignore break; case JSTokenTypes.SEMICOLON: // this needs to be a // "statement" instead of end // line mode = MODE_NORMAL; pos = -1; // break break; case JSTokenTypes.LPAREN: parenCount--; if (parenCount < 0) { mode = MODE_INVOKING; pos = -1; // break } break; default: // if (curLexeme.getCategoryIndex() == JSTokenCategories.PUNCTUATOR) // { // mode = JSFileEnvironment.MODE_NORMAL; // pos = -1; // } break; } pos--; } return commaCount; } /** * getFileIndex * * @return int */ public int getFileIndex() { return this.getParseState().getFileIndex(); } /** * Gets the global object for this file's environment. * * @return Returns the global object for this file's environment. */ public JSScope getGlobal() { return getEnvironment().getGlobal(); } /** * Returns the environment scope for the specified lexeme. * * @param lex * @param defaultScope * @return Returns the environment scope for the specified lexeme. */ public IScope getScope(Lexeme lex, IScope defaultScope) { return this.getEnvironmentLoader().getScope(lex.offset, defaultScope); // return getScopeFromCommandNode(lex, defaultScope); } private LexemeBasedEnvironmentLoader getEnvironmentLoader() { if (loader == null) { loader = this.getFileLanguageService().getEnvironmentLoader(); } return loader; } private JSFileLanguageService getFileLanguageService() { if (fileLangService == null) { fileLangService = JSFileLanguageService.getJSFileLanguageService(getFileService()); } return fileLangService; } // private IScope getScopeFromCommandNode(Lexeme lex, IScope defaultScope) { // // todo: use whatever new general "get scope" method we implement here // return defaultScope; // IScope scope = defaultScope; // CommandNode cn = lex.getCommandNode(); // // temp guard against ws nodes // if (cn != null) // { // if (cn instanceof FunctionNode) // { // FunctionNode fn = (FunctionNode) cn; // scope = fn.getBody().getParentScope(); // } // else // { // IScope curScope = cn.getParentScope(); // if (curScope != null) // { // scope = curScope; // } // } // } // return scope; // } /** * getParseState * * @return IParseState */ public IParseState getParseState() { return this.getFileLanguageService().getParseState(); } /** * @see com.aptana.ide.parsing.IOffsetMapper#findTarget(com.aptana.ide.lexer.Lexeme) */ public ICodeLocation findTarget(Lexeme lexeme) { // lookup the current full name String fullName = this.getNameHash(getLexemeList().getLexemeIndex(lexeme)); if (fullName.indexOf('(') > -1) { fullName = fullName.substring(0, fullName.lastIndexOf('(')); } IScope scope = this.getScope(lexeme, this.getGlobal()); if (fullName.startsWith(JSOffsetMapper.MODE_NEW)) { fullName = fullName.substring(JSOffsetMapper.MODE_NEW.length()); } IObject object = this.lookupReturnTypeFromNameHash(fullName, scope, true); Property prop = JSOffsetMapper.lookupTypeFromNameHash(fullName, scope, lexeme.getEndingOffset(), this); ICodeLocation loc = findTargetFromName(object, prop); return loc; } // /** // * Finds a target based on the global scope. // * @return Returns the target based on the global scope. // */ // public ICodeLocation findTargetFromName(IObject object, Property prop) // { // return findTargetFromName( object, prop); // } /** * Finds a target based on the passed scope. * * @param object * @param prop * @return Returns the target based on the passed scope. */ public static ICodeLocation findTargetFromName(IObject object, Property prop) { if (object == null || prop == null) { return null; } int offset = object.getStartingOffset(); if (offset < 0) { offset = Integer.MAX_VALUE + object.getStartingOffset(); } OrderedObjectCollection assignments = prop.getAssignments(); if (assignments == null || assignments.size() == 0) return null; CodeLocation loc = null; for (int i = 0; i < assignments.size(); i++) { OrderedObject orderedObject = assignments.get(i); if (orderedObject == null || orderedObject.object != object) continue; IWorkbenchWindow window = JSPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow(); IWorkbench workbench = window.getWorkbench(); IWorkbenchPage page = workbench.getActiveWorkbenchWindow().getActivePage(); IEditorReference[] editorReferences = page.getEditorReferences(); for (int j = 0; j < editorReferences.length; j++) { IEditorPart editor = editorReferences[j].getEditor(true); IEditorSite site = editor.getEditorSite(); IWorkbenchPart part = site.getPart(); if (part instanceof IUnifiedEditor) { IUnifiedEditor ue = (IUnifiedEditor) part; IFileService context = ue.getFileContext(); int fi = context.getParseState().getFileIndex(); if (orderedObject.fileIndex == fi) { IEditorInput input = editor.getEditorInput(); String path = CoreUIUtils.getPathFromEditorInput(input); Lexeme lx = context.getLexemeList().getCeilingLexeme(offset); if (lx != null) { loc = new CodeLocation(path, lx); return loc; } else { loc = new CodeLocation(path, lx); } } } } if (loc != null) { break; } int fileIndex = orderedObject.fileIndex; String path = FileContextManager.getURIFromFileIndex(fileIndex); FileService context = FileContextManager.get(path); if (context == null) { break; } LexemeList ll = context.getLexemeList(); if (ll == null) { break; } Lexeme lx = ll.getCeilingLexeme(offset); loc = new CodeLocation(path, lx); // if(fileIndex > -1 && fileIndex < paths.length) // { // } // else // { // // may be current file that is not in library // } break; } return loc; } }