package org.autoplot.scriptconsole; import com.github.difflib.UnifiedDiffUtils; import com.github.difflib.patch.AbstractDelta; import com.github.difflib.patch.ChangeDelta; import com.github.difflib.patch.Chunk; import com.github.difflib.patch.DeleteDelta; import com.github.difflib.patch.InsertDelta; import com.github.difflib.patch.Patch; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Event; import java.awt.HeadlessException; import java.awt.event.KeyEvent; import java.beans.ExceptionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.OutputStream; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.Preferences; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.DefaultListModel; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import org.autoplot.jythonsupport.JythonRefactory; import org.das2.components.DasProgressPanel; import org.das2.util.filesystem.FileSystem; import org.das2.util.monitor.NullProgressMonitor; import org.das2.util.monitor.ProgressMonitor; import org.python.core.FileUtil; import org.python.core.Py; import org.python.core.PyException; import org.python.core.PyInteger; import org.python.core.PyObject; import org.python.core.PySyntaxError; import org.python.core.PyTraceback; import org.python.core.ThreadState; import org.python.util.InteractiveInterpreter; import org.python.util.PythonInterpreter; import org.autoplot.ApplicationModel; import org.autoplot.AutoplotUtil; import org.autoplot.JythonUtil; import org.autoplot.datasource.AutoplotSettings; import org.autoplot.dom.ApplicationController; import org.autoplot.datasource.DataSetSelector; import org.autoplot.datasource.DataSetURI; import org.autoplot.datasource.FileSystemUtil; import org.autoplot.datasource.URISplit; import org.autoplot.datasource.jython.JythonDataSource; import org.autoplot.datasource.jython.JythonDataSourceFactory; import org.autoplot.jythonsupport.ui.EditorAnnotationsSupport; import org.autoplot.jythonsupport.ui.ParametersFormPanel; import org.das2.util.ColorUtil; import org.das2.util.filesystem.GitCommand; import org.python.parser.Node; import org.python.parser.ast.Expr; import org.python.parser.ast.Str; import org.python.parser.ast.TryExcept; import org.python.parser.ast.stmtType; /** * Error annotations, saveAs, etc. * @see EditorAnnotationsSupport * @author jbf */ public class ScriptPanelSupport { private static final Logger logger= org.das2.util.LoggerManager.getLogger("autoplot.jython"); private static final int RECENT_FILES_COUNT = 100; File file; final ApplicationModel model; final ApplicationController applicationController; final DataSetSelector selector; final JythonScriptPanel panel; final EditorAnnotationsSupport annotationsSupport; private final String PREFERENCE_OPEN_FILE = "openFile"; private InteractiveInterpreter interruptible; ThreadState ts; ScriptPanelSupport(final JythonScriptPanel panel, final ApplicationModel model, final DataSetSelector selector) { this.model = model; this.applicationController = model.getDocumentModel().getController(); this.selector = selector; this.panel = panel; this.annotationsSupport = panel.getEditorPanel().getEditorAnnotationsSupport(); applicationController.addPropertyChangeListener(ApplicationController.PROP_FOCUSURI, (PropertyChangeEvent evt) -> { if ( panel.runningScript==null ) { maybeDisplayDataSourceScript(); } }); } private boolean canDisplayDataSourceScript( String sfile ) { URISplit split; if (sfile == null) { return false; } split = URISplit.parse(sfile); if (!( URISplit.implicitVapScheme(split).endsWith("jyds") ) ) { return false; } if (panel.isDirty()) { logger.fine("editor is dirty, not showing script."); return false; } if ( this.panel.getRunningScript()!=null ) { logger.fine("editor is busy running a script."); return false; } String existingFile=this.panel.getFilename(); if ( existingFile!=null ) { URISplit split2= URISplit.parse(existingFile); if ( split2.ext.equals(".jy") ) { return false; } } return true; } private boolean scriptIsAlreadyShowing( String sfile ) { try { URISplit split= URISplit.parse(sfile); if ( split.params!=null ) { Map params= URISplit.parseParams(split.params); if ( params.containsKey("script") ) { sfile= params.get("script"); } else { sfile= split.resourceUri.toString(); } } else { sfile= split.resourceUri.toString(); } final URI fsfile= DataSetURI.getURI(sfile); if ( panel.getFilename()!=null ) { URI u1= DataSetURI.getURI(panel.getFilename()); String f1= URISplit.parse(u1).file; URI u2= DataSetURI.getURI(sfile); String f2= URISplit.parse(u2).file; if ( f1.startsWith("file:" ) && f2.startsWith("file:") ) { File ff1= new File( f1.substring(5) ); //TODO: Windows File ff2= new File( f2.substring(5) ); if ( ff1.equals(ff2) ) { return true; } } File ff3= DataSetURI.getCacheFilename( fsfile ); if ( f1.startsWith("file:") && ff3!=null ) { File ff1= new File( f1.substring(5) ); //TODO: Windows if ( ff3.equals(ff1) ) { return true; } } } } catch (URISyntaxException ex) { logger.log(Level.SEVERE, null, ex); } return false; } /** * @return true if the source is displayed. * @throws java.awt.HeadlessException */ private boolean maybeDisplayDataSourceScript() throws HeadlessException { try { final String sfile= applicationController.getFocusUri(); if ( sfile.trim().isEmpty() ) return false; final String fsfile; URISplit split= URISplit.parse(sfile); Map params= URISplit.parseParams(split.params); String script= params.get(JythonDataSource.PARAM_SCRIPT); if ( script!=null && script.trim().length()>0 ) { fsfile= script; } else { fsfile= split.file; } if ( canDisplayDataSourceScript( fsfile )==false ) { return false; } if ( scriptIsAlreadyShowing( fsfile )==true ) { return true; } //TODO: why can't we have a DasProgressPanel on any component? Runnable swrun= () -> { try { panel.getEditorPanel().getEditorKit(); MutableAttributeSet att= new SimpleAttributeSet(); StyleConstants.setItalic( att, true); ScriptPanelSupport.this.file= null; panel.getEditorPanel().getDocument().remove( 0, panel.getEditorPanel().getDocument().getLength() ); panel.getEditorPanel().getDocument().insertString( 0, "loading "+fsfile, att ); } catch ( BadLocationException ex ) { logger.log(Level.SEVERE, null, ex); } }; if ( SwingUtilities.isEventDispatchThread() ) { swrun.run(); } else { try { SwingUtilities.invokeAndWait(swrun); } catch (InterruptedException | InvocationTargetException ex) { logger.log(Level.SEVERE, null, ex); } } Runnable run= () -> { try { file = DataSetURI.getFile( fsfile, new NullProgressMonitor() ); loadFile(file); panel.setContext(JythonScriptPanel.CONTEXT_DATA_SOURCE); panel.setFilename(file.toString()); } catch (IOException ex) { logger.log(Level.SEVERE, ex.getMessage(), ex); } }; new Thread( run, "load script thread" ).start(); } catch (NullPointerException ex) { logger.log(Level.SEVERE, ex.getMessage(), ex); return false; } return true; } /** * get the save file name with a save file dialog. * @return response code, such as JFileChooser.APPROVE_OPTION or JFileChooser.CANCEL_OPTION * @throws IOException * @see javax.swing.JFileChooser#showSaveDialog(java.awt.Component) */ public int getSaveFile() throws IOException { JFileChooser chooser = new JFileChooser(); chooser.setFileFilter(new FileNameExtensionFilter( "python and jython scripts", new String [] { "jy", "py", "jyds" } )); final JCheckBox cb= new JCheckBox("rename"); cb.setEnabled(true); cb.setToolTipText("rename file, deleting old name \""+file+"\""); // chooser.addPropertyChangeListener( JFileChooser.SELECTED_FILE_CHANGED_PROPERTY, new PropertyChangeListener() { // @Override // public void propertyChange(PropertyChangeEvent evt) { // System.err.println("eh?"); // if ( evt.getOldValue()!=null ) { // System.err.println("evt name change"+evt.getOldValue()+"-->"); // System.err.println("evt name change"+evt.getNewValue()); // cb.setEnabled(true); // } // } // }); // This works inconsistently. chooser.setAccessory(cb); if (file != null && ! FileSystemUtil.isChildOf( FileSystem.settings().getLocalCacheDir(), file ) ) { chooser.setSelectedFile(file); } else { if ( FileSystemUtil.isChildOf( FileSystem.settings().getLocalCacheDir(), file ) ) { String home = System.getProperty("user.home"); File ff = new File( home + File.separator + file.getName() ); chooser.setSelectedFile(ff); } else { chooser.setSelectedFile(file); } Preferences prefs = AutoplotSettings.getPreferences(ScriptPanelSupport.class); String openFile= prefs.get(PREFERENCE_OPEN_FILE, ""); if ( !openFile.equals("") && !FileSystemUtil.isChildOf( FileSystem.settings().getLocalCacheDir(), new File(openFile) ) ) { File dir= new File(openFile).getParentFile(); chooser.setCurrentDirectory( dir ); } } if ( file==null ) { Preferences prefs = AutoplotSettings.getPreferences(ScriptPanelSupport.class); String openFile = prefs.get(PREFERENCE_OPEN_FILE, ""); if ( !openFile.equals("") ) { chooser.setCurrentDirectory( new File(openFile).getParentFile() ); } } int r = chooser.showSaveDialog(panel); if (r == JFileChooser.APPROVE_OPTION) { File oldFile= file; file = chooser.getSelectedFile(); if (!(file.toString().endsWith(".jy") || file.toString().endsWith(".py") || file.toString().endsWith(".jyds"))) { if (panel.getContext() == JythonScriptPanel.CONTEXT_DATA_SOURCE) { file = new File(file.toString() + ".jyds"); } else { file = new File(file.toString() + ".jy"); } } if ( oldFile!=null ) { if ( cb.isSelected() && !oldFile.equals(file) ) { if ( ! oldFile.delete() ) { JOptionPane.showMessageDialog( panel, "unable to delete old file: "+oldFile ); } } } } return r; } /** * save the editor contents to a file. Note this used to return JOptionPane.OK_OPTION, but this is also 0. * @return JFileChooser.APPROVE_OPTION or JFileChooser.CANCEL_OPTION if the operator cancelled the saveAs operation. * @throws FileNotFoundException * @throws IOException */ protected int save() throws FileNotFoundException, IOException { if ( file==null || file.toString().contains( FileSystem.settings().getLocalCacheDir().toString()) ) { logger.fine("file is null "); if ( panel.isDirty() ) { return saveAs(); } return JFileChooser.APPROVE_OPTION; } OutputStream out = null; try { if ( !( file.exists() && file.canWrite() || file.getParentFile().canWrite() ) ) throw new IOException("unable to write to file: "+file); if ( watcher!=null ) { try { logger.fine("closing watcher"); watcher.close(); } catch ( IOException ex ) { logger.log( Level.WARNING, ex.getMessage(), ex ); } } else { logger.fine("logger was null"); } out = new FileOutputStream(file); String text = panel.getEditorPanel().getText(); out.write(text.getBytes()); panel.setDirty(false); final File ffile= file; Runnable run= () -> { try { logger.fine("pausing before restarting watcher"); Thread.sleep(10000); } catch (InterruptedException ex) { Logger.getLogger(ScriptPanelSupport.class.getName()).log(Level.SEVERE, null, ex); } try { logger.fine("restarting watcher"); restartWatcher(ffile); } catch (IOException ex) { Logger.getLogger(ScriptPanelSupport.class.getName()).log(Level.SEVERE, null, ex); } }; new Thread(run).start(); } finally { if ( out!=null ) out.close(); } return JFileChooser.APPROVE_OPTION; } WatchService watcher; private void watcherRunnable( final WatchService watch, final Path fpath ) { Runnable run= () -> { Path parent= fpath.getParent(); logger.log(Level.FINER, "start watch event loop on {0}", new Object[]{parent}); while ( true ) { try { WatchKey key= watcher.take(); for ( WatchEvent e : key.pollEvents() ) { WatchEvent ev = (WatchEvent) e; Path name = ev.context(); logger.log(Level.FINER, "watch event {0} {1}", new Object[]{ev.kind(), ev.context()}); if ( parent.resolve(name).equals(fpath) ) { String newContents; try ( InputStream in = new FileInputStream( ScriptPanelSupport.this.file ) ) { newContents= new String( FileUtil.readBytes( in ) ); String currentf= panel.getEditorPanel().getText(); currentf= currentf.trim(); // there's a strange bug where newContents has a newline at the end that current doesn't. newContents= newContents.trim(); if ( currentf.equals(newContents) ) { logger.fine("timestamp changed but contents are the same."); break; } } catch ( IOException ex ) { break; } if ( !ScriptPanelSupport.this.panel.isDirty() ) { try { Color color= ScriptPanelSupport.this.panel.getBackground(); ScriptPanelSupport.this.panel.setBackground(ColorUtil.DODGER_BLUE); Thread.sleep(300); ScriptPanelSupport.this.panel.setBackground(color); loadFile( ScriptPanelSupport.this.file ); } catch ( IOException ex ) { ScriptPanelSupport.this.panel.setDirty(true); } } else { if ( JOptionPane.OK_OPTION== JOptionPane.showConfirmDialog( panel, "File changed on disk. Do you want to reload?", "File Changed on Disk", JOptionPane.OK_CANCEL_OPTION ) ) { try { loadFile( ScriptPanelSupport.this.file ); } catch (IOException ex) { logger.log(Level.SEVERE, null, ex); } } else { ScriptPanelSupport.this.panel.setDirty(true); } } } } if ( !key.reset() ) { logger.log(Level.FINER, "watch key could not be reset: {0}", key); return; } } catch ( ClosedWatchServiceException ex ) { logger.log(Level.FINER, "watch service was closed: {0}", watch); return; } catch (InterruptedException ex) { logger.log(Level.SEVERE, null, ex); } } }; new Thread(run,"fileWatcherRunnable").start(); } private void restartWatcher( File file ) throws IOException { logger.entering( "org.autoplot.scriptconsole", "restartWatcher {0}", file ); if ( watcher!=null ) { watcher.close(); } watcher = FileSystems.getDefault().newWatchService(); Path fpath= file.toPath(); Path parent= fpath.getParent(); if ( parent==null ) return; try { parent.register( watcher, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE ); //parent.register( watcher, StandardWatchEventKinds.OVERFLOW ); watcherRunnable( watcher, file.toPath() ); } catch ( ClosedWatchServiceException ex ) { logger.fine("watch service was closed"); } logger.exiting("org.autoplot.scriptconsole", "restartWatcher {0}", file ); } protected void loadFile(File file) throws IOException, FileNotFoundException { try (InputStream r = new FileInputStream(file)) { this.file= file; panel.setFilename( file.toString() ); loadInputStream( r ); if (file.toString().endsWith(".jyds")) { panel.setContext(JythonScriptPanel.CONTEXT_DATA_SOURCE); } else { panel.setContext(JythonScriptPanel.CONTEXT_APPLICATION); } } restartWatcher(file); } protected void loadInputStream( InputStream in ) throws IOException { BufferedReader r = null; panel.containsTabs= false; try { StringBuilder buf = new StringBuilder(); r = new BufferedReader(new InputStreamReader(in)); String s = r.readLine(); int tabWarn= 3; int lineNum= 1; while (s != null) { if ( s.contains("\t") ) { panel.containsTabs= true; if ( tabWarn>0 ) { logger.log(Level.FINE, "line {0} contains tabs: {1}", new Object[] { lineNum, s } ); tabWarn--; } } buf.append(s).append("\n"); s = r.readLine(); lineNum++; } final String fs= buf.toString(); Runnable run= () -> { try { annotationsSupport.clearAnnotations(); Document d = panel.getEditorPanel().getDocument(); d.remove(0, d.getLength()); d.insertString(0, fs, null); panel.setDirty(false); panel.resetUndo(); } catch ( NullPointerException ex ) { try { Document d = panel.getEditorPanel().getDocument(); d.remove(0, d.getLength()); d.insertString(0, fs, null); panel.setDirty(false); } catch ( BadLocationException ex2 ) { } } catch ( BadLocationException ex ) { } }; SwingUtilities.invokeLater(run); } finally { if (r != null) r.close(); } } private boolean uriFilesEqual(String surl1, String surl2) throws URISyntaxException { int i1 = surl1.indexOf('?'); if (i1 == -1) { i1 = surl1.length(); } URI uri1 = DataSetURI.getURI(surl1.substring(0, i1)); int i2 = surl2.indexOf('?'); if (i2 == -1) { i2 = surl2.length(); } URI uri2 = DataSetURI.getURI(surl2.substring(0, i2)); if ( uri1==null ) return false; return uri1.equals(uri2); } /** * march through the stack trace looking for any Jython references. * @param ex the exception */ public void annotateError( Throwable ex ) { int line; // the line number StackTraceElement[] ses= ex.getStackTrace(); if ( file!=null ) { for ( StackTraceElement se: ses ) { if ( se!=null && se.getFileName()!=null && se.getFileName().endsWith(file.getName()) && se.getLineNumber()>-1 ) { line= se.getLineNumber(); annotationsSupport.annotateLine( line, "error", ex.toString(), null ); } } } } /** * Annotate the error. * @param ex the exception * @param offset line offset from beginning of file where execution began. * @param interp null or the interp for further queries. */ public void annotateError(PyException ex, int offset, final PythonInterpreter interp) { if (ex instanceof PySyntaxError) { logger.log(Level.SEVERE, ex.getMessage(), ex); int lineno = offset + ((PyInteger) ex.value.__getitem__(1).__getitem__(1)).getValue(); //String filename= String.valueOf( (ex.value.__getitem__(1).__getitem__(3)) ); //int col = ((PyInteger) ex.value.__getitem__(1).__getitem__(2)).getValue(); annotationsSupport.annotateLine(lineno, "error", ex.toString(),interp); } else { //logger.log(Level.SEVERE, ex.getMessage(), ex); PyObject otraceback= ex.traceback; int line=0; final int STACK_LIMIT=7; int count=0; // just in case limit to STACK_LIMIT, because of recursion, etc. //PyFrame theFrame= null; while ( otraceback instanceof PyTraceback && count") || fn.equals("") || ( file!=null && fn.equals( file.getName() ) ) ) ) { annotationsSupport.annotateLine(offset + traceback.tb_lineno, "error", ex.toString(),interp); line= traceback.tb_lineno-1; otraceback= traceback.tb_next; count++; } else { otraceback= traceback.tb_next; } } } //System.err.println("***"); //System.err.println("line="+line); if ( line<0 ) { logger.log(Level.WARNING, "no trace information available for error {0}", ex.getMessage()); line=0; } final int fline= line; final JEditorPane textArea= panel.getEditorPanel(); SwingUtilities.invokeLater(() -> { Element element= textArea.getDocument().getDefaultRootElement().getElement(Math.max(0,fline-5)); // 5 lines of context. if ( element!=null ) textArea.setCaretPosition(element.getStartOffset()); SwingUtilities.invokeLater(() -> { Element element1 = textArea.getDocument().getDefaultRootElement().getElement(fline); // 5 lines of context. if (element1 != null) { textArea.setCaretPosition(element1.getStartOffset()); } }); }); } } protected void executeScript() { executeScript(0); } /** * allow execute with trace. This never worked effectively. * @param trace */ protected void executeScript(final boolean trace) { executeScript( trace ? java.awt.Event.CTRL_MASK : 0 ); } /** * clear the annotations and replace lint-type annotations. */ private void clearAnnotations() { annotationsSupport.clearAnnotations(); List errs= new ArrayList(); try { boolean lintWarning; if ( file==null ) { String text = panel.getEditorPanel().getText(); try ( LineNumberReader r= new LineNumberReader( new BufferedReader( new StringReader(text) ) ) ) { lintWarning= org.autoplot.jythonsupport.JythonUtil.pythonLint( r, errs); } } else { lintWarning= org.autoplot.jythonsupport.JythonUtil.pythonLint( file.toURI(), errs); } if ( lintWarning ) { EditorAnnotationsSupport esa= panel.getEditorPanel().getEditorAnnotationsSupport(); for ( String s: errs ) { String[] ss= s.split(":",2); try { String doc= ss[1]; doc= doc.replaceAll("<", "<"); doc= doc.replaceAll(">", ">"); esa.annotateLine(Integer.parseInt(ss[0]), EditorAnnotationsSupport.ANNO_CODE_HINT, "Variable name is already used before execution: " + doc + "
Consider using a different name"); } catch (BadLocationException ex) { logger.log(Level.SEVERE, ex.getMessage(), ex); } } } } catch (IOException ex) { logger.log(Level.SEVERE, null, ex); } } /** * Execute the script in the current thread. For context data source, * this means putting the URI in the data set selector and telling it to * plot. "mode" controls how the script is run, for example by bringing up * the parameters GUI first, or by tracing execution. The bit array is:
    *
  • 0 normal *
  • 1 parameters GUI (SHIFT is pressed) *
  • 2 trace (CRTL is pressed) *
  • 8 enter editor (ALT is pressed) *
* @param mode bit array controlling execution. * @throws RuntimeException * @throws Error */ private void executeScriptImmediately( int mode ) throws RuntimeException, Error { int offset = 0; int lineOffset= 0; //ProgressMonitor mon= //DasProgressPanel.createComponentPanel(model.getCanvas(),"running script"); ProgressMonitor mon= DasProgressPanel.createFramed( SwingUtilities.getWindowAncestor(panel), "running script"); try { try { if (file != null && ( file.exists() && file.canWrite() || file.getParentFile().canWrite() ) ) { if ( panel.isDirty() ) { save(); } } } catch ( SecurityException ex ) { // this is fine, we just can't save a modified version. } finally { if ( file!=null ) { applicationController.getApplicationModel().addRecent("script:"+file.toURI().toString()); } } InteractiveInterpreter interp = null; try { interp= JythonUtil.createInterpreter(true, false); EditorAnnotationsSupport.setExpressionLookup(annotationsSupport.getForInterp(interp)); interp.set("dom", model.getDocumentModel() ); interp.set("monitor", mon ); if ( file!=null ) { URISplit split= URISplit.parse(file.toString()); interp.set( "PWD", split.path ); } setInterruptible( interp ); ts= Py.getThreadState(); boolean dirty0 = panel.isDirty(); clearAnnotations(); panel.setDirty(dirty0); if ( ( ( mode & Event.CTRL_MASK ) == Event.CTRL_MASK ) ) { // trace String script = panel.getEditorPanel().getText(); org.python.parser.ast.Module n = (org.python.parser.ast.Module) org.python.core.parser.parse(script, "exec"); String[] sscript= script.split("\n"); for ( int iline=0; ilinei ) { if ( i>-1 ) { s= s.substring(0,i)+"..."; } } else { s= s.substring(0,i); } logger.log( Level.FINE, String.format( "%4d %.3f %4d # %s", l0, elapsedTime/1000., l1-l0, s ) ); } } clearAnnotations(); } else if ( ( ( mode & Event.SHIFT_MASK ) == Event.SHIFT_MASK ) || ( ( mode & Event.ALT_MASK ) == Event.ALT_MASK ) ) { JPanel p= new JPanel(); Map vars= new HashMap(); ParametersFormPanel pfp= new org.autoplot.jythonsupport.ui.ParametersFormPanel(); Map env= new HashMap(); env.put("dom",interp.get("dom") ); env.put("PWD",interp.get("PWD") ); ParametersFormPanel.FormData fd= pfp.doVariables( env, panel.getEditorPanel().getText(), vars, p ); if ( fd.count>0 ) { JScrollPane pane= new JScrollPane(p); if ( AutoplotUtil.showConfirmDialog2( panel, pane, "edit parameters", JOptionPane.OK_CANCEL_OPTION )==JOptionPane.OK_OPTION ) { ParametersFormPanel.resetVariables( fd, vars ); String parseExcept= null; for ( Entry v: vars.entrySet() ) { try { fd.implement( interp, v.getKey(), v.getValue() ); } catch ( ParseException ex ) { parseExcept= v.getKey(); } } if ( parseExcept!=null ) { JOptionPane.showMessageDialog( panel, "ParseException in parameter "+parseExcept ); } else { interp.exec(JythonRefactory.fixImports(panel.getEditorPanel().getText())); } } } else { // no parameters interp.exec(JythonRefactory.fixImports(panel.getEditorPanel().getText())); } } else { boolean experiment= System.getProperty("jythonDebugger","false").equals("true"); if ( experiment ) { final DebuggerConsole dc= DebuggerConsole.getInstance(panel); dc.setInterp(interp); interp.setOut(getOutput(dc)); EditorAnnotationsSupport.setExpressionLookup( (final String expr) -> dc.setEval(expr) //return dc.getEval(); ); } String code= panel.getEditorPanel().getText(); if ( file!=null ) { char[] cc= code.toCharArray(); boolean warning= false; int ioffs=0; for ( char c: cc ) { ioffs++; if ( c>128 ) { int ic= (int)c; logger.log(Level.INFO, "non-ASCII character ({0}) at character offset {1}", new Object[] { ic, ioffs }); warning= true; } } if ( warning ) { System.err.println("code contains data that will not be represented properly!"); } //experiment with writing to a temporary file and executing it. if ( experiment ) { String fixedCode= JythonRefactory.fixImports( code ); if ( fixedCode.equals(code) ) { interp.execfile( file.getAbsolutePath() ); } else { File tempFile= new File( file.getAbsolutePath() + ".t" ); Files.copy( new ByteArrayInputStream( fixedCode.getBytes() ), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING ); interp.execfile( tempFile.getAbsolutePath() ); } } else { try (InputStream in = new ByteArrayInputStream( code.getBytes() )) { interp.execfile(JythonRefactory.fixImports(in,file.getName()),file.getName()); } } } else { interp.exec(JythonRefactory.fixImports(code)); } } setInterruptible( null ); if ( !mon.isFinished() ) mon.finished(); // bug1251: in case script didn't call finished applicationController.setStatus("done executing script"); } catch (IOException ex) { if ( !mon.isFinished() ) mon.finished(); logger.log(Level.WARNING, ex.getMessage(), ex); applicationController.setStatus("error: I/O exception: " + ex.toString()); } catch (PyException ex) { if ( !mon.isFinished() ) mon.finished(); annotateError(ex, offset, interp ); //logger.log(Level.WARNING, ex.getMessage(), ex ); applicationController.setStatus("error: " + ex.toString()); } } catch (IOException ex) { throw new RuntimeException(ex); } catch ( Error ex ) { if ( !ex.getMessage().contains("Python interrupt") ) { throw ex; } else { applicationController.setStatus("script interrupted"); } } finally { if ( !mon.isFinished() ) mon.finished(); // bug1251: in case script didn't call finished setInterruptible( null ); panel.setRunningScript(null); } } /** * Execute the script. For context data source, this means putting the URI in the data set selector and telling it to plot. * "mode" controls how the script is run, for example by bringing up the parameters GUI first, or by tracing execution. * The bit array is:
    *
  • 0 normal *
  • 1 parameters GUI (SHIFT is pressed) *
  • 2 trace (CRTL is pressed) *
  • 8 enter editor (ALT is pressed) *
* TODO: what's the difference between ALT and SHIFT? * @param mode =0 normal. =2=CTRL_MASK= trace. ALT_MASK is enter editor. SHIFT_MASK brings up parameters GUI. */ protected void executeScript(final int mode) { try { if (panel.getContext() == JythonScriptPanel.CONTEXT_DATA_SOURCE) { if (file != null) { URI uri; try { uri = new URI("vap+jyds:" + file.toURI().toString()); // bug 3055130 okay JythonDataSourceFactory factory = (JythonDataSourceFactory) DataSetURI.getDataSourceFactory( uri, new NullProgressMonitor()); if (factory != null) { factory.addExeceptionListener((Exception e) -> { if (e instanceof PyException) { PyException ex= (PyException)e; annotateError(ex, 0, null ); } }); } } catch (URISyntaxException ex) { throw new RuntimeException(ex); } } boolean updateSurl = false; if ( file == null || ( panel.isDirty() && FileSystemUtil.isChildOf( FileSystem.settings().getLocalCacheDir(), file ) ) ) { if (getSaveFile() == JFileChooser.APPROVE_OPTION) { updateSurl = true; } else { return; } } if (file != null) { try { if (!uriFilesEqual(selector.getValue(), file.toURI().toString())) { updateSurl = true; } } catch (URISyntaxException ex) { updateSurl = true; } if (panel.isDirty() && ( file.exists() && file.canWrite() || file.getParentFile().canWrite() ) ) { save(); } if (updateSurl) { selector.setValue("vap+jyds:"+file.toURI().toString()); } clearAnnotations(); if ( ( mode & KeyEvent.ALT_MASK ) == KeyEvent.ALT_MASK || ( mode & KeyEvent.SHIFT_MASK )== KeyEvent.SHIFT_MASK ) { selector.maybePlot(KeyEvent.ALT_MASK); //TODO: no editor panel! } else { selector.maybePlot(false); } if ( updateSurl ) { panel.setFilename(file.toString()); } } panel.setRunningScript(null); } else if (panel.getContext() == JythonScriptPanel.CONTEXT_APPLICATION) { applicationController.setStatus("busy: running application script"); Runnable run = () -> { executeScriptImmediately(mode); }; new Thread(run,"sessionRunScriptThread").start(); } } catch (IOException iOException) { model.getExceptionHandler().handle(iOException); panel.setRunningScript(null); } } protected class MyOutputStream extends FilterOutputStream { OutputStream sink; DebuggerConsole dc; public MyOutputStream(OutputStream out,DebuggerConsole dc) { super(out); this.sink= out; this.dc= dc; } StringBuilder currentLine= new StringBuilder(); /** * PDB has just returned from a routine, so don't close the debugging session. */ boolean returnFlag= false; /** * PDB has just returned from a routine, so don't close the debugging session. * @return */ public boolean getReturnFlag() { return this.returnFlag; } /** * standard mode output. */ private final Object STATE_OPEN="OPEN"; /** * collect the prompt so that it is not echoed. */ private final Object STATE_FORM_PDB_PROMPT="PROMPT"; /** * collect the prompt so that it is not echoed. */ private final Object STATE_RETURN_INIT_PROMPT="RETURN"; /** * pdb output encountered. */ private final Object STATE_PDB="PDB"; /** * pdb response */ protected final Object STATE_FORM_PDB_RESPONSE="RESPONSE"; Object state= STATE_OPEN; public void checkState(int b) throws IOException { try { Thread.sleep(10); } catch (InterruptedException ex) { Logger.getLogger(ScriptPanelSupport.class.getName()).log(Level.SEVERE, null, ex); } if ( dc!=null ) dc.print(String.valueOf((char)b),state); if ( state==STATE_OPEN ) { if ( b>=0 ) currentLine.append((char)b); if ( currentLine.length()==1 && currentLine.substring(0,1).equals("(") ) { state= STATE_FORM_PDB_PROMPT; } else if ( currentLine.length()==1 && currentLine.substring(0,1).equals(">") ) { state= STATE_FORM_PDB_RESPONSE; } else if ( currentLine.length()>10 && currentLine.substring(0,10).equals("--Return--") ) { state= STATE_RETURN_INIT_PROMPT; dc.started(); dc.next(); // jump out of pdb } else if ( currentLine.length()>0 && ( b==10 || b==13 ) ) { currentLine= new StringBuilder(); } } else if ( state==STATE_RETURN_INIT_PROMPT ) { if ( b>=0 ) currentLine.append((char)b); if ( currentLine.length()==1 && currentLine.substring(0,1).equals("(") ) { state= STATE_FORM_PDB_PROMPT; } else if ( currentLine.length()>0 && ( b==10 || b==13 ) ) { currentLine= new StringBuilder(); } } else if ( state==STATE_FORM_PDB_PROMPT ) { if ( b>=0 ) currentLine.append((char)b); if ( currentLine.length()>=5 ) { if ( currentLine.substring(0,5).equals("(Pdb)") ) { state= STATE_PDB; dc.started(); } else { sink.write(currentLine.toString().getBytes()); state= STATE_OPEN; } } else if ( currentLine.length()>0 && ( b==10 || b==13 ) ) { // newlines should clear the currentLine sink.write(currentLine.toString().getBytes()); currentLine= new StringBuilder(); state= STATE_OPEN; } } else if ( state==STATE_FORM_PDB_RESPONSE ) { if ( b>=0 && b!=10 && b!=13 ) currentLine.append((char)b); Pattern p= Pattern.compile("\\>? \\S+\\((\\d+)\\)\\S+\\(\\)"); Matcher m= p.matcher(currentLine); if ( m.matches() ) { String linenum= m.group(1); annotationsSupport.clearAnnotations(); int[] pos= annotationsSupport.getLinePosition(Integer.parseInt(linenum)); annotationsSupport.annotateChars( pos[0], pos[1], "programCounter", "pc", interruptible ); state= STATE_OPEN; currentLine= new StringBuilder(); } else { if ( b==13 || b==10 ) { state= STATE_OPEN; sink.write(currentLine.toString().getBytes()); currentLine= new StringBuilder(); } } } else if ( state==STATE_PDB ) { // the beginning of the currentLine is (Pdb) and we want a terminator Pattern p= Pattern.compile("\\(Pdb\\) (.*)> .*\\.jy\\((\\d+)\\).*\\(\\)\\s*"); // TODO: replace ).* with more precise value. Pattern p2= Pattern.compile("\\(Pdb\\) (.*)--Return--.*"); int l= currentLine.length(); if ( b>=0 && b!=10 && b!=13 ) currentLine.append((char)b); //TODO: why is my regex not working when newlines get in there? if ( l>2 && currentLine.substring(l-2,l).equals("()") ) { Matcher m= p.matcher(currentLine); if ( m.matches() ) { String linenum= m.group(2); annotationsSupport.clearAnnotations(); int[] pos= annotationsSupport.getLinePosition(Integer.parseInt(linenum)); annotationsSupport.annotateChars( pos[0], pos[1], "programCounter", "pc", interruptible ); String userOutput= m.group(1); if ( userOutput!=null && userOutput.length()>0 ) { sink.write(userOutput.getBytes()); sink.write("\n".getBytes()); } state= STATE_OPEN; currentLine= new StringBuilder(); } else { Matcher m2= p2.matcher(currentLine); if ( m2.matches() ) { annotationsSupport.clearAnnotations(); String userOutput= m2.group(1); if ( userOutput.length()>0 ) { sink.write(userOutput.getBytes()); sink.write("\n".getBytes()); } state= STATE_OPEN; currentLine= new StringBuilder(); dc.finished(); returnFlag= true; dc.next(); } else { state= STATE_OPEN; currentLine= new StringBuilder(); } } } } } @Override public void write(int b) throws IOException { this.checkState(b); } @Override public void write(byte[] b) throws IOException { for ( int i=0; i { Map recent= model.getRecent(filter,10*limit); final DefaultListModel mm= new DefaultListModel(); List ss= new ArrayList( recent.keySet() ); int count=0; for ( int i=ss.size()-1; i>=0; i-- ) { String s= ss.get(i); if ( s.startsWith("script:") ) s= s.substring(7); if ( s.startsWith("vap+jyds:") ) s= s.substring(9); if ( s.startsWith("vap+jy:") ) s= s.substring(7); int iscript= s.indexOf("script="); if ( iscript>-1 ) { s= s.substring(iscript+"script=".length()); } if ( s.startsWith("file:") ) { if ( s.startsWith("file://") ) s= s.substring(7); if ( s.startsWith("file:") ) s= s.substring(5); int iq= s.indexOf('?'); if ( iq>-1 ) { s= s.substring(0,iq); } if ( mm.contains(s) ) { continue; } mm.addElement(s); count++; if ( count==limit ) break; } } Runnable run1 = () -> { p.setModel( mm ); }; SwingUtilities.invokeLater(run1); } ; new Thread(run).start(); p.addListSelectionListener((ListSelectionEvent e) -> { if ( !e.getValueIsAdjusting() ){ if ( p.getSelectedValue().equals(msgWait) ) { } else { String s= (String)p.getSelectedValue(); File ff= new File(s); //TODO: still can't figure out mac bug where select doesn't work c.setSelectedFile( ff ); } } }); JScrollPane scrollPane= new JScrollPane(p); //p.setPreferredSize( new Dimension(300,300) ); //p.setMinimumSize( new Dimension(300,300) ); scrollPane.setPreferredSize( new Dimension(300,200) ); scrollPane.setMinimumSize( new Dimension(300,100) ); scrollPane.setMaximumSize( new Dimension(300,200) ); recentPanel.add( new JLabel("Recently used local ("+filter+") files:"), BorderLayout.NORTH ); recentPanel.add( scrollPane, BorderLayout.CENTER ); return recentPanel; } /** * mark the changes git indicates. * @param support * @param fln * @return the number of changes identified. * @throws IOException * @throws InterruptedException */ public static int markChanges( EditorAnnotationsSupport support, File fln ) throws IOException, InterruptedException { GitCommand.GitResponse diffOutput = new GitCommand(fln.getParentFile()).diff(fln); Patch deltas = UnifiedDiffUtils.parseUnifiedDiff( Arrays.asList( diffOutput.getResponse().split("\n") ) ); support.clearAnnotations(); List> dd= deltas.getDeltas(); dd.forEach((d) -> { Chunk source = d.getSource(); List ll = source.getChangePosition(); List ss = d.getTarget().getChangePosition(); int[] lp0,lp1; String sourceText = String.join( "\n", d.getSource().getLines() ); if ( d instanceof ChangeDelta ) { for ( int i : ss ) { lp0 = support.getLinePosition(i); lp1 = support.getLinePosition(i); System.err.println( String.format("change characters %d through %d", lp0[0], lp1[1] ) ); support.annotateChars(lp0[0],lp1[1],EditorAnnotationsSupport.ANNO_CHANGE,sourceText,null); } } else if ( d instanceof DeleteDelta ) { for ( int i : ll ) { lp0 = support.getLinePosition(i); lp1 = support.getLinePosition(i); support.annotateChars(lp0[0],lp1[1],EditorAnnotationsSupport.ANNO_DELETE,sourceText,null); } } else if ( d instanceof InsertDelta ) { for ( int i : ss ) { lp0 = support.getLinePosition(i); lp1 = support.getLinePosition(i); System.err.println(String.format("insert characters %d through %d", lp0[0], lp1[1] ) ); support.annotateChars(lp0[0],lp1[1],EditorAnnotationsSupport.ANNO_INSERT,sourceText,null); } } }); return dd.size(); } protected void open() { try { if (this.file == null) { String sfile = selector.getValue(); URISplit split = null; if (sfile != null) { split = URISplit.parse(sfile); } if (split == null || !( split.file!=null && ( split.file.endsWith(".py") || split.file.endsWith(".jy") ) ) ) { file = null; } else { try { file = DataSetURI.getFile(DataSetURI.getURL(sfile), new NullProgressMonitor()); } catch ( IOException | IllegalArgumentException ex ) { logger.fine("old file reference from data set selector is ignored"); file= null; } } } Preferences prefs = AutoplotSettings.getPreferences(ScriptPanelSupport.class); String openFile = prefs.get(PREFERENCE_OPEN_FILE, ""); JFileChooser chooser = new JFileChooser(); chooser.setFileSelectionMode( JFileChooser.FILES_ONLY ); chooser.setFileFilter(new FileNameExtensionFilter( "python and jython scripts", new String [] { "jy", "py", "jyds" } )); if (file != null) { chooser.setSelectedFile(file); } if (openFile.length() > 0) { // this hangs on Java 1.7.0_17 Linux Centos 6: chooser.getFileSystemView().isParent(chooser.getCurrentDirectory(),new File(openFile)); File fopenFile= new File(openFile); chooser.setSelectedFile(fopenFile); // I have to hope that it was an NFS problem we were having. I don't see the problem on Ubuntu or Macs. } chooser.setAccessory( getRecentAccessory("*.jy*", RECENT_FILES_COUNT,chooser) ); chooser.revalidate(); int r = chooser.showOpenDialog(panel); if (r == JFileChooser.APPROVE_OPTION) { if ( panel.isDirty() ) { int option= JOptionPane.showConfirmDialog( panel, "Save edits first?", "Save Current Editor", JOptionPane.YES_NO_CANCEL_OPTION ); if ( option==JOptionPane.YES_OPTION ) { saveAs(); } else if ( option==JOptionPane.CANCEL_OPTION ) { return; } } file = chooser.getSelectedFile(); prefs.put(PREFERENCE_OPEN_FILE, file.toString()); loadFile(file); panel.setFilename(file.toString()); annotationsSupport.clearAnnotations(); } } catch (IOException ex) { model.getExceptionHandler().handle(ex); } } /** * interrupt the running script. */ void interrupt() { InteractiveInterpreter interp= getInterruptible(); if ( interp!=null ) { interp.interrupt( ts ); } } public static final String PROP_INTERRUPTABLE= "interruptable"; /** * return the interruptible InteractiveInterpreter that runs the script. * @return the interruptible */ public InteractiveInterpreter getInterruptible() { return interruptible; } private void setInterruptible( InteractiveInterpreter interruptable ) { InteractiveInterpreter old= this.interruptible; this.interruptible= interruptable; propertyChangeSupport.firePropertyChange( PROP_INTERRUPTABLE, old, interruptable ); } private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(listener); } public void addPropertyChangeListener(String name, PropertyChangeListener listener) { propertyChangeSupport.addPropertyChangeListener(name, listener); } public void removePropertyChangeListener(String name,PropertyChangeListener listener) { propertyChangeSupport.removePropertyChangeListener(name,listener); } }