package org.das2.jythoncompletion; import java.awt.Color; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent; import javax.swing.text.Utilities; import org.das2.jythoncompletion.support.CompletionResultSet; import org.das2.jythoncompletion.support.CompletionTask; import org.das2.util.LoggerManager; import org.python.core.PyClass; import org.python.core.PyClassPeeker; import org.python.core.PyException; import org.python.core.PyFunction; import org.python.core.PyInteger; import org.python.core.PyJavaClass; import org.python.core.PyJavaClassPeeker; import org.python.core.PyJavaInstance; import org.python.core.PyJavaInstancePeeker; import org.python.core.PyJavaPackage; import org.python.core.PyList; import org.python.core.PyMethod; import org.python.core.PyMethodPeeker; import org.python.core.PyNone; import org.python.core.PyObject; import org.python.core.PyReflectedFunction; import org.python.core.PyReflectedFunctionPeeker; import org.python.core.PyString; import org.python.core.PyStringMap; import org.python.core.PyTableCode; import org.python.util.PythonInterpreter; import org.autoplot.jythonsupport.JythonOps; import org.autoplot.jythonsupport.JythonRefactory; import org.autoplot.jythonsupport.JythonToJavaConverter; import org.autoplot.jythonsupport.SimplifyScriptSupport; import org.das2.graph.GraphUtil; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.python.core.PyArray; import org.python.core.PyFloat; import org.python.core.PyReflectedField; /** * Completions for Jython code. The completion task is created with the * editor configured for completions (code and caret position within code), * and "query" is called which will fill a CompletionResultSet. * @author jbf * @see org.das2.jythoncompletion.JythonCompletionProvider */ public class JythonCompletionTask implements CompletionTask { private static final Logger logger= LoggerManager.getLogger("jython.editor.completion"); private static final ImageIcon LOCALVARICON= new ImageIcon( JythonCompletionTask.class.getResource("ui/localVariable.png") ); private static final ImageIcon JAVA_CLASS_ICON= new ImageIcon( JythonCompletionTask.class.getResource("ui/javaClass.png") ); private static final ImageIcon JYTHONCOMMANDICON= new ImageIcon( JythonCompletionTask.class.getResource("ui/jythonCommand.png") ); private static final ImageIcon JAVA_JYTHON_METHOD_ICON= new ImageIcon( JythonCompletionTask.class.getResource("ui/javaJythonMethod.png") ); private static final ImageIcon JAVA_FIELD_ICON= new ImageIcon( JythonCompletionTask.class.getResource("ui/javaStaticField.png") ); private static final ImageIcon JAVA_METHOD_ICON= new ImageIcon( JythonCompletionTask.class.getResource("ui/javaMethod.png") ); private static final ImageIcon JAVA_STATIC_METHOD_ICON= new ImageIcon( JythonCompletionTask.class.getResource("ui/javaStaticMethod.png") ); private static final ImageIcon JAVA_CONSTRUCTOR_ICON= new ImageIcon( JythonCompletionTask.class.getResource("ui/javaConstructor.png") ); private static final int JYTHONCOMMAND_SORT = 2; private static final int JAVAMETHOD_SORT = 1; private static final int JAVACLASS_SORT = 1; private static final int PYREFLECTEDFIELD_SORT = 3; private static final int PYCLASS_SORT = 3; private static final int LOCALVAR_SORT = -10; private static final int AUTOVAR_SORT = -3; private static final int AUTOCOMMAND_SORT = -2; private static final int AUTOVARHIDE_SORT = 9; private static final int JAVASTATICFIELD_SORT=1; public static final String CLIENT_PROPERTY_INTERPRETER_PROVIDER = "JYTHON_INTERPRETER_PROVIDER"; JTextComponent editor; private final JythonInterpreterProvider jythonInterpreterProvider; /** * create the completion task on the text component, using its content and caret position. * @param t the text component */ public JythonCompletionTask(JTextComponent t) { this.editor = t; jythonInterpreterProvider = (JythonInterpreterProvider) t.getClientProperty(CLIENT_PROPERTY_INTERPRETER_PROVIDER); } private Method getReadMethod(PyObject context, PyObject po, Class dc, String propName) { try { String methodName = "get" + propName.substring(0,1).toUpperCase() + propName.substring(1); Method m = dc.getMethod(methodName); return m; } catch (NoSuchMethodException ex) { if ( po instanceof PyInteger ) { String methodName = "is" + propName.substring(0,1).toUpperCase() + propName.substring(1); try { Method m = dc.getMethod(methodName); return m; } catch ( NoSuchMethodException | SecurityException ex2 ) { return null; } } return null; } catch (SecurityException ex) { return null; } } @Override public void query(CompletionResultSet arg0) throws PyException { try { JythonCompletionProvider.getInstance().setMessage("busy: getting completions"); CompletionContext cc = CompletionSupport.getCompletionContext(editor); if (cc == null) { logger.fine("no completion context"); } else { doQuery( cc, arg0); // TODO: how to make it so the plotx reference documentation waits? I guess we add multiple completions. //( arg0.addItem(new MessageCompletionItem("please wait")) ); } } catch ( BadLocationException ex ) { logger.log( Level.WARNING, null, ex ); arg0.addItem( new MessageCompletionItem( ex.getMessage() ) ); } finally { JythonCompletionProvider.getInstance().setMessage("done getting completions"); arg0.finish(); } } /** * perform the completions query. This is the heart of Jython completions. * @param cc * @param resultSet * @return the count */ public int doQuery( CompletionContext cc, CompletionResultSet resultSet ) { int c=0; try { switch (cc.contextType) { case CompletionContext.MODULE_NAME: c= queryModules(cc, resultSet); break; case CompletionContext.PACKAGE_NAME: c= queryPackages(cc, resultSet); break; case CompletionContext.DEFAULT_NAME: c= queryNames(cc, resultSet); break; case CompletionContext.METHOD_NAME: c= queryMethods(cc, resultSet); break; case CompletionContext.STRING_LITERAL_ARGUMENT: c= queryStringLiteralArgument(cc, resultSet); break; case CompletionContext.COMMAND_ARGUMENT: c= queryCommandArgument(cc, resultSet); c+= queryNames(cc, resultSet); break; case CompletionContext.CLASS_METHOD_NAME: c= queryClassMethods( cc, resultSet ); break; default: break; } } catch ( BadLocationException ex ) { logger.log( Level.WARNING, null, ex ); if ( resultSet!=null ) resultSet.addItem( new MessageCompletionItem( ex.getMessage() ) ); } finally { } return c; } private Method getJavaMethod(PyMethod m, int i) { PyMethodPeeker mpeek = new PyMethodPeeker(m); //PyJavaInstancePeeker peek = new PyJavaInstancePeeker((PyJavaInstance) context); return new PyReflectedFunctionPeeker(mpeek.getReflectedFunction()).getMethod(i); } private int getMethodCount( PyMethod m ) { PyMethodPeeker mpeek = new PyMethodPeeker(m); return new PyReflectedFunctionPeeker(mpeek.getReflectedFunction()).getArgsCount(); } private int queryClassMethods(CompletionContext cc, CompletionResultSet rs) { int count= 0; Class c= cc.getContextObjectClass(); while ( c!=null && c!=Object.class ) { Method[] mm= c.getDeclaredMethods(); for ( Method m: mm ){ if ( m.getName().startsWith(cc.completable) ) { String signature = methodSignature(m); String args = methodArgs(m); String ss= m.getName(); String label= ss + args; String link = getLinkForJavaSignature(signature); rs.addItem(new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link)); count++; } } c= c.getSuperclass(); } return count; } /** * remove getProp and setProp and replace with just "prop" * @param po2 * @return */ private List reduceGetterSetters( PyObject lcontext, PyList po2, boolean cullGetterSetters ) { Map mm= new LinkedHashMap<>(); for (int i = 0; i < po2.__len__(); i++) { PyString s = (PyString) po2.__getitem__(i); mm.put( s.toString(), s.toString() ); } if ( cullGetterSetters ) { List ss= new ArrayList<>( mm.keySet() ); for ( String s: ss ) { if ( s.startsWith("set") ) { String prop= s.substring(3); if ( mm.get("get"+prop )!=null ) { String propName= Character.toLowerCase( prop.charAt(0) ) + prop.substring(1); if ( mm.containsKey(propName) ) { mm.remove("get"+prop); mm.remove("set"+prop); } } else if ( mm.get("is"+prop )!=null ) { String propName= Character.toLowerCase( prop.charAt(0) ) + prop.substring(1); if ( mm.containsKey(propName) ) { mm.remove("is"+prop); mm.remove("set"+prop); } } } } } return new ArrayList<>( mm.keySet() ); } private int queryMethods(CompletionContext cc, CompletionResultSet rs) throws BadLocationException { logger.fine("queryMethods"); PythonInterpreter interp; interp = getInterpreter(); String eval; if ( JythonCompletionProvider.getInstance().settings().isSafeCompletions() ) { eval = editor.getText(0, Utilities.getRowStart(editor, editor.getCaretPosition())); String eval1 = SimplifyScriptSupport.removeSideEffects( eval ); //String eval2 = JythonUtil.removeSideEffects( eval ); eval = eval1; } else { eval= editor.getText(0, Utilities.getRowStart(editor, editor.getCaretPosition())); } //kludge to handle increase in indent level if (eval.endsWith(":\n")) { eval = eval + " pass\n"; } try { interp.exec(JythonRefactory.fixImports(eval)); } catch ( PyException ex ) { // something bad has happened, remove side effects (we might have done this already) and try again. eval = editor.getText(0, Utilities.getRowStart(editor, editor.getCaretPosition())); String eval1 = SimplifyScriptSupport.removeSideEffects( eval ); //String eval2 = JythonUtil.removeSideEffects( eval ); eval= eval1; if (eval.endsWith(":\n")) { eval = eval + " pass\n"; } try { eval= sanitizeLeaveImports(eval); interp.exec(eval); } catch (PyException ex2 ) { rs.addItem(new MessageCompletionItem("Eval error in code before current position", ex2.toString())); return 0; } } catch (IOException ex) { rs.addItem(new MessageCompletionItem("Exception occurred: " + ex.toString())); return 0; } PyObject lcontext=null; PyJavaClass lcontextClass=null; boolean fromArray= false; try { lcontext = interp.eval(cc.contextString); } catch (PyException ex) { try { if ( cc.contextString.endsWith("]") ) { int k= cc.contextString.lastIndexOf("["); if ( k>-1 ) { PyObject occ= interp.eval(cc.contextString.substring(0,k)); if ( occ instanceof PyArray ) { PyArray pa= (PyArray)occ; Object o= pa.getArray(); Class oc= o.getClass(); if ( oc.isArray() ) { lcontextClass= PyJavaClass.lookup( oc.getComponentType() ); try { lcontext = new PyJavaInstance( oc.getComponentType().getDeclaredConstructors()[0].newInstance() ); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex1) { Logger.getLogger(JythonCompletionTask.class.getName()).log(Level.SEVERE, null, ex1); } fromArray= true; } } } } // check to see if we have identified the class of the symbol. if ( lcontextClass==null ) { PyObject occ= interp.eval(cc.contextString+__CLASSTYPE); if ( occ!=null && occ instanceof PyJavaClass ) { lcontextClass= (PyJavaClass)occ; } else { rs.addItem(new MessageCompletionItem("EVAL error: " + cc.contextString, ex.toString())); return 0; } } } catch ( PyException ex2 ) { rs.addItem(new MessageCompletionItem("Eval error: " + cc.contextString, ex.toString())); return 0; } } if ( lcontext==null ) { logger.log(Level.FINE, "completions have the class but not the instance to work with: {0}", lcontextClass.__name__); lcontext= lcontextClass; } PyList po2; try { po2= (PyList) lcontext.__dir__(); } catch ( PyException e ) { logger.log( Level.SEVERE, e.getMessage(), e ); return 0; } List po3= reduceGetterSetters( lcontext, po2, fromArray || ( lcontext!=lcontextClass ) ); int count=0; for (int i = 0; i < po3.size(); i++) { String ss = po3.get(i); logger.log(Level.FINEST, "does {0} start {1}", new Object[] { cc.completable, ss } ); if (ss.startsWith(cc.completable)) { boolean notAlreadyAdded= true; PyObject po; try { po = lcontext.__getattr__(ss); } catch (PyException e) { logger.log(Level.FINE, "PyException from \"{0}\":", ss); logger.log( Level.SEVERE, e.getMessage(), e ); continue; } catch ( IllegalArgumentException e ) { logger.log( Level.SEVERE, e.getMessage(), e ); continue; } String label = ss; String signature = null; String args = ""; ImageIcon icon= null; if (lcontext instanceof PyJavaClass) { if (po.getClass().toString().equals( "class org.python.core.PyReflectedConstructor" ) ) { args= "()"; signature= ""; } else if (po instanceof PyReflectedFunction) { Method m = new PyReflectedFunctionPeeker((PyReflectedFunction) po).getMethod(0); if ( Modifier.isStatic( m.getModifiers() ) ) { signature = methodSignature(m); icon= getIconFor(m); args = methodArgs(m); } else { if ( lcontext==lcontextClass ) { // whoops, we have an instance of a class here signature = methodSignature(m); icon= getIconFor(m); args = methodArgs(m); } else { continue; } } } else if ( po instanceof PyString || po instanceof PyInteger || po instanceof PyJavaInstance) { Class c= new PyClassPeeker((PyJavaClass) lcontext).getJavaClass(); try { Field f = c.getField(ss); signature= fieldSignature(f); icon= getIconFor(f); } catch ( NoSuchFieldException ex ) { } } } else if ( lcontext instanceof PyJavaPackage ) { if (po instanceof PyJavaClass) { Class dc = getJavaClass((PyJavaClass)po); if ( dc.getConstructors().length>0 ) { Constructor constructor= dc.getConstructors()[0]; signature= constructorSignature( constructor ); args= argsList( constructor.getParameterTypes() ); signature= signature+args; } else { signature= dc.getCanonicalName().replaceAll("\\.","/")+".html"; } //Method m = new PyJavaClassPeeker((PyJavaClass)po).getMethod(0); //signature = methodSignature(m); //args = methodArgs(m); } else if ( po instanceof PyJavaPackage ) { //Method m = new PyJavaClassPeeker((PyJavaClass)po).getMethod(0); //signature = methodSignature(m); //args = methodArgs(m); } } else if (lcontext instanceof PyClass) { PyClassPeeker peek = new PyClassPeeker((PyClass) lcontext); Class dc = peek.getJavaClass(); if ( dc==null ) { logger.fine("unable to identify JavaClass"); signature = "" + lcontext.__getattr__(label); } else { Field f = null; try { f = dc.getField(label); } catch (NoSuchFieldException | SecurityException ex) { } if (f == null) { continue; } signature = fieldSignature(f); } } else if (lcontext instanceof PyJavaInstance) { if (po instanceof PyMethod) { PyMethod m = (PyMethod) po; Method jm; try { for ( int im=0; im("+type+")"; } else { Field f = null; try { f = dc.getField(label); } catch (NoSuchFieldException ex) { logger.log(Level.FINEST, "NoSuchFieldException for item {0}", ss); } catch (SecurityException ex) { logger.log(Level.FINEST, "SecurityException for item {0}", ss); } if (f == null) continue; icon= getIconFor(f); //TODO: don't include static fields in list. signature = fieldSignature(f); boolean showValues=false; if ( showValues ) { if ( po instanceof PyInteger ) { label= ss + " = " + po.toString(); } else if ( po instanceof PyFloat ) { label= ss + " = " + po.toString(); } else if ( po instanceof PyString ) { label= ss + " = " + po.toString(); } else { label = ss; } } else { label = ss; } } } } else { //PyObject o= context.__dir__(); label= ss; signature= null; if ( po instanceof PyMethod ) { PyMethod pm= (PyMethod)po; PyObject pm2= pm.im_func; if ( pm2 instanceof PyFunction ) { Object doc= ((PyFunction)pm2).__doc__; if ( doc!=null ) { signature= doc instanceof PyNone ? "(No documentation)" : doc.toString(); String[] ss2= signature.split("\n"); if ( ss2.length>1 ) { for ( int jj= 0; jj< ss2.length; jj++ ){ ss2[jj]= escapeHtml(ss2[jj]); } String sig= getPyFunctionSignature( (PyFunction)pm2 ); if ( !signature.startsWith("" ) ) { signature= ""+sig+ "

"+join( ss2, "
" )+""; } else { signature= ""+sig+ "

" + signature.substring(6)+""; } } signature= "inline:"+signature; } } } //String link = "http://docs.python.org/library/"; //TODO: this could probably be done } if ( notAlreadyAdded ) { if ( signature!=null && signature.startsWith("inline:") ) { rs.addItem(new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, signature)); } else { String link = getLinkForJavaSignature(signature); if ( icon==null ) { rs.addItem(new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, JAVAMETHOD_SORT, null ) ); } else { rs.addItem(new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, JAVAMETHOD_SORT, icon ) ); } } count++; } } } return count; } public static final String __CLASSTYPE = "__CLASSTYPE"; /** * * @param cc * @param rs * @return the count */ private int queryModules(CompletionContext cc, CompletionResultSet rs) { logger.fine("queryModules"); PythonInterpreter interp = getInterpreter(); String eval = "targetComponents = '" + cc.contextString + "'.split('.')\n" + "base = targetComponents[0]\n" + "baseModule = __import__(base, globals(), locals())\n" + "module = baseModule \n" + "for component in targetComponents[1:]:\n" + " module = getattr(module, component)\n" + "list = dir(module)\n" + "if ( list.count('__name__')>0 ):\n" + " list.remove('__name__')\n" + "list.append('*')\n" + "list"; try { interp.exec(eval); } catch ( PyException ex ) { if ( rs!=null ) rs.addItem(new MessageCompletionItem("Eval error in code before current position", ex.toString())); return 0; } int count=0; PyList po2 = (PyList) interp.eval("list"); for (int i = 0; i < po2.__len__(); i++) { PyString s = (PyString) po2.__getitem__(i); String ss = s.toString(); if (ss.startsWith(cc.completable)) { String javaClass= cc.contextString + "." + ss; String signature; String link; if ( ss.length()>0 && Character.isUpperCase(ss.charAt(0)) ) { signature= join( javaClass.split("\\."), "/") + ".html"; link= JavadocLookup.getInstance().getLinkForJavaSignature(signature); } else { signature= join( javaClass.split("\\."), "/") + "/package-summary.html"; link= JavadocLookup.getInstance().getLinkForJavaSignature(signature); } if ( link!=null ) link+= "#skip.navbar.top"; if ( rs!=null ) rs.addItem(new DefaultCompletionItem(ss, cc.completable.length(), ss, ss, link)); count++; } } return count; } /** * look for package names. * @param cc * @param rs * @return the count */ private int queryPackages(CompletionContext cc, CompletionResultSet rs) { logger.fine("queryPackages"); PythonInterpreter interp = getInterpreter(); HashSet results= new HashSet(); int count=0; if ( cc.completable.equals("import") ) { if ( rs!=null ) rs.addItem(new DefaultCompletionItem( " ", 0, " ", "space", null )); return 1; } if ( !cc.contextString.equals( cc.completable ) ) { // something to work with String eval = "import " + cc.contextString + "\n" + "targetComponents = '" + cc.contextString + "'.split('.')\n" + "base = targetComponents[0]\n" + "baseModule = __import__(base, globals(), locals(), [], -1 )\n" + "module = baseModule \n" + "name= base\n" + "for component in targetComponents[1:]:\n" + " name= name + '.' + component\n" + " baseModule = __import__( name, None, None )\n" + " module = getattr(module, component)\n" + "list = dir(module)\n" + "if ( '__name__' in list ): list.remove('__name__')\n" + "list\n"; PyList po2; try { interp.exec(eval); } catch ( PyException ex ) { if ( rs!=null ) rs.addItem(new MessageCompletionItem("Eval error in code before current position", ex.toString())); return 0; } po2 = (PyList) interp.eval("list"); for (int i = 0; i < po2.__len__(); i++) { PyString s = (PyString) po2.__getitem__(i); String ss = s.toString(); if (ss.startsWith(cc.completable)) { String javaClass= cc.contextString + "." + ss; String signature; String link; if ( ss.length()>0 && Character.isUpperCase(ss.charAt(0)) ) { signature= join( javaClass.split("\\."), "/") + ".html"; link= JavadocLookup.getInstance().getLinkForJavaSignature(signature); } else { signature= join( javaClass.split("\\."), "/") + "/package-summary.html"; link= JavadocLookup.getInstance().getLinkForJavaSignature(signature); } if ( link!=null ) link+= "#skip.navbar.top"; if ( rs!=null ) rs.addItem(new DefaultCompletionItem(ss, cc.completable.length(), ss, ss, link )); count++; results.add(ss); } } } BufferedReader reader= null; try { reader= new BufferedReader( new InputStreamReader( JythonCompletionTask.class.getResourceAsStream("packagelist.txt") ) ); String ss= reader.readLine(); String search= cc.contextString + "." + cc.completable; int plen= cc.contextString.length()+1; if ( cc.contextString.equals(cc.completable) ) { search= cc.contextString; plen= search.length(); } while ( ss!=null ) { if ( !ss.startsWith("#") && ss.length()>0 ) { if ( ss.startsWith(search) && !results.contains(ss.substring(plen)) ) { String link= "http://www-pw.physics.uiowa.edu/~jbf/autoplot/javadoc2018/" + ss.replaceAll("\\.","/") + "/package-summary.html"; if ( rs!=null ) rs.addItem(new DefaultCompletionItem(ss, search.length(), ss, ss, link )); count++; } } ss= reader.readLine(); } } catch ( IOException ex ) { logger.log( Level.WARNING, null, ex ); } finally { if ( reader!=null ) try { reader.close(); } catch (IOException ex) { logger.log( Level.WARNING, null, ex ); } } return count; } private static String join(String[] list, String delim) { return join(Arrays.asList(list), delim); } private static String join(List list, String delim) { if (list.isEmpty()) { return ""; } else { StringBuilder result = new StringBuilder(list.get(0)); for (int i = 1; i < list.size(); i++) { result.append(delim).append(list.get(i)); } return result.toString(); } } /** * quick-n-dirty one line parser is better than nothing. * @param s * @return */ private static String popOffComments( String s ) { char inString= 0; for ( int i=0; i lastLine= new LinkedList<>(); int i1= script.indexOf(s,i); String indent= script.substring(i+1,i1); lastLine.add( 0, l ); int i2= script.lastIndexOf("\n",i-1); String l2= script.substring(i2+1,i); lastLine.add( 0, l2 ); while ( l2.startsWith(indent) ) { i= i2; i2= script.lastIndexOf("\n",i-1); l2= script.substring(i2+1,i); lastLine.add( 0, l2 ); } return String.join( "\n",lastLine ); } /** * introduced to see if we can pop a little code from the end, in case we * are within a triple-quoted string. * @param script the script * @return the script, possibly with a few fewer lines. * @see SimplifyScriptSupport#alligatorParse(java.lang.String) */ public static String trimLinesToMakeValid( String script ) { return SimplifyScriptSupport.alligatorParse(script); } private int queryNames(CompletionContext cc, CompletionResultSet rs) throws BadLocationException { logger.fine("queryNames"); int count=0; String[] keywords = new String[]{ "def", "elif", "except", "from", "for", "finally", "import", "while", "print", "raise"}; //TODO: not complete for (String kw : keywords) { if (kw.startsWith(cc.completable)) { if ( rs!=null ) rs.addItem(new DefaultCompletionItem(kw, cc.completable.length(), kw, kw, null, JYTHONCOMMAND_SORT, JYTHONCOMMANDICON)); count++; } } PythonInterpreter interp = getInterpreter(); String eval; int eolnCarot= Utilities.getRowStart(editor, editor.getCaretPosition()); eval= editor.getText(0, eolnCarot); eval= trimLinesToMakeValid( eval ); if ( eolnCarot>0 ) { int startLastLine= Utilities.getRowStart(editor, eolnCarot-1 ); String lastLine= editor.getText( startLastLine, eolnCarot-startLastLine ); Matcher m= Pattern.compile("(\\s*)(\\S+).*(\\s)*").matcher(lastLine); if ( m.matches() ) { int i= m.group(1).length(); String indent= lastLine.substring(0,i); if ( !eval.endsWith("\n") ) { eval= eval + "\n" + indent + "__dummy__=1\n"; } else { eval= eval + indent + "__dummy__=1\n"; } } } if ( JythonCompletionProvider.getInstance().settings().isSafeCompletions() ) { try { eval= sanitizeLeaveImports( eval ); } catch ( Exception ex ) { // adding __dummy__ didn't work, so start removing lines at the end. eval= editor.getText(0, eolnCarot); eval= trimLinesToMakeValid( eval ); eval= sanitizeLeaveImports( eval ); } } try { interp.exec( JythonRefactory.fixImports(eval) ); } catch ( PyException ex ) { String message= "

Code completions couldn't run on a simplified version of the script. This may" + " due to a bug in the simplification process, or there may be a bug in the script. " + "The error is shown below, and the simplified script can be reveiwed using " + "Actions→Developer→\"Show Simplified Script used for Completions.\"



"+ex.toString()+""; if ( rs!=null ) rs.addItem( new MessageCompletionItem("Eval error in code before current position", message)); int nlocal= getLocalsCompletions( interp, cc, rs); int nimportable; if ( cc.completable.length()>0 ) { nimportable= getImportableCompletions( eval, cc, rs ); } else { nimportable= 0; } return count + nlocal + nimportable + 1; } catch (IOException ex) { if ( rs!=null ) rs.addItem(new MessageCompletionItem("Error with completions",ex.toString())); return 0; } int nlocal= getLocalsCompletions( interp, cc, rs); int nimportable; if ( cc.completable.length()>0 ) { nimportable= getImportableCompletions( eval, cc, rs ); } else { nimportable= 0; } return count + nlocal + nimportable; } private static String argsList( Class[] classes ) { //String LPAREN="%28"; String LPAREN = "("; //String RPAREN="%29"; String RPAREN = ")"; String SPACE = " "; // "%20"; StringBuilder sig = new StringBuilder(); sig.append(LPAREN); List sargs = new ArrayList<>(); for (Class arg : classes ) { sargs.add(arg.getSimpleName()); } sig.append(join(sargs, "," )); sig.append(RPAREN); return sig.toString(); } private static String methodArgs(Method javaMethod) { return argsList( javaMethod.getParameterTypes()); } /** * Javadocs don't have the path on the constructor internal links. * @param c * @return */ private static String constructorSignatureNew( Constructor c ) { String n= c.getName(); //String[] ss= n.split("\\."); String javadocPath = join( n.split("\\."), "/") + ".html"; StringBuilder sig = new StringBuilder(javadocPath); //String LPAREN="%28"; String LPAREN = "("; //String RPAREN="%29"; String RPAREN = ")"; String name= c.getName(); int i= name.lastIndexOf("."); if ( i>-1 ) name= name.substring(i+1); sig.append("#").append( name ).append(LPAREN); List sargs = new ArrayList<>(); for (Class arg : c.getParameterTypes()) { sargs.add(arg.getCanonicalName()); } sig.append(join(sargs, "," )); sig.append(RPAREN); return sig.toString(); } private static String methodSignature(Method javaMethod) { String n= javaMethod.getDeclaringClass().getCanonicalName(); if ( n==null ) { // anonymous methods or inner class. return ""; } String javadocPath = join( n.split("\\."), "/") + ".html"; StringBuilder sig = new StringBuilder(javadocPath); //String LPAREN="%28"; String LPAREN = "("; //String RPAREN="%29"; String RPAREN = ")"; String SPACE = " "; // "%20"; sig.append("#").append(javaMethod.getName()).append(LPAREN); List sargs = new ArrayList<>(); for (Class arg : javaMethod.getParameterTypes()) { sargs.add(arg.getCanonicalName()); } sig.append(join(sargs, "," )); sig.append(RPAREN); return sig.toString(); } private String fieldSignature(Field f) { String javadocPath = join(f.getDeclaringClass().getCanonicalName().split("\\."), "/") + ".html"; StringBuilder sig = new StringBuilder(javadocPath); sig.append("#").append(f.getName()); return sig.toString(); } private String constructorSignature( Constructor f ) { String javadocPath = join( f.getDeclaringClass().getCanonicalName().split("\\."), "/") + ".html"; StringBuilder sig = new StringBuilder(javadocPath); int i= f.getName().lastIndexOf("."); sig.append("#").append(f.getName().substring(i + 1)); return sig.toString(); } /** * Do dataset URL completions on strings that start with / and getDataSet calls. * There's a bug here where "formatDataSet" is not detected as the context in * formatDataSet(ds,'/home/jbf/foo.cdf') * @param cc * @param arg0 * @return */ private int queryStringLiteralArgument(CompletionContext cc, CompletionResultSet arg0) { String method = cc.contextString; int [] pos= new int[2]; Map r= DataSetUrlCompletionTask.popString(editor,pos); String s= (String)r.get("string"); if (method.equals("getDataSet") || method.equals("plot") || method.equals("plotx") || method.equals("getCompletions") ) { DataSetUrlCompletionTask task = new DataSetUrlCompletionTask(editor); task.query(arg0); } else if ( method.equals("File") && s.startsWith("/") ) { DataSetUrlCompletionTask task = new DataSetUrlCompletionTask(editor); task.query(arg0); } else if ( method.equals("'resourceURI'") ) { DataSetUrlCompletionTask task = new DataSetUrlCompletionTask(editor); task.query(arg0); } else if ( s.startsWith("/") ) { DataSetUrlCompletionTask task = new DataSetUrlCompletionTask(editor); task.query(arg0); } return 0; } /** * show the documentation found for the command. * @param cc * @param result * @return * @throws BadLocationException */ private int queryCommandArgument(CompletionContext cc, CompletionResultSet result ) throws BadLocationException { logger.fine("queryCommandArgument"); String method = cc.contextString; PythonInterpreter interp = getInterpreter(); String eval; eval= editor.getText(0, Utilities.getRowStart(editor, editor.getCaretPosition())); if ( JythonCompletionProvider.getInstance().settings().isSafeCompletions() ) { eval= sanitizeLeaveImports( eval ); } try { interp.exec(eval); } catch ( PyException ex ) { result.addItem(new MessageCompletionItem("Eval error in code before current position", ex.toString())); return 0; } try { PyObject po= interp.eval(method); PyObject doc= interp.eval(method+".__doc__"); PyObject completions; try { completions = interp.eval(method+".__completions__"); } catch ( PyException ex ) { completions = null; } if ( po instanceof PyFunction ) { method= getPyFunctionSignature((PyFunction)po); String signature= makeInlineSignature( po, doc ); result.addItem( new MessageCompletionItem( method, signature ) ); } else if ( po instanceof PyReflectedFunction ) { PyReflectedFunction prf = (PyReflectedFunction) po; List labels= new ArrayList(); List signatures= new ArrayList(); List argss= new ArrayList(); doPyReflectedFunction(eval, prf, labels, signatures, argss ); for ( int jj=0; jj rr= getLocalsCompletions( interp, cc ); for ( DefaultCompletionItem item: rr ) { if ( rs!=null ) rs.addItem( item ); count++; } return count; } /** * get completions by looking at importLookup.jy, which is a list of commonly imported codes. * @param source the script source. * @param cc * @param result * @return */ public static int getImportableCompletions( String source, CompletionContext cc, CompletionResultSet result ) { int count= 0; List completions= JythonToJavaConverter.guessCompletions(cc.completable); for ( String ss: completions ) { String pkg= JythonToJavaConverter.guessPackage(ss); if ( !JythonToJavaConverter.hasImport( source, pkg, ss ) ) { String javaClass= pkg+"."+ss; String signature= join( javaClass.split("\\."), "/") + ".html"; String link= JavadocLookup.getInstance().getLinkForJavaSignature(signature); ClassImportCompletionItem ci= new ClassImportCompletionItem( cc.completable, cc.completable.length(), ss, ss + " and import from " + pkg, link, 0, JAVA_CLASS_ICON, pkg, ss ); result.addItem( ci ); } count++; } return count; } /** * replace java names like org.das2.qds.QDataSet with less-ominous names like "QDataSet" * @param label * @return the simplified name. */ private static String hideJavaPaths( String label ) { StringBuffer build= new StringBuffer(); Pattern p= Pattern.compile("(org.das2.qds.QDataSet|java.lang.String|java.lang.Object|org.das2.util.monitor.ProgressMonitor|org.das2.datum.DatumRange|org.das2.datum.Datum)"); Matcher m= p.matcher(label); while ( m.find() ) { String s= m.group(1); switch (s) { case "org.das2.qds.QDataSet": m.appendReplacement(build,"QDataSet"); break; case "java.lang.String": m.appendReplacement(build,"String"); break; case "java.lang.Object": m.appendReplacement(build,"Object"); break; case "org.das2.util.monitor.ProgressMonitor": m.appendReplacement(build,"Monitor"); break; case "org.das2.datum.DatumRange": m.appendReplacement(build,"DatumRange"); break; case "org.das2.datum.Datum": m.appendReplacement(build,"Datum"); break; default: break; } } m.appendTail(build); return build.toString(); } public static String escapeHtml( String s ) { StringBuffer out= new StringBuffer(); Pattern p= Pattern.compile("([\\<\\>])"); Matcher m= p.matcher(s); while ( m.find() ) { m.appendReplacement( out, "" ); String ss= m.group(1); if ( ss.equals("<") ) { out.append( "<"); } else if ( ss.equals(">") ) { out.append( ">"); } } m.appendTail(out); return out.toString(); } /** * get the python signature for the function. I can't figure out how to get defaults for the named keyword parameters. * @param pf the function * @return String like "docDemo8( arg )" */ private static String getPyJavaClassSignature( PyJavaClass pf ) { Class javaClass= getJavaClass(pf); return javaClass.getCanonicalName().replaceAll("\\.","/"); } /** * get the python signature for the function. I can't figure out how to get defaults for the named keyword parameters. * @param pf the function * @return String like "docDemo8( arg )" */ private static String getPyFunctionSignature( PyFunction pf ) { Object[] defaults= pf.func_defaults; String[] vars= ((PyTableCode)pf.func_code).co_varnames; int count= ((PyTableCode)pf.func_code).co_argcount; StringBuilder sig= new StringBuilder( pf.__name__+"(" ); if ( count>0 ) { if ( defaults.length==vars.length ) { sig.append(vars[0]).append("=").append(defaults[0]); } else { sig.append(vars[0]); } } int nreq= vars.length-defaults.length; for ( int i=1; i=nreq) { sig.append(",").append(vars[i]).append("=").append(defaults[i-nreq]); } else { sig.append(",").append(vars[i]); } } if ( count + defaults.length == vars.length-2 ) { // quick kludge for var args see /home/jbf/ct/autoplot/script/demos/operators/synchronizeDemo.jy sig.append(",..."); } sig.append(")"); return sig.toString(); } /** * get __doc__ from the function. * @param po PyFunction, typically. * @param doc the documentation for the function. * @return "inline:..." */ private static String makeInlineSignature( PyObject po, PyObject doc ) { String sig= ( po instanceof PyFunction ) ? getPyFunctionSignature((PyFunction)po) : ""; String signature= doc instanceof PyNone ? "(No documentation)" : doc.toString(); if ( sig.length()>0 ) { sig= ""+sig+ "

"; } String[] ss2= signature.split("\n"); if ( ss2.length>1 ) { for ( int jj= 0; jj< ss2.length; jj++ ){ ss2[jj]= escapeHtml(ss2[jj]); } if ( !signature.startsWith("" ) ) { signature= ""+sig+join( ss2, "
" )+""; } else { signature= ""+sig+ signature.substring(6)+""; } } else { signature= ""+sig+ signature+""; } signature= "inline:" + signature; return signature; } private static void doPyReflectedFunction( String ss, PyReflectedFunction prf, List labels, List signatures, List argss ) { PyReflectedFunctionPeeker peek = new PyReflectedFunctionPeeker(prf); for ( int jj=0; jj-1 ) { label= signature.substring(j+1); label= hideJavaPaths( label ); Class ret= method1.getReturnType(); label= label + "->" + hideJavaPaths( ret.getCanonicalName() ); } signatures.add(signature); labels.add(label); argss.add(args); } } private static void doConstructors( Constructor[] constructors, List labels, List signatures, String ss, List argss ) { for (Constructor constructor : constructors) { String signature = constructorSignatureNew(constructor); if ( signature.contains("$") ) { signature= signature.replaceAll("\\$","."); } int j= signature.indexOf("#"); String label= ss + "() JAVA"; if (j>-1) { label= signature.substring(j+1); label= hideJavaPaths( label ); Class ret = constructor.getDeclaringClass(); label= label + "->" + hideJavaPaths( ret.getCanonicalName() ); } signatures.add(signature); labels.add(label); argss.add(argsList(constructor.getParameterTypes())); } } /** * sorts all the lists by the first list. * See http://stackoverflow.com/questions/15400514/syncronized-sorting-between-two-arraylists/24688828#24688828 * Note the key list must be repeated for it to be sorted as well! * @param the list type * @param key the list used to sort * @param lists the lists to be sorted, often containing the key as well. */ public static > void keySort( final List key, List... lists){ // Create a List of indices List indices = new ArrayList<>(); for(int i = 0; i < key.size(); i++) { indices.add(i); } // Sort the indices list based on the key Collections.sort(indices, new Comparator(){ @Override public int compare(Integer i, Integer j) { return key.get(i).compareTo(key.get(j)); } }); // Create a mapping that allows sorting of the List by N swaps. // Only swaps can be used since we do not know the type of the lists Map swapMap = new HashMap<>(indices.size()); List swapFrom = new ArrayList<>(indices.size()), swapTo = new ArrayList<>(indices.size()); for(int i = 0; i < key.size(); i++){ int k = indices.get(i); while(i != k && swapMap.containsKey(k)) { k = swapMap.get(k); } swapFrom.add(i); swapTo.add(k); swapMap.put(i, k); } // use the swap order to sort each list by swapping elements for(List list : lists) { for(int i = 0; i < list.size(); i++) { Collections.swap(list, swapFrom.get(i), swapTo.get(i)); } } } /** * At some point we decided all the methods would take Object as well as QDataSet, and then convert * to these. This should be discouraged, and hide these in the popups. * @param m1 * @param m2 * @return */ private static boolean methodIsSuperset( String m1, String m2 ) { Pattern p0= Pattern.compile("([a-zA-Z0-9/]*\\.html)#([a-zA-Z0-9]*)\\((([a-zA-Z0-9\\.\\[\\]]+)?(,([a-zA-Z0-9\\.\\[\\]]+))*)\\)" ); Matcher m8= p0.matcher(m1); Matcher m9= p0.matcher(m2); if ( m8.matches() && m9.matches() ) { String s1= m8.group(3); String s2= m9.group(3); String[] s8= s1.split(",",-2); String[] s9= s2.split(",",-2); if ( s8.length==s9.length ) { boolean superSet= true; for ( int i=0; i signatures, List labels, List argss ) { if ( signatures.size()>1 ) { for ( int i=1; i getLocalsCompletions(PythonInterpreter interp, CompletionContext cc) { List result= new ArrayList(); PyStringMap locals = (PyStringMap) interp.getLocals(); PyList po2 = locals.keys(); for (int i = 0; i < po2.__len__(); i++) { ImageIcon icon= null; PyString s = (PyString) po2.__getitem__(i); String ss = s.toString(); String signature = null; // java signature List signatures= new ArrayList(); List argss= new ArrayList(); if (ss.startsWith(cc.completable)) { if ( ss.endsWith(__CLASSTYPE) ) { ss= ss.substring(0,ss.length()-__CLASSTYPE.length()); if ( !ss.startsWith(cc.completable) ){ continue; } else { result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss, ss, null, LOCALVAR_SORT, LOCALVARICON ) ); continue; } } logger.log(Level.FINER, "found completion item: {0}", ss); boolean allStatic= false; // true if the completion is a utility class. PyObject po = locals.get(s); String label = ss; List labels= new ArrayList(); String args = ""; if (po instanceof PyReflectedFunction) { PyReflectedFunction prf = (PyReflectedFunction) po; doPyReflectedFunction( ss, prf, labels, signatures, argss ); } else if (po.isCallable()) { label = ss + "() "; if ( po instanceof PyFunction ) { label= getPyFunctionSignature((PyFunction)po); args= label.substring(ss.length()); } PyObject doc= interp.eval(ss+".__doc__"); signature= makeInlineSignature( po, doc ); } else if (po.isNumberType()) { switch (po.getType().getFullName()) { case "javaclass": case "javainnerclass": label = ss; Class jclass= getJavaClass((PyJavaClass)po); String n= jclass.getCanonicalName(); allStatic= true; logger.log(Level.FINER, "check for non-static methods: {0}", n); Method[] mm= jclass.getMethods(); for ( Method m: mm ) { if ( !m.getDeclaringClass().equals(Object.class) ) { if ( !Modifier.isStatic(m.getModifiers()) ) { logger.log(Level.FINEST, "not static: {0}", m.getName()); allStatic= false; } } } logger.log(Level.FINER, " class is all static methods: {0}", allStatic ); if ( allStatic ) { doConstructors(jclass.getConstructors(),labels,signatures,n,argss); for ( int i1=0; i1 "+po+"", link, LOCALVAR_SORT, LOCALVARICON ) ); } else { result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label+" -> "+po+"", link, JAVASTATICFIELD_SORT, null ) ); } } else { if ( allStatic ) { result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args + ".", label, link, JAVACLASS_SORT, JAVA_CLASS_ICON) ); } else { if ( po instanceof PyJavaClass ) { result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, JAVACLASS_SORT, JAVA_CONSTRUCTOR_ICON ) ); } else if ( po.getType().toString().contains("Command") ) { // TODO: FIX THIS if ( ss.equals("plotx") ) continue; result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, AUTOCOMMAND_SORT, JAVA_JYTHON_METHOD_ICON ) ); } else if ( po instanceof PyFunction ) { result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, AUTOCOMMAND_SORT, JAVA_JYTHON_METHOD_ICON ) ); } else if ( po instanceof PyClass ) { result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, PYCLASS_SORT, JAVA_JYTHON_METHOD_ICON ) ); } else if ( po instanceof PyReflectedField ) { result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, PYREFLECTEDFIELD_SORT, JAVA_JYTHON_METHOD_ICON ) ); } else { switch (ss) { case "monitor": case "dom": case "PI": case "TAU": case "E": result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, AUTOVAR_SORT, LOCALVARICON ) ); break; case "params": case "outputParams": case "__doc__": //things I don't want developers to see result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, AUTOVARHIDE_SORT, LOCALVARICON ) ); break; default: result.add( new DefaultCompletionItem(ss, cc.completable.length(), ss + args, label, link, LOCALVAR_SORT, LOCALVARICON ) ); break; } } } } } } } logger.log( Level.FINE, "getLocalsCompletions found {0} completions", new Object[]{ result.size() } ); return result; } /** * return the Java class for the PyJavaClass. The implementation may change * with Jython2.7. * @param po the PyJavaClass wrapper. * @return the Java class. */ private static Class getJavaClass(PyJavaClass po) { PyJavaClassPeeker peek= new PyJavaClassPeeker(po); Class jclass= peek.getProxyClass(); return jclass; } /** * return a link to the documentation for a java signature. For standard library * things, this goes to Oracle's website. For other things, this goes * to the Autoplot/Das2 javadocs. * @param signature signature like javax.swing.JCheckBox#paramString() * @return the link, like http://docs.oracle.com/javase/7/docs/api/javax/swing/JCheckBox#paramString() */ private static String getLinkForJavaSignature(String signature) { return JavadocLookup.getInstance().getLinkForJavaSignature(signature); } /** * return an identifying icon for the object, or null. * @param jm java.lang.reflect.Method, or PyInteger, etc. * @return the icon or null. */ public static ImageIcon getIconFor(Object jm) { ImageIcon icon=null; if ( jm instanceof java.lang.reflect.Method ) { Method m= (Method)jm; if ( Modifier.isStatic(m.getModifiers()) ) { icon= JAVA_STATIC_METHOD_ICON; } else { icon= JAVA_METHOD_ICON; } } else if ( jm instanceof java.lang.reflect.Field ) { Field m= (Field)jm; if ( Modifier.isStatic(m.getModifiers()) ) { try { Object o= m.get(java.awt.Color.class); if ( o instanceof Color ) { Color testColor= (Color) o; return GraphUtil.colorImageIcon( testColor, 16, 16 ); } else { return JAVA_FIELD_ICON; } } catch (IllegalArgumentException | IllegalAccessException ex) { logger.log( Level.FINE, null, ex ); return JAVA_FIELD_ICON; } } else { icon= JAVA_FIELD_ICON; } } return icon; } }