/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package ca.weblite.netbeans.mirah.cc;
import ca.weblite.netbeans.mirah.lexer.DocumentQuery;
import ca.weblite.netbeans.mirah.lexer.MirahParser;
import ca.weblite.netbeans.mirah.lexer.MirahTokenId;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import mirah.impl.Tokens;
import mirah.lang.ast.Constant;
import mirah.lang.ast.Node;
import mirah.lang.ast.NodeScanner;
import org.mirah.tool.MirahCompiler;
import org.mirah.typer.ResolvedType;
import org.netbeans.api.editor.completion.Completion;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.parsing.api.Snapshot;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.spi.editor.completion.CompletionResultSet;
import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
/**
*
* @author shannah
*/
public class MethodCompletionQuery extends AsyncCompletionQuery {
boolean parsed = false;
int tries = 0;
Object lock = new Object();
String filter = null;
Class currentType = null;
boolean isStatic;
FileObject file;
final int initialOffset;
public MethodCompletionQuery(int initOff, FileObject file){
this.initialOffset = initOff;
this.file = file;
}
@Override
protected boolean canFilter(JTextComponent component) {
if ( currentType == null ){
return false;
}
int currentPos = component.getCaretPosition();
if ( currentPos <= initialOffset){
return false;
}
try {
char c = component.getText(currentPos, 1).charAt(0);
switch ( c){
case '.':
case ':':
case '(':
case ';':
return false;
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
try {
filter = component.getText(initialOffset, currentPos-initialOffset).trim();
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
return filter != null;
}
@Override
protected void filter(CompletionResultSet resultSet) {
//System.out.println("Filter "+filter);
for ( Method m : currentType.getMethods()){
if ( m.getName().toLowerCase().indexOf(filter.toLowerCase()) == 0 && isStatic == Modifier.isStatic(m.getModifiers()) ){
resultSet.addItem(new MirahMethodCompletionItem(file, m, initialOffset, filter.length(), currentType));
}
}
resultSet.finish();
}
@Override
protected void query(final CompletionResultSet crs, final Document doc, final int caretOffset) {
BaseDocument bdoc = (BaseDocument)doc;
if ( crs.isFinished() || this.isTaskCancelled()){
return;
}
if ( caretOffset < initialOffset ){
crs.finish();
Completion.get().hideAll();
return;
}
tries++;
MirahParser.DocumentDebugger dbg = MirahParser.getDocumentDebugger(doc);
if ( dbg != null ){
try {
int p = caretOffset-1;
if ( p < 0 ){
return;
}
String lastChar = doc.getText(p, 1);
TokenSequence<MirahTokenId> toks = MirahCodeCompleter.mirahTokenSequence(doc, caretOffset, true);
MirahTokenId tDot = MirahTokenId.get(Tokens.tDot.ordinal());
MirahTokenId tId = MirahTokenId.get(Tokens.tIDENTIFIER.ordinal());
MirahTokenId tNL = MirahTokenId.get(Tokens.tNL.ordinal());
MirahTokenId tWS = MirahTokenId.WHITESPACE;
// Will hold the "dot" token that we want to do code
// completion for
Token<MirahTokenId> dotToken = null;
// Will hold the subject token (i.e. the last token before
// the dot that we will be checking for a type
Token<MirahTokenId> subjectToken = null;
Token<MirahTokenId> thisTok = toks.token();
if ( thisTok != null ){
MirahTokenId thisTokType = thisTok.id();
Token<MirahTokenId> prevTok = null;
MirahTokenId prevTokType = null;
if ( toks.movePrevious() ){
prevTok = toks.token();
prevTokType = prevTok.id();
toks.moveNext();
}
if ( tId.equals(thisTokType) && tDot.equals(prevTokType) ){
filter = toks.token().text().toString();
dotToken = prevTok;
toks.movePrevious();
} else if (tDot.equals(thisTokType)){
filter = "";
dotToken = thisTok;
}
}
if ( dotToken == null ){
crs.finish();
Completion.get().hideAll();
return;
}
// Now to find the subject token.
while ( toks.movePrevious() ){
Token<MirahTokenId> tok = toks.token();
if ( tWS.equals(tok.id())||tNL.equals(tok.id())){
//
} else {
subjectToken = tok;
break;
}
}
if ( dotToken == null || subjectToken == null ){
crs.finish();
Completion.get().hideAll();
return;
}
bdoc.readLock();
TokenHierarchy<?> hi = TokenHierarchy.get(doc);
bdoc.readUnlock();
int bol = MirahCodeCompleter.getBeginningOfLine(doc, caretOffset);
int eol = MirahCodeCompleter.getEndOfLine(doc, caretOffset);
int dotPos = dotToken.offset(hi);
Node foundNode = MirahCodeCompleter.findNode(dbg, subjectToken.offset(hi)+subjectToken.length());
ResolvedType type = null;
if ( foundNode != null ){
type = dbg.getType(foundNode);
if ( type == null ){
DocumentQuery dq = new DocumentQuery(bdoc);
TokenSequence<MirahTokenId> seq = dq.getTokens(foundNode.position().endChar(), true);
String typeName = dq.guessType(seq, file);
//System.out.println("Type name guessed to be "+typeName);
}
//Node c = foundNode;
//while ( c != null ){
//
// c = c.parent();
//}
}
if ( foundNode == null || type == null ){
Source src = Source.create(doc);
MirahParser parser = new MirahParser();
try {
Snapshot snapshot = src.createSnapshot();
String text = snapshot.getText().toString();
StringBuilder sb = new StringBuilder();
sb.append(text.substring(0, dotPos));
for ( int i=dotPos; i<eol; i++){
sb.append(' ');
}
sb.append(text.substring(eol));
parser.reparse(snapshot, sb.toString());
} catch (ParseException ex){
Exceptions.printStackTrace(ex);
}
dbg = MirahParser.getDocumentDebugger(doc);
//printNodes(dbg.compiler.compiler(), rightEdgeFinal);
foundNode = MirahCodeCompleter.findNode(dbg, subjectToken.offset(hi)+subjectToken.length());
if ( foundNode != null ){
type = dbg.getType(foundNode);
}
}
if ( foundNode != null ){
type = dbg.getType(foundNode);
if ( type != null ){
FileObject fileObject = NbEditorUtilities.getFileObject(doc);
Class cls = MirahCodeCompleter.findClass(fileObject, dbg.getType(foundNode).name());
currentType = cls;
isStatic = foundNode instanceof Constant;
if ( cls != null ){
if ( isStatic && filter == null || "new".startsWith(filter)){
for ( Constructor c : cls.getConstructors()){
crs.addItem(new MirahConstructorCompletionItem(c, caretOffset-filter.length(), filter.length()));
}
}
for ( Method m : cls.getMethods()){
if ( m.getName().startsWith(filter) && isStatic == Modifier.isStatic(m.getModifiers())){
crs.addItem(new MirahMethodCompletionItem(fileObject, m, caretOffset-filter.length(), filter.length(), cls));
}
}
}
}
}
} catch ( BadLocationException ble ){
ble.printStackTrace();
}
}
if ( !crs.isFinished() ){
crs.finish();
}
}
private void printNodes(MirahCompiler compiler, int rightEdgeFinal) {
for ( Object o : compiler.getParsedNodes()){
if ( o instanceof Node && o != null ){
Node node = (Node)o;
node.accept(new NodeScanner(){
@Override
public boolean enterDefault(Node node, Object arg) {
return super.enterDefault(node, arg); //To change body of generated methods, choose Tools | Templates.
}
}, null);
}
}
}
}