/* * 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.MirahLanguageHierarchy; import ca.weblite.netbeans.mirah.lexer.MirahParser.DocumentDebugger; import ca.weblite.netbeans.mirah.lexer.MirahTokenId; import ca.weblite.netbeans.mirah.lexer.SourceQuery; import java.io.File; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.JTextComponent; import mirah.impl.Tokens; import mirah.lang.ast.FieldDeclaration; import mirah.lang.ast.Node; import mirah.lang.ast.NodeFilter; import mirah.lang.ast.NodeScanner; import mirah.lang.ast.Position; import org.mirah.typer.ResolvedType; import org.netbeans.api.editor.mimelookup.MimeRegistration; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.lexer.Token; import org.netbeans.api.lexer.TokenHierarchy; import org.netbeans.api.lexer.TokenSequence; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.editor.BaseDocument; import org.netbeans.modules.editor.NbEditorUtilities; import org.netbeans.spi.editor.completion.CompletionProvider; import org.netbeans.spi.editor.completion.CompletionTask; import org.netbeans.spi.editor.completion.support.AsyncCompletionTask; import org.netbeans.spi.java.classpath.support.ClassPathSupport; import org.openide.filesystems.FileObject; /** * * @author shannah */ @MimeRegistration(mimeType="text/x-mirah", service=CompletionProvider.class) public class MirahCodeCompleter implements CompletionProvider { private static final Logger LOG = Logger.getLogger(MirahCodeCompleter.class.getCanonicalName()); static FieldDeclaration[] findFields(final DocumentDebugger dbg, final int rightEdge, final boolean isClassVar){ final ArrayList<FieldDeclaration> foundNodes = new ArrayList<FieldDeclaration>(); for( Object node : dbg.compiler.compiler().getParsedNodes() ){ if ( node instanceof Node ){ ((Node)node).accept(new NodeScanner(){ @Override public boolean enterFieldDeclaration(FieldDeclaration node, Object arg) { if ( node.isStatic() == isClassVar ){ foundNodes.add(node); } return super.enterFieldDeclaration(node, arg); } }, null); } }; return foundNodes.toArray(new FieldDeclaration[0]); } public static Node findNode(final DocumentDebugger dbg, final int rightEdge){ final Node[] foundNode = new Node[1]; for( Object node : dbg.compiler.compiler().getParsedNodes() ){ if ( node instanceof Node ){ ((Node)node).accept(new NodeScanner(){ @Override public boolean enterDefault(Node node, Object arg) { if ( node != null ){ Position nodePos = node.position(); ResolvedType type = dbg.getType(node); if ( type != null && nodePos != null && nodePos.endChar() == rightEdge ){ foundNode[0] = node; } else if ( nodePos != null && nodePos.endChar() == rightEdge ){ } else { } } return super.enterDefault(node, arg); //To change body of generated methods, choose Tools | Templates. } }, null); //walkTree((Node)node); } }; return foundNode[0]; } //@Override public CompletionTask createTask2(int queryType, final JTextComponent jtc) { if ( queryType == CompletionProvider.COMPLETION_QUERY_TYPE){ //System.out.println("Request for completion query"); } else if ( queryType == CompletionProvider.DOCUMENTATION_QUERY_TYPE){ //System.out.println("Request for documentation"); } else if ( queryType == CompletionProvider.TOOLTIP_QUERY_TYPE){ //System.out.println("Request for tooltip"); } FileObject fileObject = NbEditorUtilities.getFileObject(jtc.getDocument()); int caretOffset = jtc.getCaretPosition(); DocumentQuery dq = new DocumentQuery(jtc.getDocument()); SourceQuery sq = new SourceQuery(jtc.getDocument()); TokenSequence<MirahTokenId> seq = dq.getTokens(caretOffset, true); if ( seq.token() != null ){ int startPos = seq.offset(); int len = seq.token().length(); //System.out.println("Start: "+startPos+" len "+len+" caret "+caretOffset); if ( seq.token().id().ordinal() == Tokens.tIDENTIFIER.ordinal()){ //String id = seq.token().text().toString(); //while ( seq.movePrevious() && MirahTokenId.WHITESPACE_AND_COMMENTS.contains(seq.token().id())){} String type = dq.guessType(seq, fileObject); //System.out.println("Guessed type "+type); SourceQuery method = sq.findMethod(caretOffset); //System.out.println("Finding local var "+seq.token().text()); SourceQuery localVar = method.findLocalVars(String.valueOf(seq.token().text())); if ( localVar.size() > 0 ){ //System.out.println("Found local var "+localVar.getType()); } } else if ( seq.token().id().ordinal() == Tokens.tCONSTANT.ordinal()){ //System.out.println("Constant"); } else { //System.out.println("other"); } } return null; } @Override public CompletionTask createTask(int queryType, final JTextComponent jtc) { if ( queryType != CompletionProvider.COMPLETION_QUERY_TYPE){ return null; } final int initialOffset = jtc.getCaretPosition(); try { int caretOffset = jtc.getCaretPosition(); int p = caretOffset-1; if ( p < 0 ){ return null; } String lastChar = jtc.getDocument().getText(p, 1); FileObject fileObject = NbEditorUtilities.getFileObject(jtc.getDocument()); while ( p > 0 && lastChar.trim().isEmpty()){ p--; lastChar = jtc.getDocument().getText(p, 1); } TokenSequence<MirahTokenId> toks = mirahTokenSequence(jtc.getDocument(), caretOffset, true); // Tokens that activate code completion MirahTokenId tWhitespace = MirahTokenId.WHITESPACE; MirahTokenId tComment = MirahTokenId.get(Tokens.tComment.ordinal()); MirahTokenId tIdentifier = MirahTokenId.get(Tokens.tIDENTIFIER.ordinal()); MirahTokenId tInstanceVar = MirahTokenId.get(Tokens.tInstVar.ordinal()); MirahTokenId tClassVar = MirahTokenId.get(Tokens.tClassVar.ordinal()); MirahTokenId tAt = MirahTokenId.get(Tokens.tAt.ordinal()); MirahTokenId tDot = MirahTokenId.get(Tokens.tDot.ordinal()); MirahTokenId tDef = MirahTokenId.get(Tokens.tDef.ordinal()); Set<MirahTokenId> activators = new HashSet<MirahTokenId>(); activators.add(tDot); activators.add(tAt); activators.add(tInstanceVar); activators.add(tDef); activators.add(tClassVar); Token<MirahTokenId> activator = null; int activatorOffset = -1; int activatorLen = -1; boolean hasWhitespace = false; boolean hasIdentifier = false; while ( toks.token().id() == tIdentifier || toks.token().id() == tComment || toks.token().id() == tWhitespace || toks.token().id().ordinal() == MirahLanguageHierarchy.METHOD_DECLARATION || activators.contains(toks.token().id()) ){ Token<MirahTokenId> curr = toks.token(); if ( curr.id() == tWhitespace || curr.id() == tComment){ hasWhitespace = true; } else if ( curr.id() == tIdentifier || curr.id().ordinal() == MirahLanguageHierarchy.METHOD_DECLARATION ){ hasIdentifier = true; } else { activator = toks.token(); activatorOffset = toks.offset(); activatorLen = curr.length(); break; } if ( !toks.movePrevious() ){ break; } } if ( activator == null ){ return null; } if ( activator.id() == tAt || activator.id() == tInstanceVar || activator.id() == tClassVar ){ if ( hasWhitespace ){ return null; } return new AsyncCompletionTask(new PropertyCompletionQuery(activatorOffset-activatorLen), jtc); } else if ( activator.id() == tDot ){ if ( hasWhitespace && hasIdentifier ){ return null; } return new AsyncCompletionTask(new MethodCompletionQuery(initialOffset, fileObject), jtc); } else if ( activator.id() == tDef ){ return new AsyncCompletionTask(new DefCompletionQuery(initialOffset), jtc); } } catch ( BadLocationException ble){ return null; } return null; } @Override public int getAutoQueryTypes(JTextComponent jtc, String string) { return 0; } static Class findClass(FileObject o, String name){ return findClass(o, name, true); } static Class findClass(FileObject o, String name, boolean cache){ Project proj = FileOwnerQuery.getOwner(o); FileObject projectDirectory = proj.getProjectDirectory(); ClassPath[] paths = new ClassPath[]{ ClassPath.getClassPath(o, ClassPath.SOURCE), ClassPathSupport.createClassPath(new File(projectDirectory.getPath(), "src").getPath()), ClassPath.getClassPath(o, ClassPath.EXECUTE), ClassPath.getClassPath(o, ClassPath.COMPILE), ClassPath.getClassPath(o, ClassPath.BOOT), }; for ( int i=0; i<paths.length; i++){ ClassPath cp = paths[i]; try { Class c = cp.getClassLoader(true).loadClass(name); if ( c != null ){ return c; } } catch ( ClassNotFoundException ex){ } } return null; } private static void walkTree(Node node){ NodeFilter f = new NodeFilter(){ @Override public boolean matchesNode(Node node) { return true; } }; List nodes = node.findChildren(f); for ( Object o : nodes ){ if ( o instanceof Node ){ walkTree((Node)o); } } } private static String nodeToString(Node n){ if ( n == null || n.position() == null ){ if ( n != null ){ return ""+n; } return ""; } StringBuilder sb = new StringBuilder(); sb.append("Node ").append(n) .append(n.position().startLine()) .append(".") .append(n.position().startColumn()) .append(":") .append(n.position().startChar()) .append("-") .append(n.position().endLine()) .append(".") .append(n.position().endColumn()) .append(":") .append(n.position().endChar()) .append(" # "); return sb.toString(); } static TokenSequence<MirahTokenId> mirahTokenSequence(Document doc, int caretOffset, boolean backwardBias) { BaseDocument bd = (BaseDocument)doc; try { bd.readLock(); TokenHierarchy<?> hi = TokenHierarchy.get(doc); List<TokenSequence<?>> tsList = hi.embeddedTokenSequences(caretOffset, backwardBias); // Go from inner to outer TSes for (int i = tsList.size() - 1; i >= 0; i--) { TokenSequence<?> ts = tsList.get(i); if (ts.languagePath().innerLanguage() == MirahTokenId.getLanguage()) { TokenSequence<MirahTokenId> javaInnerTS = (TokenSequence<MirahTokenId>) ts; //bd.readUnlock(); return javaInnerTS; } } } finally { bd.readUnlock(); } return null; } static int getEndOfLine(Document doc, int caretOffset){ BaseDocument bd = (BaseDocument)doc; bd.readLock(); try { TokenHierarchy<?> hi = TokenHierarchy.get(doc); TokenSequence<MirahTokenId> toks = mirahTokenSequence(doc, caretOffset, false); MirahTokenId eol = MirahTokenId.get(Tokens.tNL.ordinal()); MirahTokenId eof = MirahTokenId.get(Tokens.tEOF.ordinal()); while ( !eol.equals(toks.token().id()) && !eof.equals(toks.token().id())){ if ( !toks.moveNext() ){ break; } } int off = toks.token().offset(hi); return off; } finally { bd.readUnlock(); } } static int getBeginningOfLine(Document doc, int caretOffset){ BaseDocument bd = (BaseDocument)doc; bd.readLock(); try { TokenHierarchy<?> hi = TokenHierarchy.get(doc); TokenSequence<MirahTokenId> toks = mirahTokenSequence(doc, caretOffset, true); MirahTokenId eol = MirahTokenId.get(Tokens.tNL.ordinal()); //MirahTokenId eof = MirahTokenId.get(Tokens.tEOF.ordinal()); while ( !eol.equals(toks.token().id())){ if ( !toks.movePrevious() ){ break; } } int off = toks.token().offset(hi)+toks.token().length(); return off; } finally { bd.readUnlock(); } } }