org.fife.rsta.ac.js.JavaScriptLinkGenerator Maven / Gradle / Ivy
package org.fife.rsta.ac.js;
import javax.swing.text.BadLocationException;
import org.fife.rsta.ac.js.ast.JavaScriptDeclaration;
import org.fife.rsta.ac.js.ast.VariableResolver;
import org.fife.ui.rsyntaxtextarea.LinkGenerator;
import org.fife.ui.rsyntaxtextarea.LinkGeneratorResult;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
import org.fife.ui.rsyntaxtextarea.SelectRegionLinkGeneratorResult;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.TokenImpl;
public class JavaScriptLinkGenerator implements LinkGenerator {
private JavaScriptLanguageSupport language;
private boolean findLocal;
private boolean findPreprocessed;
private boolean findSystem;
public JavaScriptLinkGenerator(JavaScriptLanguageSupport language) {
this.language = language;
this.findLocal = true;
}
/**
* {@inheritDoc}
*/
@Override
public LinkGeneratorResult isLinkAtOffset(RSyntaxTextArea textArea, int offs) {
JavaScriptDeclaration dec = null;
IsLinkableCheckResult result = checkForLinkableToken(textArea, offs);
if (result != null) {
//re-parse the document to resolve any variables local to the offs
Token t = result.token;
boolean function = result.function;
String name = t.getLexeme();
if(name != null && name.length() > 0)
{
//only re-parse the document if there is a character that could potentially be a variable or function
if(name.length() > 1 || (name.length() == 1 && Character.isJavaIdentifierPart(name.charAt(0))))
{
language.reparseDocument(offs);
}
}
JavaScriptParser parser = language.getJavaScriptParser();
VariableResolver variableResolver = parser.getVariablesAndFunctions();
if (variableResolver != null) {
if (!function) { // must be a variable
dec = variableResolver.findDeclaration(name, offs, findLocal, findPreprocessed, findSystem);
}
else {
String lookup = getLookupNameForFunction(textArea, offs, name);
// lookup Function based on the name
dec = variableResolver.findFunctionDeclaration(lookup, findLocal, findPreprocessed);
if(dec == null) {
dec = variableResolver.findFunctionDeclarationByFunctionName(name, findLocal, findPreprocessed);
}
}
}
if (dec != null) {
return createSelectedRegionResult(textArea, t, dec);
}
}
return null;
}
/**
* @return LinkGeneratorResult based on the JavaScriptDeclaration and the position
*/
protected LinkGeneratorResult createSelectedRegionResult(RSyntaxTextArea textArea, Token t, JavaScriptDeclaration dec) {
if(dec.getTypeDeclarationOptions() != null && !dec.getTypeDeclarationOptions().isSupportsLinks()) {
return null;
}
return new SelectRegionLinkGeneratorResult(textArea, t.getOffset(), dec.getStartOffSet(), dec.getEndOffset());
}
/**
* @param find flag to state whether to look in the RSTA editing script for variable/function completions
*/
public void setFindLocal(boolean find) {
this.findLocal = find;
}
/**
* @param find flag to state whether to look in the pre-processed scripts for variable/function completions
*/
public void setFindPreprocessed(boolean find) {
this.findPreprocessed = find;
}
/**
* @param find flag to state whether to look in the system scripts for variable/function completions
*/
public void setFindSystem(boolean find) {
this.findSystem = find;
}
/**
* Convert the function Token to JavaScript variable resolver lookup name by replacing any parameters with 'p' and stripping any whitespace between the parameters:
* e.g.
* Token may contain the function:
* addTwoNumbers(num1, num2);
*
* The return result will be:
* addTwoNumbers(p,p);
*
* @return converted function name to variable resolver lookup name
*/
private String getLookupNameForFunction(RSyntaxTextArea textArea, int offs, String name) {
StringBuilder temp = new StringBuilder();
if (offs>=0) {
try {
int line = textArea.getLineOfOffset(offs);
Token first = wrapToken(textArea.getTokenListForLine(line));
for (Token t=first; t!=null && t.isPaintable(); t=wrapToken(t.getNextToken())) {
if (t.containsPosition(offs)) {
for (Token tt=t; tt!=null && tt.isPaintable(); tt=wrapToken(tt.getNextToken())) {
temp.append(tt.getLexeme());
if (tt.isSingleChar(Token.SEPARATOR, ')')) {
break;
}
}
}
}
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
}
//now replace all the variables with lookup 'p'
String function = temp.toString().replaceAll("\\s", ""); //remove all whitespace
boolean params = false;
int count = 0;
StringBuilder sb = new StringBuilder();
for(int i=0; i= 0) {
try {
int line = textArea.getLineOfOffset(offs);
Token first = wrapToken(textArea.getTokenListForLine(line));
Token prev = null;
for (Token t = first; t != null && t.isPaintable(); t = wrapToken(t
.getNextToken())) {
if (t.containsPosition(offs)) {
// RSTA's tokens are pooled and re-used, so we must
// defensively make a copy of the one we want to keep!
Token token = wrapToken(t);
boolean isFunction = false;
if (prev != null && prev.isSingleChar('.')) {
// Not a field or method defined in this.
break;
}
Token next = wrapToken(RSyntaxUtilities
.getNextImportantToken(t.getNextToken(),
textArea, line));
if (next != null
&& next.isSingleChar(Token.SEPARATOR, '(')) {
isFunction = true;
}
result = new IsLinkableCheckResult(token, isFunction);
break;
}
else if (!t.isCommentOrWhitespace()) {
prev = t;
}
}
} catch (BadLocationException ble) {
ble.printStackTrace(); // Never happens
}
}
return result;
}
/**
* Due to the Tokens being reused, this can cause problems when finding the
* next token associated with a token. Wrap the tokens to stop this
* problem.
*
* @param token to wrap
* @return copy of the original token
*/
private Token wrapToken(Token token) {
if (token != null)
return new TokenImpl(token);
return token;
}
/**
* @return JavaScriptLanguage support
*/
public JavaScriptLanguageSupport getLanguage() {
return language;
}
/**
* The result of checking whether a region of code under the mouse is
* possibly link-able.
*/
private static class IsLinkableCheckResult {
/**
* The token under the mouse position.
*/
private Token token;
/**
* Whether the token is a function invocation (as opposed to a local
* variable or object).
*/
private boolean function;
private IsLinkableCheckResult(Token token, boolean function) {
this.token = token;
this.function = function;
}
}
}