diff --git a/README.md b/README.md index b536249..a49be85 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +**Update**: This repo is no longer active. PDE X codebase was merged into the [main processing repo](https://github.com/processing/processing/) starting with the 3.0 alpha releases (July 2014), further development continues in the main repo. So please report any issues in the main Processing repo. + PDE X ===== diff --git a/Todo, GSoC 2013.txt b/Todo, GSoC 2013.txt index d2e5777..c7020fc 100644 --- a/Todo, GSoC 2013.txt +++ b/Todo, GSoC 2013.txt @@ -4,7 +4,7 @@ This would also be a break down of my thought process and ideas as I tackle vari Manindra Moharana (me@mkmoharana.com) -* : Todo, x : Done, ? : Undecided Todo, ! : Critical, + : Minor Todo +[ ]: Todo, [x] : Done, ? : Undecided Todo, ! : Critical, + : Minor Todo Code Completion =============== @@ -16,122 +16,122 @@ The big stuff: - Many of the cases seem to have been covered, and I'm achieving more and more code unification as I'm working through the problem step by step - Looks almost complete now, nearly all cases covered(July 13th) x After popup appears, the popup location is fixed for the current line. So if editor window is moved while staying in the same line, popup appears at the prev location. Need to ensure editor is still at last know location. Fixed. -* Keyboard Shortcut for completion popup - Ctrl + Space -* Scope handling? Static/non static scope? -* Disable completions on comment line -* Trie implementation would be lower priority, "premature optimisation is pure evil". Get all features of CC working good enough and then plan this. +[ ]Keyboard Shortcut for completion popup - Ctrl + Space +[ ]Scope handling? Static/non static scope? +[ ]Disable completions on comment line +[ ]Trie implementation would be lower priority, "premature optimisation is pure evil". Get all features of CC working good enough and then plan this. -x Ensure that a compilation unit is created at startup! +[x]Ensure that a compilation unit is created at startup! x! Code competition for local code is working with recursive look up. -x Completion doesn't seem to show up for fields of a type defined locally. But works for methods with return type defined locally. Take ideas. Some case missing most probably. Fixed -x Discovered another major issue due to offset differences -> While looking for predictions, if the parsed string contains pde enhancements, predictions FAIL! Zomg. +[x]Completion doesn't seem to show up for fields of a type defined locally. But works for methods with return type defined locally. Take ideas. Some case missing most probably. Fixed +[x]Discovered another major issue due to offset differences -> While looking for predictions, if the parsed string contains pde enhancements, predictions FAIL! Zomg. Ex - "s.substring(int(13.4))." fails. Thinking to just do the substitutions before sending it to updatePredictions(), coz offsets aren't really a concern here, right? Yup, fixed it! x! Code completion with library code, non-nested seems to be broken, fix it. Fixed. -x Completion for external classes - ArrayList, HashMap, etc. +[x]Completion for external classes - ArrayList, HashMap, etc. x! Recursive lookup for compiled(library) code! x! Library CC for nested would be tricky. Need to jump from local->compiled code while searching recursively. Recursive find's current implementation is based on ASTNode return type. Afaik, no way to instantiate orphaned ASTNode objects(or did I miss it?). ASTNode objects have to be created only from the main ast instance. But I need to find a way to switch to compiled instances from local class instance. x! Should I implement wrapper for ASTNode? - possibly needed for code completion with compiled and non-compiled code. Done. -x Differentiating between multiple statements on the same line. How to? Done with offset handling. -x - Cache predictions if current 'word' is increasing in length. If already showing predictions beginning with 's', for 'sa', remove extra completions, rather than recalculating predictions. Performance increase. -x Parameterized type support is broken. -x Array types, all all other types support broken. :\ -x Completion for array access, strings[0]. +[x]Differentiating between multiple statements on the same line. How to? Done with offset handling. +[x]- Cache predictions if current 'word' is increasing in length. If already showing predictions beginning with 's', for 'sa', remove extra completions, rather than recalculating predictions. Performance increase. +[x]Parameterized type support is broken. +[x]Array types, all all other types support broken. :\ +[x]Completion for array access, strings[0]. Finer details -* findDeclarations should support 3rd party classes too. It's about time. ;) -* printStuff(int,float,String) - completion endings have to be appropriate. Right now it's just printStuff(,,). Cursor positioning also needs to be taken care of(done). Argument list as tooltip if possible? +[ ]findDeclarations should support 3rd party classes too. It's about time. ;) +[ ]printStuff(int,float,String) - completion endings have to be appropriate. Right now it's just printStuff(,,). Cursor positioning also needs to be taken care of(done). Argument list as tooltip if possible? *! p5 enhanced stuff in java, how does it fit in with everything else, and edge cases. Possibly add support for them. Offset handling improvements should help here. -* Diamond operator isn't supported for now. Bummer. +[ ]Diamond operator isn't supported for now. Bummer. -x Completion popup height is now dynamic, decreases to fit. -* Completion width can be dynamic, if really needed.. -x Icons for completions? Or overkill right now? -x 'Show Usage' menu item added -x Show declaring class for completions +[x]Completion popup height is now dynamic, decreases to fit. +[ ]Completion width can be dynamic, if really needed.. +[x]Icons for completions? Or overkill right now? +[x]'Show Usage' menu item added +[x]Show declaring class for completions x! Ignore String case while finding completion candidates -x Multiple 3rd party classes found in various packages. Not a chance no more. -x Obj a1; a1.-> completion doesn't work before it is instantiated. Look into that. Began working again by itself. Yay! -x Cursor positioning should be after the first ( if arguments present, else after () -x Display the type of Completion(method return type, variable type) in the popup. +[x]Multiple 3rd party classes found in various packages. Not a chance no more. +[x]Obj a1; a1.-> completion doesn't work before it is instantiated. Look into that. Began working again by itself. Yay! +[x]Cursor positioning should be after the first ( if arguments present, else after () +[x]Display the type of Completion(method return type, variable type) in the popup. - facing some issues for local types. Fixed. -x Sorted list of completion candidates - fields, then methods. It's unsorted presently. -x Reflection API - getMethods vs getDeclaredMethods. declared. -x Need to add offset correction to ASTGenerator and its lookup methods. Or leave it for later? All set to implement -x Completion List should get hidden on hitting esc key +[x]Sorted list of completion candidates - fields, then methods. It's unsorted presently. +[x]Reflection API - getMethods vs getDeclaredMethods. declared. +[x]Need to add offset correction to ASTGenerator and its lookup methods. Or leave it for later? All set to implement +[x]Completion List should get hidden on hitting esc key Offset Mapping ============== First major hurdle is offset mapping *! pde<->java code offset : precise conversion needed -* W.r.t PDE specific enhancements, things are almost working. There are some offset issues when multiple pde statements are in the same line, but I guess it's good enough for now to proceed ahead. Will keep a close watch for potential bugs. -x for the above, I've decide to first implement a sketch outline like feature, which would highlight an AST element precisely in the pde code. This would ensure I've got the mapping working properly. And may lead to a future feature. -x This is precise upto a certain line. Once on a line, pde stuff have to be taken into consideration. -x Edge case - multiple statements in a single line -x PDE specific enhancements will also have to be tackled like int(), # literals. The length of the node returned needs to be modified to make up for extra chars added like PApplet.parseFloat, etc. Also the 2nd or futher pde enhancements in the same line means even the beginning offset would need adjustment. Meh. +[ ]W.r.t PDE specific enhancements, things are almost working. There are some offset issues when multiple pde statements are in the same line, but I guess it's good enough for now to proceed ahead. Will keep a close watch for potential bugs. +[x]for the above, I've decide to first implement a sketch outline like feature, which would highlight an AST element precisely in the pde code. This would ensure I've got the mapping working properly. And may lead to a future feature. +[x]This is precise upto a certain line. Once on a line, pde stuff have to be taken into consideration. +[x]Edge case - multiple statements in a single line +[x]PDE specific enhancements will also have to be tackled like int(), # literals. The length of the node returned needs to be modified to make up for extra chars added like PApplet.parseFloat, etc. Also the 2nd or futher pde enhancements in the same line means even the beginning offset would need adjustment. Meh. Refactoring =========== -* Undo misbehaves here, handle carefully. -* Fails to rename the first defined global variable, if a javadoc comment precedes it. But owrds for single/multiline comments. Wth! -x New Name is validated. -x Ordered list in 'Show Usage' window -x Add support for word select on right click and rename, mouse co-ordinates need to obtained carefully +[ ]Undo misbehaves here, handle carefully. +[ ]Fails to rename the first defined global variable, if a javadoc comment precedes it. But owrds for single/multiline comments. Wth! +[x]New Name is validated. +[x]Ordered list in 'Show Usage' window +[x]Add support for word select on right click and rename, mouse co-ordinates need to obtained carefully Refactoring would work only when code is compiler error free. I plan to do a find replace type op on the compile ready code. 1. First identify the declaration of the variable in the AST. We'll then make a list of all its occurrences. 2. DFS through the AST, for each (SimpleName)instance of the word in code, find if the matched word is the same one whose declaration we found. -x Edge Case: For renaming a TypeDeclaration, the declaration of SimpleName instance of the TD and it's constructor(s) aren't added to the list generated by DFS. So for renaming TD, will have to manually add the TD SimpleName and it's constructors' SimpleNames to the a list of declaration nodes that can be positively matched against. -x Renaming any constructor is equivalent to renaming the TD +[x]Edge Case: For renaming a TypeDeclaration, the declaration of SimpleName instance of the TD and it's constructor(s) aren't added to the list generated by DFS. So for renaming TD, will have to manually add the TD SimpleName and it's constructors' SimpleNames to the a list of declaration nodes that can be positively matched against. +[x]Renaming any constructor is equivalent to renaming the TD 3. Find corresponding PDE offsets of the SimpleNames, rename in each line. -x Edge Case: Need to take displaced offsets on a line, due to pde enhancements, into consideration. +[x]Edge Case: Need to take displaced offsets on a line, due to pde enhancements, into consideration. 4. All the changes in code would be made in a separate copy of the code(?). After all the renaming is done, allow it only if the new code compiles. Basically an undo should be possible in case of conflicts. -x Refactoring ui -x For now, user needs to highlight the name of the var, and then right-click -> Rename.. -x Handle saving. If sketch closed after renaming w/o saving find bugs. Done, marking the sketch as modified after renaming. +[x]Refactoring ui +[x]For now, user needs to highlight the name of the var, and then right-click -> Rename.. +[x]Handle saving. If sketch closed after renaming w/o saving find bugs. Done, marking the sketch as modified after renaming. Quick Navigation ================ *+ A silly bug where the name of the first field declaration isn't highlighted correctly. Seems to be happening if there's a javadoc or multiline comment near about the top. -x On OS X, Ctrl + Click is right mouse click, so implement Cmd + Click instead. isMetaDown()? -x Ctrl + Click on an element to scroll to its definition in code -x Local Vars -x Local Methods -x Local Classes -x Recursive lookup, a.b().c() -x Now highlihgting the declaration name, rather than the whole declaration. +[x]On OS X, Ctrl + Click is right mouse click, so implement Cmd + Click instead. isMetaDown()? +[x]Ctrl + Click on an element to scroll to its definition in code +[x]Local Vars +[x]Local Methods +[x]Local Classes +[x]Recursive lookup, a.b().c() +[x]Now highlihgting the declaration name, rather than the whole declaration. Sketch Outline ============== -x Show Sketch Outline Tree -x Filter stuff in text field -x Add icons - custom cell renderer +[x]Show Sketch Outline Tree +[x]Filter stuff in text field +[x]Add icons - custom cell renderer Suggestion for missing imports ============================== -* Find a more subtle way to suggest for imports. The current method is too troublesome. Randomly pops up offering suggestions. May intimidate beginners. +[ ]Find a more subtle way to suggest for imports. The current method is too troublesome. Randomly pops up offering suggestions. May intimidate beginners. 1. In compileCheck() in ECS, check if error message is of the type "__" cannot be resolved to a type. 2. Find the class name via astGen, and suggest import as a popup. -x Barebones functionality done. -x Add imports only to beginning of first tab. -x Search within contributed libraries folder -x Hide suggestion list before showing import suggestions -x Search within code folder of sketch +[x]Barebones functionality done. +[x]Add imports only to beginning of first tab. +[x]Search within contributed libraries folder +[x]Hide suggestion list before showing import suggestions +[x]Search within code folder of sketch Labels for Java elements ======================== -x Working for local code -* Need to modify getASTNodeAt to also fetch the type for predefined classes. -* Labels for predefined class objects -* Chaining support for labels +[x]Working for local code +[ ]Need to modify getASTNodeAt to also fetch the type for predefined classes. +[ ]Labels for predefined class objects +[ ]Chaining support for labels Synchronization =============== @@ -140,31 +140,31 @@ Gotta do it carefully between main thread, ECS Thread, and SwingWorker threads Fields that are concurrently accessed: ECS members: -x ArrayList problems - updated in ECS, accessed by ErrorBar.update() -x ArrayList classpathJars - updated in ECS, accessed by ASTGenerator.loadJars() -x hasErrors, syntaxErrors - Atomic Boolean -x boolean warningsEnabled - made it volatile -* CompilationUnit cu - updated in ECS, accessed a zillion times in ASTGenerator :'( +[x]ArrayList problems - updated in ECS, accessed by ErrorBar.update() +[x]ArrayList classpathJars - updated in ECS, accessed by ASTGenerator.loadJars() +[x]hasErrors, syntaxErrors - Atomic Boolean +[x]boolean warningsEnabled - made it volatile +[ ]CompilationUnit cu - updated in ECS, accessed a zillion times in ASTGenerator :'( General Stuff ============= -* [Critical] PermGen out of memory bug. Manually triggering GC after making the classloader null ensures permgen memory is reclaimed on editor exit. Max open window still limited by max permgen size. Also, added a classloadcounter in ECS to trigger GC periodically. +[ ][Critical] PermGen out of memory bug. Manually triggering GC after making the classloader null ensures permgen memory is reclaimed on editor exit. Max open window still limited by max permgen size. Also, added a classloadcounter in ECS to trigger GC periodically. https://github.com/processing/processing-experimental/issues/1 See: http://stackoverflow.com/questions/2095974/how-to-unload-a-already-loaded-class-in-java I'm making the classLoader null, but what about the classes loaded by ASTGen? Investigate. -x Disabling Error Checking disables predictions as well! Fixed. -x Added doc listener for text area updates -x Consult Ben on where to save preferences - main preferences.txt or custom one. - Main prefs file -x Save preferences to main preference.txt -x Hide breakpoint markers when Debugger isn't active -x Ensure gutter mouse handler is taken care of when hiding Debugger breakpoint bar. -x Ensure all editor windows are closed when editor is closed. -x Add a red marker near Errors label in console toggle, to indicate errors present in sketch. -x Add option for toggling debug output -x On Run/Debug Console is visible(ProblemsList hidden) -* Update wiki for Ctrl + H instead of Ctrl + J shortcuts -x update build.xml to produce dists -x Make this a contributed mode - mode.txt, github releases feature, version numbering, git tags, etc -x Add GitHub link to PDE X Menu +[x]Disabling Error Checking disables predictions as well! Fixed. +[x]Added doc listener for text area updates +[x]Consult Ben on where to save preferences - main preferences.txt or custom one. - Main prefs file +[x]Save preferences to main preference.txt +[x]Hide breakpoint markers when Debugger isn't active +[x]Ensure gutter mouse handler is taken care of when hiding Debugger breakpoint bar. +[x]Ensure all editor windows are closed when editor is closed. +[x]Add a red marker near Errors label in console toggle, to indicate errors present in sketch. +[x]Add option for toggling debug output +[x]On Run/Debug Console is visible(ProblemsList hidden) +[ ]Update wiki for Ctrl + H instead of Ctrl + J shortcuts +[x]update build.xml to produce dists +[x]Make this a contributed mode - mode.txt, github releases feature, version numbering, git tags, etc +[x]Add GitHub link to PDE X Menu diff --git a/application/Info.plist.tmpl b/application/Info.plist.tmpl new file mode 100644 index 0000000..28a2e02 --- /dev/null +++ b/application/Info.plist.tmpl @@ -0,0 +1,74 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + @@sketch@@ + CFBundleIconFile + sketch.icns + CFBundleIdentifier + @@sketch@@ + CFBundleDisplayName + @@sketch@@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + @@sketch@@ + CFBundlePackageType + APPL + + + CFBundleShortVersionString + 1 + CFBundleVersion + 1 + CFBundleSignature + ???? + NSHumanReadableCopyright + Your copyright here + CFBundleGetInfoString + Created with Processing + + + @@jvm_runtime@@ + + JVMMainClassName + @@sketch@@ + + LSMinimumSystemVersion + 10.7.3 + + NSHighResolutionCapable + + + LSArchitecturePriority + + x86_64 + + + LSEnvironment + + LC_CTYPE + UTF-8 + + + LSUIPresentationMode + @@lsuipresentationmode@@ + + JVMOptions + + @@jvm_options_list@@ + -Xdock:icon=Contents/Resources/sketch.icns + -Dapple.laf.useScreenMenuBar=true + -Dcom.apple.macos.use-file-dialog-packages=true + -Dcom.apple.macos.useScreenMenuBar=true + -Dcom.apple.mrj.application.apple.menu.about.name=@@sketch@@ + -Dcom.apple.smallTabs=true + + JVMArguments + + + + diff --git a/application/sketch.icns b/application/sketch.icns new file mode 100644 index 0000000..2bdb4df Binary files /dev/null and b/application/sketch.icns differ diff --git a/application/template.app/Contents/MacOS/JavaApplicationStub b/application/template.app/Contents/MacOS/JavaApplicationStub old mode 100644 new mode 100755 diff --git a/application/template.app/Contents/PkgInfo b/application/template.app/Contents/PkgInfo old mode 100644 new mode 100755 diff --git a/application/template.exe b/application/template.exe old mode 100644 new mode 100755 diff --git a/application/template.plist b/application/template.plist old mode 100644 new mode 100755 diff --git a/build.properties b/build.properties index 4dfc002..ed26d76 100644 --- a/build.properties +++ b/build.properties @@ -1,10 +1,10 @@ -sketchbook.location= -classpath.local.location= -core.library.location= -app.library.location= -java.target.version=1.6 +sketchbook.location=${user.home}/Documents/Processing +classpath.local.location=${user.home}/Documents/workspace/libs +core.library.location=/home/quarkninja/Workspaces/processing-workspace/processing/app/core/library +app.library.location=/home/quarkninja/Workspaces/processing-workspace/processing/app/ +java.target.version=1.7 lib.name=ExperimentalMode prettyName=PDE X dist=dist -release=3 -prettyVersion=1.0.0b +release=7 +prettyVersion=1.0.4b diff --git a/pdeX.txt b/pdeX.txt index 803ecd0..7c2a3aa 100644 --- a/pdeX.txt +++ b/pdeX.txt @@ -3,5 +3,5 @@ authorList=[The Processing Foundation](http://processing.org) url=https://github.com/processing/processing-experimental sentence=The next generation of PDE paragraph=Intelligent Code Completion, Live Error Checker, Debugger, Auto Refactor, etc. -version=5 -prettyVersion=1.0.2b +version=7 +prettyVersion=1.0.4b diff --git a/revisions.txt b/revisions.txt index 5581f04..647859a 100644 --- a/revisions.txt +++ b/revisions.txt @@ -1,4 +1,53 @@ +PDE X v1.0.4b - May 9, 2014 + +Requires Processing 2.1.2 or above. + +Bug fixes + ++ Disabled auto-save. My sincere apologies to those who lost data due +to this bug. It was wrong of me to release an untested feature without +adding an option to enable/disable it. I've learnt a lesson and I shall +ensure this sort of thing doesn't happen again in the future. + ++ Autocompletion bug, column is sometimes off by 1 +https://github.com/processing/processing-experimental/issues/38 + ++ Persistent completion dialog on OS X +https://github.com/processing/processing-experimental/issues/32 + ++ Status bar update bug +https://github.com/processing/processing-experimental/issues/29 + ++ Export application broken +https://github.com/processing/processing-experimental/issues/45 + ++ Status Bar - New Tab prompt bug +https://github.com/processing/processing-experimental/issues/53 + ++ Show usage fails for methods which have javadoc comment +https://github.com/processing/processing-experimental/issues/51 + +. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + +PDE X v1.0.3b - January 21, 2014 + +New Feature + ++ PDE X now saves a backup of your sketch every 3 minutes(configurable in preferences.txt). +In case of an unexpected crash, this should save the day! +https://github.com/processing/processing-experimental/issues/36 + +Bug fixes + ++ Outline Window width is now fixed +https://github.com/processing/processing-experimental/issues/31 + ++ Export Application works again on OS X +https://github.com/processing/processing-experimental/issues/33 + +. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + PDE X v1.0.2b - October 21, 2013 Bug fixes diff --git a/src/processing/mode/experimental/ASTGenerator.java b/src/processing/mode/experimental/ASTGenerator.java index 00ea002..d254db7 100644 --- a/src/processing/mode/experimental/ASTGenerator.java +++ b/src/processing/mode/experimental/ASTGenerator.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Stack; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import javax.swing.BorderFactory; @@ -52,6 +53,8 @@ import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.text.BadLocationException; +import javax.swing.text.Element; +import javax.swing.text.PlainDocument; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.MutableTreeNode; @@ -109,7 +112,7 @@ public class ASTGenerator { /** * AST Window */ - protected JFrame frame2; + protected JFrame frmASTView; protected JFrame frameAutoComp; @@ -150,17 +153,19 @@ public ASTGenerator(ErrorCheckerService ecs) { //addCompletionPopupListner(); addListeners(); //loadJavaDoc(); + predictionOngoing = new AtomicBoolean(false); } protected void setupGUI(){ - frame2 = new JFrame(); + frmASTView = new JFrame(); jtree = new JTree(); - frame2.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); - frame2.setBounds(new Rectangle(680, 100, 460, 620)); + frmASTView.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + frmASTView.setBounds(new Rectangle(680, 100, 460, 620)); + frmASTView.setTitle("AST View - " + editor.getSketch().getName()); JScrollPane sp = new JScrollPane(); sp.setViewportView(jtree); - frame2.add(sp); + frmASTView.add(sp); btnRename = new JButton("Rename"); btnListOccurrence = new JButton("Show Usage"); @@ -231,6 +236,11 @@ protected void setupGUI(){ } + + /** + * Toggle AST View window + */ + public static final boolean SHOWAST = true; protected DefaultMutableTreeNode buildAST(String source, CompilationUnit cu) { if (cu == null) { @@ -246,14 +256,14 @@ protected DefaultMutableTreeNode buildAST(String source, CompilationUnit cu) { compilationUnit = (CompilationUnit) parser.createAST(null); } else { compilationUnit = cu; - log("Other cu"); + //log("Other cu"); } // OutlineVisitor visitor = new OutlineVisitor(); // compilationUnit.accept(visitor); getCodeComments(); codeTree = new DefaultMutableTreeNode(new ASTNodeWrapper((ASTNode) compilationUnit .types().get(0))); - log("Total CU " + compilationUnit.types().size()); + //log("Total CU " + compilationUnit.types().size()); if(compilationUnit.types() == null || compilationUnit.types().isEmpty()){ logE("No CU found!"); } @@ -267,14 +277,16 @@ protected Object doInBackground() throws Exception { protected void done() { if (codeTree != null) { -// if (jtree.hasFocus() || frame2.hasFocus()) -// return; -// jtree.setModel(new DefaultTreeModel(codeTree)); -// ((DefaultTreeModel) jtree.getModel()).reload(); -// jtree.validate(); -// if (!frame2.isVisible()) { -// frame2.setVisible(true); -// } + if(SHOWAST){ + if (jtree.hasFocus() || frmASTView.hasFocus()) + return; + jtree.setModel(new DefaultTreeModel(codeTree)); + ((DefaultTreeModel) jtree.getModel()).reload(); + jtree.validate(); + if (!frmASTView.isVisible()) { + frmASTView.setVisible(true); + } + } // if (!frameAutoComp.isVisible()) { // // frameAutoComp.setVisible(true); @@ -561,6 +573,9 @@ public ClassMember resolveExpression3rdParty(ASTNode nearestNode, case ASTNode.FIELD_ACCESS: FieldAccess fa = (FieldAccess) astNode; if (fa.getExpression() == null) { + + // TODO: Check for existence of 'new' keyword. Could be a ClassInstanceCreation + // Local code or belongs to super class log("FA,Not implemented."); return null; @@ -602,6 +617,7 @@ public ClassMember resolveExpression3rdParty(ASTNode nearestNode, return new ClassMember(extracTypeInfo(temp)); } if (mi.getExpression() == null) { +// if() //Local code or belongs to super class log("MI,Not implemented."); return null; @@ -775,15 +791,31 @@ protected void trimCandidates(String newWord){ //protected AtomicBoolean predictionsEnabled; protected int predictionMinLength = 2; + + private AtomicBoolean predictionOngoing; + + /** + * The main function that calculates possible code completion candidates + * + * @param word + * @param line + * @param lineStartNonWSOffset + */ public void preparePredictions(final String word, final int line, final int lineStartNonWSOffset) { + if(predictionOngoing.get()) return; + if(!ExperimentalMode.codeCompletionsEnabled) return; - if(word.length() < predictionMinLength) return; + if(word.length() < predictionMinLength) return; + + predictionOngoing.set(true); // This method is called from TextArea.fetchPhrase, which is called via a SwingWorker instance // in TextArea.processKeyEvent if(caretWithinLineComment()){ log("No predictions."); + predictionOngoing.set(false); return; } + // SwingWorker worker = new SwingWorker() { // // @Override @@ -817,6 +849,7 @@ public void preparePredictions(final String word, final int line, final int line } showPredictions(word); lastPredictedWord = word2; + predictionOngoing.set(false); return; } } @@ -859,12 +892,19 @@ public void preparePredictions(final String word, final int line, final int line ASTNode testnode = parser.createAST(null); //logE("PREDICTION PARSER PROBLEMS: " + parser); // Find closest ASTNode of the document to this word - logE("Typed: " + word2 + "|"); - nearestNode = findClosestNode(lineNumber, (ASTNode) compilationUnit.types() + logE("Typed: " + word2 + "|" + " temp Node type: " + testnode.getClass().getSimpleName()); + if(testnode instanceof MethodInvocation){ + MethodInvocation mi = (MethodInvocation)testnode; + System.out.println(mi.getName() + "," + mi.getExpression() + "," + mi.typeArguments().size()); + } + + // find nearest ASTNode + nearestNode = findClosestNode(lineNumber, (ASTNode) errorCheckerService.getLastCorrectCU().types() .get(0)); - if (nearestNode == null) - //Make sure nearestNode is not NULL if couldn't find a closeset node - nearestNode = (ASTNode) compilationUnit.types().get(0); + if (nearestNode == null) { + // Make sure nearestNode is not NULL if couldn't find a closeset node + nearestNode = (ASTNode) errorCheckerService.getLastCorrectCU().types().get(0); + } logE(lineNumber + " Nearest ASTNode to PRED " + getNodeAsString(nearestNode)); @@ -993,7 +1033,7 @@ public void preparePredictions(final String word, final int line, final int line } showPredictions(word); - + predictionOngoing.set(false); // } // }; // @@ -1149,6 +1189,67 @@ public ArrayList getMembersForType(ClassMember tehClass, return candidates; } + public String getPDESourceCodeLine(int javaLineNumber) { + int res[] = errorCheckerService + .calculateTabIndexAndLineNumber(javaLineNumber); + if (res != null) { + return errorCheckerService.getPDECodeAtLine(res[0], res[1]); + } + return null; + } + + /** + * Returns the java source code line at the given line number + * @param javaLineNumber + * @return + */ + public String getJavaSourceCodeLine(int javaLineNumber) { + try { + PlainDocument javaSource = new PlainDocument(); + javaSource.insertString(0, errorCheckerService.sourceCode, null); + Element lineElement = javaSource.getDefaultRootElement() + .getElement(javaLineNumber - 1); + if (lineElement == null) { + log("Couldn't fetch jlinenum " + javaLineNumber); + return null; + } + String javaLine = javaSource.getText(lineElement.getStartOffset(), + lineElement.getEndOffset() + - lineElement.getStartOffset()); + return javaLine; + } catch (BadLocationException e) { + logE(e + " in getJavaSourceCodeline() for jinenum: " + javaLineNumber); + } + return null; + } + + /** + * Returns the java source code line Element at the given line number. + * The Element object stores the offset data, but not the actual line + * of code. + * @param javaLineNumber + * @return + */ + public Element getJavaSourceCodeElement(int javaLineNumber) { + try { + PlainDocument javaSource = new PlainDocument(); + javaSource.insertString(0, errorCheckerService.sourceCode, null); + Element lineElement = javaSource.getDefaultRootElement() + .getElement(javaLineNumber - 1); + if (lineElement == null) { + log("Couldn't fetch jlinenum " + javaLineNumber); + return null; + } +// String javaLine = javaSource.getText(lineElement.getStartOffset(), +// lineElement.getEndOffset() +// - lineElement.getStartOffset()); + return lineElement; + } catch (BadLocationException e) { + logE(e + " in getJavaSourceCodeline() for jinenum: " + javaLineNumber); + } + return null; + } + /** * Searches for the particular class in the default list of imports as well as * the Sketch classpath @@ -1505,7 +1606,7 @@ public void scrollToDeclaration(int lineNumber, String name, int offset) { /** - * + * Given a word(identifier) in pde code, finds its location in the ASTNode * @param lineNumber * @param name * @param offset - line start nonwhitespace offset @@ -1515,7 +1616,10 @@ public void scrollToDeclaration(int lineNumber, String name, int offset) { public ASTNodeWrapper getASTNodeAt(int lineNumber, String name, int offset, boolean scrollOnly) { - log("----getASTNodeAt----"); + // Convert tab based pde line number to actual line number + int pdeLineNumber = lineNumber + errorCheckerService.mainClassOffset; + log("----getASTNodeAt---- CU State: " + + errorCheckerService.compilationUnitState); if (errorCheckerService != null) { editor = errorCheckerService.getEditor(); int codeIndex = editor.getSketch().getCodeIndex(editor.getCurrentTab()); @@ -1523,118 +1627,118 @@ public ASTNodeWrapper getASTNodeAt(int lineNumber, String name, int offset, for (int i = 0; i < codeIndex; i++) { SketchCode sc = editor.getSketch().getCode(i); int len = Base.countLines(sc.getProgram()) + 1; - lineNumber += len; + pdeLineNumber += len; } } } - log("FLON: " + lineNumber); - ASTNode lineNode = findLineOfNode(compilationUnit, lineNumber, offset, name); - - log("+> " + lineNode); + + // Find closest ASTNode to the linenumber + log("getASTNodeAt: Node line number " + pdeLineNumber); + ASTNode lineNode = findLineOfNode(compilationUnit, pdeLineNumber, offset, + name); + + log("Node text +> " + lineNode); ASTNode decl = null; String nodeLabel = null; String nameOfNode = null; // The node name which is to be scrolled to + + // Obtain correspondin java code at that line, match offsets if (lineNode != null) { + String pdeCodeLine = errorCheckerService.getPDECodeAtLine(editor + .getSketch().getCurrentCodeIndex(), lineNumber); + String javaCodeLine = getJavaSourceCodeLine(pdeLineNumber); + + log(lineNumber + " Original Line num.\nPDE :" + pdeCodeLine); + log("JAVA:" + javaCodeLine); + log("Clicked on: " + name + " start offset: " + offset); + // Calculate expected java offset based on the pde line + OffsetMatcher ofm = new OffsetMatcher(pdeCodeLine, javaCodeLine); + int javaOffset = ofm.getJavaOffForPdeOff(offset, name.length()) + + lineNode.getStartPosition(); + log("JAVA ast offset: " + (javaOffset)); - // Some delicate offset handling follows. - ASTNodeWrapper lineNodeWrap = new ASTNodeWrapper(lineNode); - int altOff = offset; - int ret[][] = lineNodeWrap.getOffsetMapping(errorCheckerService); - if(ret != null){ - altOff = 0; - int javaCodeMap[] = ret[0], pdeCodeMap[] = ret[1]; - - for (; altOff < javaCodeMap.length; altOff++) { - if (javaCodeMap[altOff] == pdeCodeMap[offset]) { - break; - } - } - } - log("FLON2: " + lineNumber + " LN spos " - + lineNode.getStartPosition() + " off " + offset + " alt off" + altOff); - /* - * Now I need to see if multiple statements exist with this same line number - * If that's the case, I need to ensure the offset is right. - */ - ASTNode parLineNode = lineNode.getParent(); - - Iterator it = parLineNode - .structuralPropertiesForType().iterator(); - boolean flag = true; - int offAdjust = 0; - while (it.hasNext() && flag) { - StructuralPropertyDescriptor prop = (StructuralPropertyDescriptor) it - .next(); - if (prop.isChildListProperty()) { - List nodelist = (List) parLineNode - .getStructuralProperty(prop); - for (ASTNode cnode : nodelist) { - if (getLineNumber(cnode) == lineNumber) { - if (cnode.getStartPosition() <= lineNode.getStartPosition() - + altOff - && cnode.getStartPosition() + cnode.getLength() > lineNode - .getStartPosition() + altOff) { - log(cnode); - offAdjust = cnode.getStartPosition() - lineNode.getStartPosition(); - lineNode = cnode; - altOff -= offAdjust; - flag = false; - break; - } - - } - } + // Find the corresponding node in the AST + ASTNode simpName = dfsLookForASTNode(errorCheckerService.getLatestCU(), + name, javaOffset, + javaOffset + name.length()); + + // If node wasn't found in the AST, lineNode may contain something + if (simpName == null && lineNode instanceof SimpleName) { + switch (lineNode.getParent().getNodeType()) { + case ASTNode.TYPE_DECLARATION: + + case ASTNode.METHOD_DECLARATION: + + case ASTNode.FIELD_DECLARATION: + + case ASTNode.VARIABLE_DECLARATION_FRAGMENT: + decl = lineNode.getParent(); + return new ASTNodeWrapper(decl, ""); + default: + break; } } - log("FLON3 "+lineNode.getStartPosition() + " off " + offset + " alt off" + altOff); - ASTNode simpName = pinpointOnLine(lineNode, altOff, - lineNode.getStartPosition(), name); - log("+++> " + simpName); - + + // SimpleName instance found, now find its declaration in code if (simpName instanceof SimpleName) { nameOfNode = simpName.toString(); log(getNodeAsString(simpName)); decl = findDeclaration((SimpleName) simpName); if (decl != null) { logE("DECLA: " + decl.getClass().getName()); - nodeLabel = getLabelIfType(new ASTNodeWrapper(decl), (SimpleName) simpName); + nodeLabel = getLabelIfType(new ASTNodeWrapper(decl), + (SimpleName) simpName); //retLabelString = getNodeAsString(decl); - } else + } else { logE("null"); + if (scrollOnly) { + editor.statusMessage(simpName + " is not defined in this sketch", + DebugEditor.STATUS_ERR); + } + } log(getNodeAsString(decl)); - + + /* // - findDecl3 testing - - ASTNode nearestNode = findClosestNode(lineNumber, (ASTNode) compilationUnit.types() - .get(0)); + + ASTNode nearestNode = findClosestNode(lineNumber, + (ASTNode) compilationUnit.types() + .get(0)); ClassMember cmem = resolveExpression3rdParty(nearestNode, - (SimpleName) simpName, false); - if(cmem != null){ - log("CMEM-> "+cmem); - } - else + (SimpleName) simpName, + false); + if (cmem != null) { + log("CMEM-> " + cmem); + } else log("CMEM-> null"); + */ } } if (decl != null && scrollOnly) { /* - * For scrolling, we highlight just the name of the node, - * i.e., a SimpleName instance. But the declared node always - * points to the declared node itself, like TypeDecl, MethodDecl, etc. - * This is important since it contains all the properties. + * For scrolling, we highlight just the name of the node, i.e., a + * SimpleName instance. But the declared node always points to the + * declared node itself, like TypeDecl, MethodDecl, etc. This is important + * since it contains all the properties. */ - ASTNode simpName2 = getNodeName(decl,nameOfNode); + ASTNode simpName2 = getNodeName(decl, nameOfNode); logE("FINAL String decl: " + getNodeAsString(decl)); logE("FINAL String label: " + getNodeAsString(simpName2)); - errorCheckerService.highlightNode(simpName2); - } + //errorCheckerService.highlightNode(simpName2); + ASTNodeWrapper declWrap = new ASTNodeWrapper(simpName2, nodeLabel); + //errorCheckerService.highlightNode(declWrap); + if (!declWrap.highlightNode(this)) { + logE("Highlighting failed."); + } + } - return new ASTNodeWrapper(decl,nodeLabel); + // Return the declaration wrapped as ASTNodeWrapper + return new ASTNodeWrapper(decl, nodeLabel); } - + /** * Given a declaration type astnode, returns the SimpleName peroperty * of that node. @@ -1735,6 +1839,7 @@ public static void traversal2() { } } + final ASTGenerator thisASTGenerator = this; protected void addListeners(){ jtree.addTreeSelectionListener(new TreeSelectionListener() { @@ -1758,6 +1863,34 @@ protected void done() { if (tnode.getUserObject() instanceof ASTNodeWrapper) { ASTNodeWrapper awrap = (ASTNodeWrapper) tnode.getUserObject(); errorCheckerService.highlightNode(awrap); + + //-- + try { + int javaLineNumber = getLineNumber(awrap.getNode()); + int pdeOffs[] = errorCheckerService + .calculateTabIndexAndLineNumber(javaLineNumber); + PlainDocument javaSource = new PlainDocument(); + javaSource.insertString(0, errorCheckerService.sourceCode, null); + Element lineElement = javaSource.getDefaultRootElement() + .getElement(javaLineNumber-1); + if(lineElement == null) { + return; + } + + String javaLine = javaSource.getText(lineElement.getStartOffset(), + lineElement.getEndOffset() + - lineElement.getStartOffset()); + editor.getSketch().setCurrentCode(pdeOffs[0]); + String pdeLine = editor.getLineText(pdeOffs[1]); + //String lookingFor = nodeName.toString(); + //log(lookingFor + ", " + nodeName.getStartPosition()); + log("JL " + javaLine + " LSO " + lineElement.getStartOffset() + "," + + lineElement.getEndOffset()); + log("PL " + pdeLine); + } catch (BadLocationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } } }; @@ -1827,7 +1960,8 @@ protected void done() { if (tnode.getUserObject() instanceof ASTNodeWrapper) { ASTNodeWrapper awrap = (ASTNodeWrapper) tnode.getUserObject(); - errorCheckerService.highlightNode(awrap); + //errorCheckerService.highlightNode(awrap); + awrap.highlightNode(thisASTGenerator); } } }; @@ -1840,12 +1974,15 @@ protected void refactorIt(){ String newName = txtRenameField.getText().trim(); String selText = lastClickedWord == null ? editor.ta.getSelectedText() : lastClickedWord; - DefaultMutableTreeNode defCU = findAllOccurrences(); + // Find all occurrences of last clicked word + DefaultMutableTreeNode defCU = findAllOccurrences(); //TODO: Repetition here if(defCU == null){ - editor.statusError("Can't locate definition of " + selText); + editor.statusMessage("Can't locate definition of " + selText, + DebugEditor.STATUS_ERR); return; } + // Verify if the new name is a valid java identifier if(!newName.matches("([a-zA-Z][a-zA-Z0-9_]*)|([_][a-zA-Z0-9_]+)")) { JOptionPane.showConfirmDialog(new JFrame(), newName @@ -1859,73 +1996,96 @@ protected void refactorIt(){ treeRename.setModel(new DefaultTreeModel(defCU)); ((DefaultTreeModel) treeRename.getModel()).reload(); } - frmOccurenceList.setTitle("Usage of " + selText); - frmOccurenceList.setLocation(editor.getX() + editor.getWidth(),editor.getY()); - frmOccurenceList.setVisible(true); +// frmOccurenceList.setTitle("Usage of \"" + selText + "\" : " +// + defCU.getChildCount() + " time(s)"); +// frmOccurenceList.setLocation(editor.getX() + editor.getWidth(),editor.getY()); +// frmOccurenceList.setVisible(true); int lineOffsetDisplacementConst = newName.length() - selText.length(); HashMap lineOffsetDisplacement = new HashMap(); // I need to store the pde and java offsets beforehand because once // the replace starts, all offsets returned are affected - int offsetsMap[][][] = new int[defCU.getChildCount()][2][]; + //int offsetsMap[][][] = new int[defCU.getChildCount()][2][]; + int pdeOffsets[][] = new int[defCU.getChildCount()][3]; for (int i = 0; i < defCU.getChildCount(); i++) { ASTNodeWrapper awrap = (ASTNodeWrapper) ((DefaultMutableTreeNode) (defCU .getChildAt(i))).getUserObject(); - offsetsMap[i][0] = awrap.getPDECodeOffsets(errorCheckerService); - offsetsMap[i][1] = awrap.getJavaCodeOffsets(errorCheckerService); + int ans[] = errorCheckerService.calculateTabIndexAndLineNumber(awrap + .getLineNumber()); + pdeOffsets[i][0] = ans[0]; + pdeOffsets[i][1] = ans[1]; + pdeOffsets[i][2] = awrap.getPDECodeOffsetForSN(this); } for (int i = 0; i < defCU.getChildCount(); i++) { - int pdeoffsets[] = offsetsMap[i][0]; - int javaoffsets[] = offsetsMap[i][1]; + ASTNodeWrapper awrap = (ASTNodeWrapper) ((DefaultMutableTreeNode) (defCU + .getChildAt(i))).getUserObject(); // correction for pde enhancements related displacement on a line int off = 0; - if (lineOffsetDisplacement.get(javaoffsets[0]) != null) { - off = lineOffsetDisplacement.get(javaoffsets[0]); + if (lineOffsetDisplacement.get(awrap.getLineNumber()) != null) { + off = lineOffsetDisplacement.get(awrap.getLineNumber()); - lineOffsetDisplacement.put(javaoffsets[0], + lineOffsetDisplacement.put(awrap.getLineNumber(), lineOffsetDisplacementConst + off); } else { - lineOffsetDisplacement.put(javaoffsets[0], + lineOffsetDisplacement.put(awrap.getLineNumber(), lineOffsetDisplacementConst); } - ErrorCheckerService.scrollToErrorLine(editor, pdeoffsets[0], - pdeoffsets[1], - javaoffsets[1] + off, - javaoffsets[2]); - //int k = JOptionPane.showConfirmDialog(new JFrame(), "Rename?","", JOptionPane.INFORMATION_MESSAGE)); +// logE(getNodeAsString(awrap.getNode()) + ", T:" + pdeOffsets[i][0] +// + ", L:" + pdeOffsets[i][1] + ", O:" + pdeOffsets[i][2]); + highlightPDECode(pdeOffsets[i][0], + pdeOffsets[i][1], pdeOffsets[i][2] + + off, awrap.getNode() + .toString().length()); + //int k = JOptionPane.showConfirmDialog(new JFrame(), "Rename?","", JOptionPane.INFORMATION_MESSAGE); editor.ta.setSelectedText(newName); } errorCheckerService.resumeThread(); -// for (Integer lineNum : lineOffsetDisplacement.keySet()) { -// log(lineNum + "line, disp" -// + lineOffsetDisplacement.get(lineNum)); -// } editor.getSketch().setModified(true); errorCheckerService.runManualErrorCheck(); - frmOccurenceList.setVisible(false); +// frmOccurenceList.setVisible(false); frmRename.setVisible(false); lastClickedWord = null; lastClickedWordNode = null; } + /** + * Highlights text in the editor + * @param tab + * @param lineNumber + * @param lineStartWSOffset - line start offset including initial white space + * @param length + */ + public void highlightPDECode(int tab, int lineNumber, int lineStartWSOffset, + int length) { + log("ASTGen.highlightPDECode: T " + tab + ",L: " + lineNumber + ",LSO: " + + lineStartWSOffset + ",Len: " + length); + editor.toFront(); + editor.getSketch().setCurrentCode(tab); + lineStartWSOffset += editor.ta.getLineStartOffset(lineNumber); + editor.ta.select(lineStartWSOffset, lineStartWSOffset + length); + } + public void handleShowUsage(){ log("Last clicked word:" + lastClickedWord); if(lastClickedWord == null && editor.ta.getSelectedText() == null){ - editor.statusError("Highlight the class/function/variable name first"); + editor.statusMessage("Highlight the class/function/variable name first" + , DebugEditor.STATUS_INFO); return; } if(errorCheckerService.hasSyntaxErrors()){ - editor.statusError("Can't perform action until syntax errors are fixed :("); + editor.statusMessage("Can't perform action until syntax errors are " + + "fixed :(", DebugEditor.STATUS_WARNING); return; } DefaultMutableTreeNode defCU = findAllOccurrences(); String selText = lastClickedWord == null ? editor.ta.getSelectedText() : lastClickedWord; if(defCU == null){ - editor.statusError("Can't locate definition of " + selText); + editor.statusMessage("Can't locate definition of " + selText, + DebugEditor.STATUS_ERR); return; } if(defCU.getChildCount() == 0) @@ -1933,7 +2093,8 @@ public void handleShowUsage(){ treeRename.setModel(new DefaultTreeModel(defCU)); ((DefaultTreeModel) treeRename.getModel()).reload(); treeRename.setRootVisible(false); - frmOccurenceList.setTitle("Usage of \"" + selText+ "\""); + frmOccurenceList.setTitle("Usage of \"" + selText + "\" : " + + defCU.getChildCount() + " time(s)"); frmOccurenceList.setLocation(editor.getX() + editor.getWidth(),editor.getY()); frmOccurenceList.setVisible(true); lastClickedWord = null; @@ -2089,7 +2250,8 @@ public void dfsNameOnly(DefaultMutableTreeNode tnode,ASTNode decl, String name) ASTNodeWrapper awnode = (ASTNodeWrapper) cnode.getUserObject(); // log("Visiting: " + getNodeAsString(awnode.getNode())); if(isInstanceOfType(awnode.getNode(), decl, name)){ - int val[] = errorCheckerService.JavaToPdeOffsets(awnode.getLineNumber(), 0); + int val[] = errorCheckerService + .JavaToPdeOffsets(awnode.getLineNumber(), 0); tnode.add(new DefaultMutableTreeNode(new ASTNodeWrapper(awnode .getNode(), "Line " + (val[1] + 1) + " | Tab: " + editor.getSketch().getCode(val[0]).getPrettyName()))); @@ -2098,6 +2260,67 @@ public void dfsNameOnly(DefaultMutableTreeNode tnode,ASTNode decl, String name) } } + public ASTNode dfsLookForASTNode(ASTNode root, String name, int startOffset, + int endOffset) { + log("dfsLookForASTNode() lookin for " + name + " Offsets: " + startOffset + + "," + endOffset); + Stack stack = new Stack(); + stack.push(root); + + while (!stack.isEmpty()) { + ASTNode node = (ASTNode) stack.pop(); + //log("Popped from stack: " + getNodeAsString(node)); + Iterator it = node + .structuralPropertiesForType().iterator(); + while (it.hasNext()) { + StructuralPropertyDescriptor prop = (StructuralPropertyDescriptor) it + .next(); + + if (prop.isChildProperty() || prop.isSimpleProperty()) { + if (node.getStructuralProperty(prop) instanceof ASTNode) { + ASTNode temp = (ASTNode) node.getStructuralProperty(prop); + if (temp.getStartPosition() <= startOffset + && (temp.getStartPosition() + temp.getLength()) >= endOffset) { + if(temp instanceof SimpleName){ + if(name.equals(temp.toString())){ + log("Found simplename: " + getNodeAsString(temp)); + return temp; + } + log("Bummer, didn't match"); + } + else + stack.push(temp); + //log("Pushed onto stack: " + getNodeAsString(temp)); + } + } + } + else if (prop.isChildListProperty()) { + List nodelist = (List) node + .getStructuralProperty(prop); + for (ASTNode temp : nodelist) { + if (temp.getStartPosition() <= startOffset + && (temp.getStartPosition() + temp.getLength()) >= endOffset) { + stack.push(temp); + log("Pushed onto stack: " + getNodeAsString(temp)); + if(temp instanceof SimpleName){ + if(name.equals(temp.toString())){ + log("Found simplename: " + getNodeAsString(temp)); + return temp; + } + log("Bummer, didn't match"); + } + else + stack.push(temp); + //log("Pushed onto stack: " + getNodeAsString(temp)); + } + } + } + } + } + log("dfsLookForASTNode() not found " + name); + return null; + } + protected SketchOutline sketchOutline; protected void showSketchOutline(){ sketchOutline = new SketchOutline(codeTree, errorCheckerService); @@ -2106,7 +2329,7 @@ protected void showSketchOutline(){ public int javaCodeOffsetToLineStartOffset(int line, int jOffset){ // Find the first node with this line number, return its offset - jOffset - line = PdeToJavaLineNumber(line); + line = pdeLineNumToJavaLineNum(line); log("Looking for line: " + line + ", jOff " + jOffset); Stack temp = new Stack(); temp.push(codeTree); @@ -2130,17 +2353,22 @@ public int javaCodeOffsetToLineStartOffset(int line, int jOffset){ return -1; } - protected int PdeToJavaLineNumber(int lineNum){ - int lineNumber = lineNum + errorCheckerService.getPdeImportsCount(); + /** + * Converts pde line number to java line number + * @param pdeLineNum - pde line number + * @return + */ + protected int pdeLineNumToJavaLineNum(int pdeLineNum){ + int javaLineNumber = pdeLineNum + errorCheckerService.getPdeImportsCount(); // Adjust line number for tabbed sketches int codeIndex = editor.getSketch().getCodeIndex(editor.getCurrentTab()); if (codeIndex > 0) for (int i = 0; i < codeIndex; i++) { SketchCode sc = editor.getSketch().getCode(i); int len = Base.countLines(sc.getProgram()) + 1; - lineNumber += len; + javaLineNumber += len; } - return lineNumber; + return javaLineNumber; } protected boolean isInstanceOfType(ASTNode node,ASTNode decl, String name){ @@ -2177,12 +2405,24 @@ protected boolean isInstanceOfType(ASTNode node,ASTNode decl, String name){ public void handleRefactor(){ log("Last clicked word:" + lastClickedWord); if(lastClickedWord == null && editor.ta.getSelectedText() == null){ - editor.statusError("Highlight the class/function/variable name first"); + editor.statusMessage("Highlight the class/function/variable name first", + DebugEditor.STATUS_INFO); return; } if(errorCheckerService.hasSyntaxErrors()){ - editor.statusError("Can't perform action until syntax errors are fixed :("); + editor + .statusMessage("Can't perform action until syntax errors are fixed :(", + DebugEditor.STATUS_WARNING); + return; + } + + DefaultMutableTreeNode defCU = findAllOccurrences(); + String selText = lastClickedWord == null ? editor.ta.getSelectedText() + : lastClickedWord; + if(defCU == null){ + editor.statusMessage(selText + " isn't defined in this sketch, so it can't" + + " be renamed", DebugEditor.STATUS_ERR); return; } if (!frmRename.isVisible()){ @@ -2302,10 +2542,10 @@ protected static ASTNode findLineOfNode(ASTNode node, int lineNumber, @SuppressWarnings("unchecked") public static ASTNode pinpointOnLine(ASTNode node, int offset, int lineStartOffset, String name) { - + //log("pinpointOnLine node class: " + node.getClass().getSimpleName()); if (node instanceof SimpleName) { SimpleName sn = (SimpleName) node; - log(offset+ "off,pol " + getNodeAsString(sn)); + //log(offset+ "off,pol " + getNodeAsString(sn)); if ((lineStartOffset + offset) >= sn.getStartPosition() && (lineStartOffset + offset) <= sn.getStartPosition() + sn.getLength()) { @@ -3155,17 +3395,16 @@ public void actionPerformed(ActionEvent e) { } - public void disposeAllWindows(){ - disposeWindow(frame2); - disposeWindow(frameAutoComp); - disposeWindow(frmImportSuggest); - disposeWindow(frmOccurenceList); - disposeWindow(frmRename); + public void disposeAllWindows() { + disposeWindow(frmASTView, frameAutoComp, frmImportSuggest, + frmOccurenceList, frmRename); } - public static void disposeWindow(JFrame f) { - if(f != null) - f.dispose(); + public static void disposeWindow(JFrame... f) { + for (JFrame jFrame : f) { + if(jFrame != null) + jFrame.dispose(); + } } public static final String ignoredImports[] = { diff --git a/src/processing/mode/experimental/ASTNodeWrapper.java b/src/processing/mode/experimental/ASTNodeWrapper.java index 9e99ad5..9798f3e 100644 --- a/src/processing/mode/experimental/ASTNodeWrapper.java +++ b/src/processing/mode/experimental/ASTNodeWrapper.java @@ -1,5 +1,24 @@ +/* + * Copyright (C) 2012-14 Manindra Moharana + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + */ + package processing.mode.experimental; import static processing.mode.experimental.ExperimentalMode.log; +import static processing.mode.experimental.ExperimentalMode.logE; import static processing.mode.experimental.ExperimentalMode.log2; import java.util.Iterator; import java.util.List; @@ -7,18 +26,30 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.swing.text.BadLocationException; +import javax.swing.text.Element; +import javax.swing.text.PlainDocument; + import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ExpressionStatement; import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.Javadoc; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; +import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +/** + * Wrapper class for ASTNode objects + * @author Manindra Moharana + * + */ public class ASTNodeWrapper { private ASTNode Node; @@ -76,6 +107,7 @@ public ASTNodeWrapper(ASTNode node, String label){ public int[] getJavaCodeOffsets(ErrorCheckerService ecs) { int nodeOffset = Node.getStartPosition(), nodeLength = Node .getLength(); + log("0.nodeOffset " + nodeOffset); ASTNode thisNode = Node; while (thisNode.getParent() != null) { if (getLineNumber(thisNode.getParent()) == lineNumber) { @@ -96,61 +128,196 @@ public int[] getJavaCodeOffsets(ErrorCheckerService ecs) { */ int altStartPos = thisNode.getStartPosition(); + log("1.Altspos " + altStartPos); thisNode = thisNode.getParent(); + Javadoc jd = null; + + /* + * There's another case that needs to be handled. If a TD, MD or FD + * contains javadoc comments(multi or single line) the starting position + * of the javadoc is treated as the beginning of the declaration by the AST parser. + * But that's clearly not what we need. The true decl begins after the javadoc ends. + * So this offset needs to be found carefully and stored in altStartPos + * + */ + if (thisNode instanceof TypeDeclaration) { + jd = ((TypeDeclaration) thisNode).getJavadoc(); + altStartPos = getJavadocOffset((TypeDeclaration) thisNode); + log("Has t jdoc " + ((TypeDeclaration) thisNode).getJavadoc()); + } else if (thisNode instanceof MethodDeclaration) { + altStartPos = getJavadocOffset((MethodDeclaration) thisNode); + jd = ((MethodDeclaration) thisNode).getJavadoc(); + log("Has m jdoc " + jd); + } else if (thisNode instanceof FieldDeclaration) { + FieldDeclaration fd = ((FieldDeclaration) thisNode); + jd = fd.getJavadoc(); + log("Has f jdoc " + fd.getJavadoc()); + altStartPos = getJavadocOffset(fd); + //nodeOffset = ((VariableDeclarationFragment)(fd.fragments().get(0))).getName().getStartPosition(); + } - Iterator it = thisNode - .structuralPropertiesForType().iterator(); - boolean flag = true; - while (it.hasNext()) { - StructuralPropertyDescriptor prop = (StructuralPropertyDescriptor) it - .next(); - if (prop.isChildListProperty()) { - List nodelist = (List) thisNode - .getStructuralProperty(prop); - for (ASTNode cnode : nodelist) { - if (getLineNumber(cnode) == lineNumber) { - if (flag) { - altStartPos = cnode.getStartPosition(); - // log("multi..."); - - flag = false; - } else { - if(cnode == Node){ - // loop only till the current node. - break; + if(jd == null){ + log("Visiting children of node " + getNodeAsString(thisNode)); + Iterator it = thisNode + .structuralPropertiesForType().iterator(); + boolean flag = true; + while (it.hasNext()) { + StructuralPropertyDescriptor prop = (StructuralPropertyDescriptor) it + .next(); + if (prop.isChildListProperty()) { + List nodelist = (List) thisNode + .getStructuralProperty(prop); + log("prop " + prop); + for (ASTNode cnode : nodelist) { + log("Visiting node " + getNodeAsString(cnode)); + if (getLineNumber(cnode) == lineNumber) { + if (flag) { + altStartPos = cnode.getStartPosition(); + // log("multi..."); + + flag = false; + } else { + if (cnode == Node) { + // loop only till the current node. + break; + } + // We've located the first node in the line. + // Now normalize offsets till Node + //altStartPos += normalizeOffsets(cnode); + } - // We've located the first node in the line. - // Now normalize offsets till Node - //altStartPos += normalizeOffsets(cnode); } - } } } + log("Altspos " + altStartPos); } - log("Altspos " + altStartPos); + int pdeoffsets[] = getPDECodeOffsets(ecs); String pdeCode = ecs.getPDECodeAtLine(pdeoffsets[0],pdeoffsets[1] - 1).trim(); - int vals[] = createOffsetMapping(pdeCode,nodeOffset - altStartPos,nodeLength); + int vals[] = createOffsetMapping(ecs, pdeCode,nodeOffset - altStartPos,nodeLength); if (vals != null) return new int[] { lineNumber, nodeOffset + vals[0] - altStartPos, vals[1] }; - else + else {// no offset mapping needed + log("joff[1] = " + (nodeOffset - altStartPos)); return new int[] { lineNumber, nodeOffset - altStartPos, nodeLength }; + } } - /** + * When FD has javadoc attached, the beginning of FD is marked as the + * start of the javadoc. This kind of screws things when trying to locate + * the exact name of the FD. So, offset compensations... * + * @param fd + * @return + */ + private int getJavadocOffset(FieldDeclaration fd){ + List list= fd.modifiers(); + SimpleName sn = (SimpleName) getNode(); + + Type tp = fd.getType(); + int lineNum = getLineNumber(sn); + log("SN "+sn + ", " + lineNum); + for (ASTNode astNode : list) { + if(getLineNumber(astNode) == lineNum) + { + log("first node in that line " + astNode); + log("diff " + (sn.getStartPosition() - astNode.getStartPosition())); + return (astNode.getStartPosition()); + } + } + if(getLineNumber(fd.getType()) == lineNum) + { + log("first node in that line " + tp); + log("diff " + (sn.getStartPosition() - tp.getStartPosition())); + return (tp.getStartPosition()); + } + + + return 0; + } + + /** + * When MD has javadoc attached, the beginning of FD is marked as the + * start of the javadoc. This kind of screws things when trying to locate + * the exact name of the MD. So, offset compensations... + * + * @param md + * @return + */ + private int getJavadocOffset(MethodDeclaration md) { + List list = md.modifiers(); + SimpleName sn = (SimpleName) getNode(); + int lineNum = getLineNumber(sn); + log("SN " + sn + ", " + lineNum); + + for (ASTNode astNode : list) { + if (getLineNumber(astNode) == lineNum) { + log("first node in that line " + astNode); + log("diff " + (sn.getStartPosition() - astNode.getStartPosition())); + return (astNode.getStartPosition()); + } + } + + if (!md.isConstructor()) { + Type tp = md.getReturnType2(); + if (getLineNumber(tp) == lineNum) { + log("first node in that line " + tp); + log("diff " + (sn.getStartPosition() - tp.getStartPosition())); + return (tp.getStartPosition()); + } + } + + return 0; + } + + /** + * When TD has javadoc attached, the beginning of FD is marked as the + * start of the javadoc. This kind of screws things when trying to locate + * the exact name of the TD. So, offset compensations... + * + * @param td + * @return + */ + private int getJavadocOffset(TypeDeclaration td){ + // TODO: This isn't perfect yet. Class \n \n \n className still breaks it.. :'( + List list= td.modifiers(); + list = td.modifiers(); + SimpleName sn = (SimpleName) getNode(); + + int lineNum = getLineNumber(sn); + log("SN "+sn + ", " + lineNum); + for (ASTNode astNode : list) { + if(getLineNumber(astNode) == lineNum) + { + log("first node in that line " + astNode); + log("diff " + (sn.getStartPosition() - astNode.getStartPosition())); + return (astNode.getStartPosition()); + } + } + + if(td.getJavadoc() != null){ + log("diff " + + (td.getJavadoc().getStartPosition() + td.getJavadoc().getLength() + 1)); + return (td.getJavadoc().getStartPosition() + td.getJavadoc().getLength() + 1); + } + log("getJavadocOffset(TypeDeclaration td) "+sn + ", found nothing. Meh."); + return 0; + } + + /** + * Finds the difference in pde and java code offsets * @param source * @param inpOffset * @param nodeLen * @return int[0] - difference in start offset, int[1] - node length */ - private int[] createOffsetMapping(String source, int inpOffset, int nodeLen) { + private int[] createOffsetMapping(ErrorCheckerService ecs, String source, int inpOffset, int nodeLen) { - int ret[][] = getOffsetMapping(source); + int ret[][] = getOffsetMapping(ecs, source); if(ret == null){ // no offset mapping needed return null; @@ -191,7 +358,7 @@ private int[] createOffsetMapping(String source, int inpOffset, int nodeLen) { * @param source * @return int[0] - java code offsets, int[1] = pde code offsets */ - public int[][] getOffsetMapping(String source){ + public int[][] getOffsetMapping(ErrorCheckerService ecs, String source){ /* * This is some tricky shiz. So detailed explanation follows: @@ -210,7 +377,7 @@ public int[][] getOffsetMapping(String source){ * index correction needed. (2) Now all java conversions are applied after * marking the offsets. This ensures that the index order isn't disturbed by * one at a time conversions as done in preprocessCode() in ECS. Took me - * sometime to figure out this was a bug. (3) Next I create a tables(two + * sometime to figure out this was a bug. (3) Next I create a table(two * separate arrays) which allows me to look it up for matching any index * between pde or java version of the snippet. This also lets me find out * any difference in length between both versions. @@ -222,9 +389,16 @@ public int[][] getOffsetMapping(String source){ */ log("Src:" + source); + // Instead of converting pde into java, how can I simply extract the same source + // from the java code? Think. TODO String sourceAlt = new String(source); + String sourceJava = ecs.astGenerator.getJavaSourceCodeLine(lineNumber); TreeMap offsetmap = new TreeMap(); + if(sourceJava.trim().startsWith("public") && !source.startsWith("public")){ + offsetmap.put(0,6); + //TODO: This is a temp fix. You GOTTA rewrite offset matching + } // Find all #[web color] // Should be 6 digits only. final String webColorRegexp = "#{1}[A-F|a-f|0-9]{6}\\W"; @@ -266,7 +440,7 @@ public int[][] getOffsetMapping(String source){ + Character.toUpperCase(dataType.charAt(0)) + dataType.substring(1) + "("); - } + } if(offsetmap.isEmpty()){ log("No offset matching needed."); return null; @@ -283,8 +457,11 @@ public int[][] getOffsetMapping(String source){ colorMatcher = colorPattern.matcher(sourceAlt); sourceAlt = colorMatcher.replaceAll("int"); - + + log("From direct source: "); +// sourceAlt = sourceJava; log(sourceAlt); + // Create code map. Beware! Dark magic ahead. int javaCodeMap[] = new int[source.length() * 2]; @@ -307,16 +484,20 @@ public int[][] getOffsetMapping(String source){ pi--; pj--; for (int i = 0; i < kval; i++, pi++, pj++) { - javaCodeMap[pi] = javaCodeMap[pi - 1]; - pdeCodeMap[pj] = pdeCodeMap[pj - 1] + 1; + if (pi > 1 && pj > 1) { + javaCodeMap[pi] = javaCodeMap[pi - 1]; + pdeCodeMap[pj] = pdeCodeMap[pj - 1] + 1; + } } } else { // repeat pde offsets pi--; pj--; for (int i = 0; i < -kval; i++, pi++, pj++) { - javaCodeMap[pi] = javaCodeMap[pi - 1] + 1; - pdeCodeMap[pj] = pdeCodeMap[pj - 1]; + if (pi > 1 && pj > 1) { + javaCodeMap[pi] = javaCodeMap[pi - 1] + 1; + pdeCodeMap[pj] = pdeCodeMap[pj - 1]; + } } } @@ -337,7 +518,7 @@ public int[][] getOffsetMapping(String source){ pj++; } - // deubg o/p + // debug o/p for (int i = 0; i < pdeCodeMap.length; i++) { if (pdeCodeMap[i] > 0 || javaCodeMap[i] > 0 || i == 0) { if (i < source.length()) @@ -354,10 +535,108 @@ public int[][] getOffsetMapping(String source){ return new int[][]{javaCodeMap,pdeCodeMap}; } + /** + * Highlight the ASTNode in the editor, if it's of type + * SimpleName + * @param astGenerator + * @return - true if highlighting was successful + */ + public boolean highlightNode(ASTGenerator astGenerator){ + if(!(Node instanceof SimpleName)){ + return false; + } + SimpleName nodeName = (SimpleName) Node; + try { + //TODO: Redundant code. See ASTGenerator.getJavaSourceCodeline() + int javaLineNumber = getLineNumber(nodeName); + int pdeOffs[] = astGenerator.errorCheckerService + .calculateTabIndexAndLineNumber(javaLineNumber); + PlainDocument javaSource = new PlainDocument(); + javaSource.insertString(0, astGenerator.errorCheckerService.sourceCode, null); + Element lineElement = javaSource.getDefaultRootElement() + .getElement(javaLineNumber-1); + if(lineElement == null) { + log(lineNumber + " line element null while highlighting " + nodeName); + return false; + } + + String javaLine = javaSource.getText(lineElement.getStartOffset(), + lineElement.getEndOffset() + - lineElement.getStartOffset()); + astGenerator.editor.getSketch().setCurrentCode(pdeOffs[0]); + String pdeLine = astGenerator.editor.getLineText(pdeOffs[1]); + String lookingFor = nodeName.toString(); + log(lookingFor + ", " + nodeName.getStartPosition()); + log(javaLineNumber +" JL " + javaLine + " LSO " + lineElement.getStartOffset() + "," + + lineElement.getEndOffset()); + log(pdeOffs[1] + " PL " + pdeLine); + if (!javaLine.contains(lookingFor) || !pdeLine.contains(lookingFor)) { + logE("Logical error in highLightNode(). Please file a bug report."); + return false; + } + + OffsetMatcher ofm = new OffsetMatcher(pdeLine, javaLine); + int highlightStart = ofm.getPdeOffForJavaOff(nodeName.getStartPosition() + - lineElement.getStartOffset(), + nodeName.getLength()); + if (highlightStart == -1) { + logE("Logical error in highLightNode() during offset matching. " + + "Please file a bug report."); + return false; + } + int lso = astGenerator.editor.ta.getLineStartOffset(pdeOffs[1]); + highlightStart += lso; + astGenerator.editor.setSelection(highlightStart, highlightStart + + nodeName.getLength()); + /* + // First find the name in the java line, and marks its index + Pattern toFind = Pattern.compile("\\b" + nodeName.toString() + "\\b"); + Matcher matcher = toFind.matcher(javaLine); + int count = 0, index = 0; + int lsto = lineElement.getStartOffset(); + while(matcher.find()){ + count++; + //log(matcher.start() + lsto); + if(lsto + matcher.start() == nodeName.getStartPosition()) + break; + } + log("count=" + count); + index = 0; + // find the same name in the pde line by its index and get its offsets + matcher = toFind.matcher(pdeLine); + while(matcher.find()){ + count--; + if(count == 0){ + log("Found on pde line lso: " + matcher.start()); + index = matcher.end(); + break; + } + } + log("pde lso " + (index - lookingFor.length())); + + int lso = astGenerator.editor.ta.getLineStartOffset(pdeOffs[1]); + astGenerator.editor.setSelection(lso + index - lookingFor.length(), lso + + index); + */ + return true; + } catch (BadLocationException e) { + logE("BLE in highLightNode() for " + nodeName); + e.printStackTrace(); + } + return false; + } + + /** + * Gets offset mapping between java and pde code + * int[0][x] stores the java code offset and + * int[1][x] is the corresponding offset in pde code + * @param ecs + * @return int[0] - java code offset, int[1] - pde code offset + */ public int[][] getOffsetMapping(ErrorCheckerService ecs){ int pdeoffsets[] = getPDECodeOffsets(ecs); String pdeCode = ecs.getPDECodeAtLine(pdeoffsets[0],pdeoffsets[1] - 1).trim(); - return getOffsetMapping(pdeCode); + return getOffsetMapping(ecs, pdeCode); } /** @@ -371,6 +650,23 @@ public int[][] getOffsetMapping(ErrorCheckerService ecs){ public int[] getPDECodeOffsets(ErrorCheckerService ecs) { return ecs.JavaToPdeOffsets(lineNumber + 1, Node.getStartPosition()); } + + public int getPDECodeOffsetForSN(ASTGenerator astGen){ + if (Node instanceof SimpleName) { + Element lineElement = astGen.getJavaSourceCodeElement(lineNumber); + log("Line element off " + lineElement.getStartOffset()); + OffsetMatcher ofm = new OffsetMatcher( + astGen + .getPDESourceCodeLine(lineNumber), + astGen + .getJavaSourceCodeLine(lineNumber)); + //log(""); + int pdeOffset = ofm.getPdeOffForJavaOff(Node.getStartPosition() + - lineElement.getStartOffset(), Node.toString().length()); + return pdeOffset; + } + return -1; + } public String toString() { return label; @@ -462,6 +758,27 @@ private static int getLineNumber(ASTNode node) { return ((CompilationUnit) node.getRoot()).getLineNumber(node .getStartPosition()); } + + /*private static int getLineNumber2(ASTNode thisNode) { + int jdocOffset = 0; Javadoc jd = null; + if(thisNode instanceof TypeDeclaration){ + jd = ((TypeDeclaration)thisNode).getJavadoc(); + log("Has t jdoc " + ((TypeDeclaration)thisNode).getJavadoc()); + } else if(thisNode instanceof MethodDeclaration){ + jd = ((MethodDeclaration)thisNode).getJavadoc(); + log("Has m jdoc " + jd); + } else if(thisNode instanceof FieldDeclaration){ + jd = ((FieldDeclaration)thisNode).getJavadoc(); + log("Has f jdoc " + ((FieldDeclaration)thisNode).getJavadoc()); + } + if(jd != null){ + jdocOffset = 1+jd.getLength(); + } + log("ln 2 = " + ((CompilationUnit) thisNode.getRoot()).getLineNumber(thisNode + .getStartPosition() + jdocOffset)); + return ((CompilationUnit) thisNode.getRoot()).getLineNumber(thisNode + .getStartPosition() + jdocOffset); + }*/ static private String getNodeAsString(ASTNode node) { if (node == null) diff --git a/src/processing/mode/experimental/AutoSaveUtil.java b/src/processing/mode/experimental/AutoSaveUtil.java new file mode 100644 index 0000000..7efa14e --- /dev/null +++ b/src/processing/mode/experimental/AutoSaveUtil.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2012-14 Manindra Moharana + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package processing.mode.experimental; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.Timer; +import java.util.TimerTask; + +import processing.app.Base; +import processing.app.Sketch; + +import static processing.mode.experimental.ExperimentalMode.log; + +/** + * Autosave utility for saving sketch backups in the background after + * certain intervals + * + * @author Manindra Moharana + * + */ +public class AutoSaveUtil { + + private DebugEditor editor; + + private Timer timer; + + private int saveTime; + + private File autosaveDir, pastSave; + + private boolean isSaving; + + private boolean isAutoSaveBackup; + + private File sketchFolder, sketchBackupFolder; + + private static final String AUTOSAVEFOLDER = "__autosave__"; + + /** + * + * @param dedit + * @param timeOut - in minutes, how frequently should saves occur + */ + public AutoSaveUtil(DebugEditor dedit, int timeOut){ + /* + editor = dedit; + if (timeOut < 1) { // less than 1 minute not allowed! + saveTime = -1; + throw new IllegalArgumentException(""); + } + else{ + saveTime = timeOut * 60 * 1000; + log("AutoSaver Interval(mins): " + timeOut); + } + checkIfBackup(); + if(isAutoSaveBackup){ + sketchBackupFolder = sketchFolder; + } + else{ + autosaveDir = new File(editor.getSketch().getFolder().getAbsolutePath() + File.separator + AUTOSAVEFOLDER); + sketchFolder = editor.getSketch().getFolder(); + sketchBackupFolder = autosaveDir; + }*/ + } + + /** + * If the sketch path looks like ../__autosave__/../FooSketch + * then assume this is a backup sketch + */ + private void checkIfBackup(){ + File parent = sketchFolder.getParentFile().getParentFile(); + if(parent.isDirectory() && parent.getName().equals(AUTOSAVEFOLDER)){ + isAutoSaveBackup = true; + log("IS AUTOSAVE " + sketchFolder.getAbsolutePath()); + } + } + + public File getActualSketchFolder(){ + if(isAutoSaveBackup) + return sketchFolder.getParentFile().getParentFile().getParentFile(); + else + return sketchFolder; + } + + public boolean isAutoSaveBackup() { + return isAutoSaveBackup; + } + + /** + * Check if any previous autosave exists + * @return + */ + public boolean checkForPastSave(){ + if(autosaveDir.exists()){ + String prevSaves[] = Base.listFiles(autosaveDir, false); + if(prevSaves.length > 0){ + File t = new File(Base.listFiles(new File(prevSaves[0]), false)[0]); + sketchBackupFolder = t; + pastSave = new File(t.getAbsolutePath() + File.separator + t.getName() + ".pde"); + if(pastSave.exists()) + return true; + } + } + return false; + } + + /** + * Refresh autosave directory if current sketch location in the editor changes + */ + public void reloadAutosaveDir(){ + while(isSaving); + autosaveDir = new File(editor.getSketch().getFolder().getAbsolutePath() + File.separator + AUTOSAVEFOLDER); + } + + public File getAutoSaveDir(){ + return autosaveDir; + } + + /** + * The folder of the original sketch + * @return + */ + public File getSketchFolder(){ + return sketchFolder; + } + + public File getSketchBackupFolder(){ + return sketchBackupFolder; + } + + public File getPastSave(){ + return pastSave; + } + + /** + * Start the auto save service + */ + public void init(){ + /* + if(isAutoSaveBackup) { + log("AutoSaver not started"); + return; + } + if(saveTime < 10000) saveTime = 10 * 1000; + saveTime = 5 * 1000; //TODO: remove + timer = new Timer(); + timer.schedule(new SaveTask(), saveTime, saveTime); + isSaving = false; + log("AutoSaver started"); + */ + } + + /** + * Stop the autosave service + */ + public void stop(){ + while(isSaving); // save operation mustn't be interrupted + if(timer != null) timer.cancel(); + Base.removeDir(autosaveDir); + ExperimentalMode.log("Stopping autosaver and deleting backup dir"); + } + + /** + * Main function that performs the save operation + * Code reused from processing.app.Sketch.saveAs() + * @return + * @throws IOException + */ + private boolean saveSketch() throws IOException{ + if(!editor.getSketch().isModified()) return false; + isSaving = true; + Sketch sc = editor.getSketch(); + + boolean deleteOldSave = false; + String oldSave = null; + if(!autosaveDir.exists()){ + autosaveDir = new File(sc.getFolder().getAbsolutePath(), AUTOSAVEFOLDER); + autosaveDir.mkdir(); + } + else + { + // delete the previous backup after saving current one. + String prevSaves[] = Base.listFiles(autosaveDir, false); + if(prevSaves.length > 0){ + deleteOldSave = true; + oldSave = prevSaves[0]; + } + } + String newParentDir = autosaveDir + File.separator + System.currentTimeMillis(); + String newName = sc.getName(); + + + // check on the sanity of the name + String sanitaryName = Sketch.checkName(newName); + File newFolder = new File(newParentDir, sanitaryName); + if (!sanitaryName.equals(newName) && newFolder.exists()) { + Base.showMessage("Cannot Save", + "A sketch with the cleaned name\n" + + "“" + sanitaryName + "” already exists."); + isSaving = false; + return false; + } + newName = sanitaryName; + +// String newPath = newFolder.getAbsolutePath(); +// String oldPath = folder.getAbsolutePath(); + +// if (newPath.equals(oldPath)) { +// return false; // Can't save a sketch over itself +// } + + // make sure there doesn't exist a tab with that name already + // but ignore this situation for the first tab, since it's probably being + // resaved (with the same name) to another location/folder. + for (int i = 1; i < sc.getCodeCount(); i++) { + if (newName.equalsIgnoreCase(sc.getCode()[i].getPrettyName())) { + Base.showMessage("Nope", + "You can't save the sketch as \"" + newName + "\"\n" + + "because the sketch already has a tab with that name."); + isSaving = false; + return false; + } + } + + + + // if the new folder already exists, then first remove its contents before + // copying everything over (user will have already been warned). + if (newFolder.exists()) { + Base.removeDir(newFolder); + } + // in fact, you can't do this on Windows because the file dialog + // will instead put you inside the folder, but it happens on OS X a lot. + + // now make a fresh copy of the folder + newFolder.mkdirs(); + + // grab the contents of the current tab before saving + // first get the contents of the editor text area + if (sc.getCurrentCode().isModified()) { + sc.getCurrentCode().setProgram(editor.getText()); + } + + File[] copyItems = sc.getFolder().listFiles(new FileFilter() { + public boolean accept(File file) { + String name = file.getName(); + // just in case the OS likes to return these as if they're legit + if (name.equals(".") || name.equals("..")) { + return false; + } + // list of files/folders to be ignored during "save as" + for (String ignorable : editor.getMode().getIgnorable()) { + if (name.equals(ignorable)) { + return false; + } + } + // ignore the extensions for code, since that'll be copied below + for (String ext : editor.getMode().getExtensions()) { + if (name.endsWith(ext)) { + return false; + } + } + // don't do screen captures, since there might be thousands. kind of + // a hack, but seems harmless. hm, where have i heard that before... + if (name.startsWith("screen-")) { + return false; + } + return true; + } + }); + // now copy over the items that make sense + for (File copyable : copyItems) { + if (copyable.isDirectory()) { + Base.copyDir(copyable, new File(newFolder, copyable.getName())); + } else { + Base.copyFile(copyable, new File(newFolder, copyable.getName())); + } + } + + // save the other tabs to their new location + for (int i = 1; i < sc.getCodeCount(); i++) { + File newFile = new File(newFolder, sc.getCode()[i].getFileName()); + sc.getCode()[i].saveAs(newFile); + } + + // While the old path to the main .pde is still set, remove the entry from + // the Recent menu so that it's not sticking around after the rename. + // If untitled, it won't be in the menu, so there's no point. +// if (!isUntitled()) { +// editor.removeRecent(); +// } + + // save the main tab with its new name + File newFile = new File(newFolder, newName + ".pde"); + sc.getCode()[0].saveAs(newFile); + +// updateInternal(newName, newFolder); +// +// // Make sure that it's not an untitled sketch +// setUntitled(false); +// +// // Add this sketch back using the new name +// editor.addRecent(); + + // let Editor know that the save was successful + + if(deleteOldSave){ + Base.removeDir(new File(oldSave)); + } + isSaving = false; + return true; + } + + /** + * Timertask used to perform the save operation every X minutes + * @author quarkninja + * + */ + private class SaveTask extends TimerTask{ + + @Override + public void run() { + try { + if(saveSketch()) + ExperimentalMode.log("Backup Saved " + editor.getSketch().getMainFilePath()); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + } + +} diff --git a/src/processing/mode/experimental/CompletionPanel.java b/src/processing/mode/experimental/CompletionPanel.java index a7fd9bb..00ee4e1 100644 --- a/src/processing/mode/experimental/CompletionPanel.java +++ b/src/processing/mode/experimental/CompletionPanel.java @@ -1,17 +1,33 @@ +/* + * Copyright (C) 2012-14 Manindra Moharana + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + */ + package processing.mode.experimental; import static processing.mode.experimental.ExperimentalMode.log; +import static processing.mode.experimental.ExperimentalMode.log2; import static processing.mode.experimental.ExperimentalMode.logE; import java.awt.BorderLayout; -import java.awt.Color; import java.awt.Component; import java.awt.FontMetrics; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.util.Iterator; -import javax.swing.BorderFactory; import javax.swing.DefaultListModel; import javax.swing.JLabel; import javax.swing.JList; @@ -23,21 +39,51 @@ import processing.app.syntax.JEditTextArea; +/** + * Manages the actual suggestion popup that gets displayed + * @author Manindra Moharana + * + */ public class CompletionPanel { + + /** + * The completion list generated by ASTGenerator + */ private JList completionList; + /** + * The popup menu in which the suggestion list is shown + */ private JPopupMenu popupMenu; + /** + * Partial word which triggered the code completion and which needs to be completed + */ private String subWord; + /** + * Postion where the completion has to be inserted + */ private int insertionPosition; private TextArea textarea; + /** + * Scroll pane in which the completion list is displayed + */ private JScrollPane scrollPane; protected DebugEditor editor; + /** + * Triggers the completion popup + * @param textarea + * @param position - insertion position(caret pos) + * @param subWord - Partial word which triggered the code completion and which needs to be completed + * @param items - completion candidates + * @param location - Point location where popup list is to be displayed + * @param dedit + */ public CompletionPanel(final JEditTextArea textarea, int position, String subWord, DefaultListModel items, final Point location, DebugEditor dedit) { this.textarea = (TextArea) textarea; @@ -60,7 +106,7 @@ public CompletionPanel(final JEditTextArea textarea, int position, String subWor textarea.requestFocusInWindow(); popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0) + location.y); - log("Suggestion constructed" + System.nanoTime()); + log("Suggestion shown: " + System.currentTimeMillis()); } public boolean isVisible() { @@ -72,12 +118,16 @@ public void setVisible(boolean v){ popupMenu.setVisible(v); } - protected int setHeight(int itemCount){ - if(scrollPane.getHorizontalScrollBar().isVisible()) itemCount++; - FontMetrics fm = textarea.getFontMetrics(textarea.getFont()); - float h = (fm.getHeight() + fm.getDescent()*0.5f) * (itemCount + 1); - log("popup height " + Math.min(250,h)); - return Math.min(250,(int)h); // popup menu height + private int setHeight(int itemCount){ + FontMetrics fm = textarea.getFontMetrics(textarea.getFont()); + float h = (fm.getHeight() + (fm.getDescent()) * 0.5f) * (itemCount); + if (scrollPane.getHorizontalScrollBar().isVisible()) + h += scrollPane.getHorizontalScrollBar().getHeight() + fm.getHeight() + + (fm.getDescent() + fm.getAscent()) * 0.8f; + // 0.5f and 0.8f scaling give respectable results. + //log("popup height " + Math.min(250,h) + //+ scrollPane.getHorizontalScrollBar().isVisible()); + return Math.min(250, (int) h); // popup menu height } /*TODO: Make width dynamic @@ -94,6 +144,12 @@ protected int setWidth(){ return Math.min(280,(int)min); // popup menu height }*/ + /** + * Created the popup list to be displayed + * @param position + * @param items + * @return + */ private JList createSuggestionList(final int position, final DefaultListModel items) { @@ -130,7 +186,7 @@ public void run() { completionList.setSelectedIndex(0); scrollPane.setViewportView(completionList); popupMenu.setPopupSize(popupMenu.getSize().width, setHeight(items.getSize())); - log("Suggestion updated" + System.nanoTime()); + //log("Suggestion updated" + System.nanoTime()); textarea.requestFocusInWindow(); popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0) + location.y); @@ -142,44 +198,148 @@ public void run() { return true; } + /** + * Inserts the CompletionCandidate chosen from the suggestion list + * + * @return + */ public boolean insertSelection() { if (completionList.getSelectedValue() != null) { try { + // If user types 'abc.', subword becomes '.' and null is returned + String currentSubword = fetchCurrentSubword(); + int currentSubwordLen = currentSubword == null ? 0 : currentSubword + .length(); + //logE(currentSubword + " <= subword,len => " + currentSubword.length()); String selectedSuggestion = ((CompletionCandidate) completionList - .getSelectedValue()).getCompletionString().substring(subWord.length()); - logE(subWord+" <= subword,Inserting suggestion=> " + selectedSuggestion); - textarea.getDocument().remove(insertionPosition-subWord.length(), subWord.length()); - textarea.getDocument().insertString(insertionPosition-subWord.length(), - ((CompletionCandidate) completionList - .getSelectedValue()).getCompletionString(), null); - if(selectedSuggestion.endsWith(")")) - { - if(!selectedSuggestion.endsWith("()")){ + .getSelectedValue()).getCompletionString(); + + if (currentSubword != null) { + selectedSuggestion = selectedSuggestion.substring(currentSubwordLen); + } else { + currentSubword = ""; + } + + logE(subWord + " <= subword, Inserting suggestion=> " + + selectedSuggestion + " Current sub: " + currentSubword); + if (currentSubword.length() > 0) { + textarea.getDocument().remove(insertionPosition - currentSubwordLen, + currentSubwordLen); + } + + textarea.getDocument() + .insertString(insertionPosition - currentSubwordLen, + ((CompletionCandidate) completionList + .getSelectedValue()).getCompletionString(), null); + if (selectedSuggestion.endsWith(")")) { + if (!selectedSuggestion.endsWith("()")) { int x = selectedSuggestion.indexOf('('); - if(x != -1){ + if (x != -1) { //log("X................... " + x); - textarea.setCaretPosition(insertionPosition + (x+1)); + textarea.setCaretPosition(insertionPosition + (x + 1)); } } + } else { + textarea.setCaretPosition(insertionPosition + + selectedSuggestion.length()); } - else { - textarea.setCaretPosition(insertionPosition + selectedSuggestion.length()); - } + log("Suggestion inserted: " + System.currentTimeMillis()); return true; } catch (BadLocationException e1) { e1.printStackTrace(); } + catch (Exception e) { + e.printStackTrace(); + } hide(); } return false; } + + private String fetchCurrentSubword() { + //log("Entering fetchCurrentSubword"); + TextArea ta = editor.ta; + int off = ta.getCaretPosition(); + //log2("off " + off); + if (off < 0) + return null; + int line = ta.getCaretLine(); + if (line < 0) + return null; + String s = ta.getLineText(line); + //log2("lin " + line); + //log2(s + " len " + s.length()); + + int x = ta.getCaretPosition() - ta.getLineStartOffset(line) - 1, x1 = x - 1; + if(x >= s.length() || x < 0) + return null; //TODO: Does this check cause problems? Verify. + log2(" x char: " + s.charAt(x)); + //int xLS = off - getLineStartNonWhiteSpaceOffset(line); + + String word = (x < s.length() ? s.charAt(x) : "") + ""; + if (s.trim().length() == 1) { + // word = "" + // + (keyChar == KeyEvent.CHAR_UNDEFINED ? s.charAt(x - 1) : keyChar); + //word = (x < s.length()?s.charAt(x):"") + ""; + word = word.trim(); + if (word.endsWith(".")) + word = word.substring(0, word.length() - 1); + + return word; + } + //log("fetchCurrentSubword 1 " + word); + if(word.equals(".")) return null; // If user types 'abc.', subword becomes '.' + // if (keyChar == KeyEvent.VK_BACK_SPACE || keyChar == KeyEvent.VK_DELETE) + // ; // accepted these keys + // else if (!(Character.isLetterOrDigit(keyChar) || keyChar == '_' || keyChar == '$')) + // return null; + int i = 0; + + while (true) { + i++; + //TODO: currently works on single line only. "a. b()" won't be detected + if (x1 >= 0) { +// if (s.charAt(x1) != ';' && s.charAt(x1) != ',' && s.charAt(x1) != '(') + if (Character.isLetterOrDigit(s.charAt(x1)) || s.charAt(x1) == '_') { + + word = s.charAt(x1--) + word; + + } else { + break; + } + } else { + break; + } + if (i > 200) { + // time out! + break; + } + } + // if (keyChar != KeyEvent.CHAR_UNDEFINED) + //log("fetchCurrentSubword 2 " + word); + if (Character.isDigit(word.charAt(0))) + return null; + word = word.trim(); + if (word.endsWith(".")) + word = word.substring(0, word.length() - 1); + //log("fetchCurrentSubword 3 " + word); + //showSuggestionLater(); + return word; + //} + } + /** + * Hide the suggestion list + */ public void hide() { popupMenu.setVisible(false); - log("Suggestion hidden" + System.nanoTime()); + //log("Suggestion hidden" + System.nanoTime()); //textarea.errorCheckerService.getASTGenerator().jdocWindowVisible(false); } + /** + * When up arrow key is pressed, moves the highlighted selection up in the list + */ public void moveUp() { if (completionList.getSelectedIndex() == 0) { scrollPane.getVerticalScrollBar().setValue(scrollPane.getVerticalScrollBar().getMaximum()); @@ -200,6 +360,9 @@ public void moveUp() { } + /** + * When down arrow key is pressed, moves the highlighted selection down in the list + */ public void moveDown() { if (completionList.getSelectedIndex() == completionList.getModel().getSize() - 1) { scrollPane.getVerticalScrollBar().setValue(0); @@ -231,7 +394,13 @@ private void selectIndex(int index) { // }); } - protected class CustomListRenderer extends + + /** + * Custom cell renderer to display icons along with the completion candidates + * @author Manindra Moharana + * + */ + private class CustomListRenderer extends javax.swing.DefaultListCellRenderer { //protected final ImageIcon classIcon, fieldIcon, methodIcon; diff --git a/src/processing/mode/experimental/DebugEditor.java b/src/processing/mode/experimental/DebugEditor.java index 953a4bf..c64a3ae 100755 --- a/src/processing/mode/experimental/DebugEditor.java +++ b/src/processing/mode/experimental/DebugEditor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Martin Leopold + * Copyright (C) 2012-14 Martin Leopold and Manindra Moharana * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software @@ -22,7 +22,10 @@ import java.awt.CardLayout; import java.awt.Color; import java.awt.Component; +import java.awt.Container; import java.awt.EventQueue; +import java.awt.FlowLayout; +import java.awt.Font; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -36,18 +39,28 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; +import java.util.Timer; +import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; +import javax.swing.BorderFactory; import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; +import javax.swing.JDialog; +import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; +import javax.swing.border.Border; import javax.swing.border.EtchedBorder; import javax.swing.table.TableModel; import javax.swing.text.Document; @@ -58,6 +71,7 @@ import processing.app.EditorState; import processing.app.EditorToolbar; import processing.app.Mode; +import processing.app.Preferences; import processing.app.Sketch; import processing.app.SketchCode; import processing.app.Toolkit; @@ -177,6 +191,8 @@ public class DebugEditor extends JavaEditor implements ActionListener { */ protected JCheckBoxMenuItem completionsEnabled; + protected AutoSaveUtil autosaver; + public DebugEditor(Base base, String path, EditorState state, Mode mode) { super(base, path, state, mode); @@ -243,7 +259,7 @@ public void actionPerformed(ActionEvent e) { ta.setECSandThemeforTextArea(errorCheckerService, dmode); addXQModeUI(); debugToolbarEnabled = new AtomicBoolean(false); - log("Sketch Path: " + path); + //log("Sketch Path: " + path); } private void addXQModeUI(){ @@ -329,7 +345,7 @@ public void windowGainedFocus(WindowEvent e) { public void dispose() { //System.out.println("window dispose"); // quit running debug session - dbg.stopDebug(); + dbg.stopDebug(); // remove var.inspector vi.dispose(); errorCheckerService.stopThread(); @@ -340,9 +356,17 @@ public void dispose() { // Added temporarily to dump error log. TODO: Remove this later public void internalCloseRunner(){ if(ExperimentalMode.errorLogsEnabled) writeErrorsToFile(); +// if(autosaver != null && !viewingAutosaveBackup) { +// log("stopping autosaver in internalCloseRunner"); +// autosaver.stop(); +// } super.internalCloseRunner(); } + /** + * Writes all error messages to a csv file. + * For analytics purposes only. + */ private void writeErrorsToFile(){ if (errorCheckerService.tempErrorLog.size() == 0) return; @@ -414,9 +438,16 @@ public void actionPerformed(ActionEvent e) { return buildSketchMenu(new JMenuItem[]{runItem, presentItem, stopItem}); }*/ + /** + * Whether debug toolbar is enabled + */ AtomicBoolean debugToolbarEnabled; + protected EditorToolbar javaToolbar, debugToolbar; + /** + * Toggles between java mode and debug mode toolbar + */ protected void switchToolbars(){ final EditorToolbar nextToolbar; if(debugToolbarEnabled.get()){ @@ -547,7 +578,7 @@ public void actionPerformed(ActionEvent e) { }); debugMenu.add(item); - problemWindowMenuCB = new JCheckBoxMenuItem("Show Problem Window"); + /*problemWindowMenuCB = new JCheckBoxMenuItem("Show Problem Window"); // problemWindowMenuCB.setSelected(true); problemWindowMenuCB.addActionListener(new ActionListener() { @@ -563,7 +594,7 @@ public void actionPerformed(ActionEvent e) { showProblemListView(XQConsoleToggle.CONSOLE); } }); - debugMenu.add(problemWindowMenuCB); + debugMenu.add(problemWindowMenuCB);*/ showWarnings = new JCheckBoxMenuItem("Warnings Enabled"); showWarnings.setSelected(ExperimentalMode.warningsEnabled); @@ -724,6 +755,7 @@ public void handleStop() { */ @Override protected boolean handleOpenInternal(String path) { + log("handleOpenInternal, path: " + path); boolean didOpen = super.handleOpenInternal(path); if (didOpen && dbg != null) { // should already been stopped (open calls handleStop) @@ -731,6 +763,12 @@ protected boolean handleOpenInternal(String path) { clearBreakpointedLines(); // force clear breakpoint highlights variableInspector().reset(); // clear contents of variable inspector } + //if(didOpen){ + autosaver = new AutoSaveUtil(this, ExperimentalMode.autoSaveInterval); // this is used instead of loadAutosaver(), temp measure + //loadAutoSaver(); + viewingAutosaveBackup = autosaver.isAutoSaveBackup(); + log("handleOpenInternal, viewing autosave? " + viewingAutosaveBackup); + //} return didOpen; } @@ -808,7 +846,38 @@ protected void addBreakpointComments(String tabFilename) { @Override public boolean handleSave(boolean immediately) { //System.out.println("handleSave " + immediately); - + + log("handleSave, viewing autosave? " + viewingAutosaveBackup); + /* If user wants to save a backup, the backup sketch should get + * copied to the main sketch directory, simply reload the main sketch. + */ + if(viewingAutosaveBackup){ + /* + File files[] = autosaver.getSketchBackupFolder().listFiles(); + File src = autosaver.getSketchBackupFolder(), dst = autosaver + .getActualSketchFolder(); + for (File f : files) { + log("Copying " + f.getAbsolutePath() + " to " + dst.getAbsolutePath()); + try { + if (f.isFile()) { + f.delete(); + Base.copyFile(f, new File(dst + File.separator + f.getName())); + } else { + Base.removeDir(f); + Base.copyDir(f, new File(dst + File.separator + f.getName())); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + File sk = autosaver.getActualSketchFolder(); + Base.removeDir(autosaver.getAutoSaveDir()); + //handleOpenInternal(sk.getAbsolutePath() + File.separator + sk.getName() + ".pde"); + getBase().handleOpen(sk.getAbsolutePath() + File.separator + sk.getName() + ".pde"); + //viewingAutosaveBackup = false; + */ + } + // note modified tabs final List modified = new ArrayList(); for (int i = 0; i < getSketch().getCodeCount(); i++) { @@ -835,6 +904,8 @@ public void run() { }); } } + // if file location has changed, update autosaver + // autosaver.reloadAutosaveDir(); return saved; } @@ -863,8 +934,45 @@ public boolean handleSaveAs() { // set new name of variable inspector vi.setTitle(getSketch().getName()); } + // if file location has changed, update autosaver +// autosaver.reloadAutosaveDir(); return saved; } + + private boolean viewingAutosaveBackup; + + /** + * Loads and starts the auto save service + * Also handles the case where an auto save backup is found. + * The user is asked to save the sketch to a new location + */ + private void loadAutoSaver(){ + log("Load Auto Saver()"); + autosaver = new AutoSaveUtil(this, ExperimentalMode.autoSaveInterval); + if(!autosaver.checkForPastSave()) { + autosaver.init(); + return; + } + File pastSave = autosaver.getPastSave(); + int response = Base + .showYesNoQuestion(this, + "Unsaved backup found!", + "An automatic backup of \"" + + pastSave.getParentFile().getName() + + "\" sketch has been found. This may mean Processing " + + "was closed unexpectedly last time.", + "Select YES to view it or NO to delete the backup."); + if(response == JOptionPane.YES_OPTION){ + handleOpenInternal(pastSave.getAbsolutePath()); + // Base.showMessage("Save it..", "Remember to save the backup sketch to a specific location if you want to."); + //log(getSketch().getMainFilePath()); + log("loadAutoSaver, viewing autosave? " + viewingAutosaveBackup); + return; + } + else{ + autosaver.init(); + } + } /** * Set text contents of a specific tab. Updates underlying document and text @@ -977,6 +1085,119 @@ public TextArea textArea() { return ta; } + + /** + * Grab current contents of the sketch window, advance the console, stop any + * other running sketches, auto-save the user's code... not in that order. + */ + @Override + public void prepareRun() { + autoSave(); + super.prepareRun(); + } + + /** + * Displays a JDialog prompting the user to save when the user hits + * run/present/etc. + */ + protected void autoSave() { + if (!ExperimentalMode.autoSaveEnabled) + return; + + try { + // if (sketch.isUntitled() && + // ExperimentalMode.untitledAutoSaveEnabled) { + // if (handleSave(true)) + // statusTimedNotice("Saved. Running...", 5); + // else + // statusTimedNotice("Save Canceled. Running anyway...", 5); + // } + // else + if (sketch.isModified() && !sketch.isUntitled()) { + if (ExperimentalMode.autoSavePromptEnabled) { + final JDialog autoSaveDialog = new JDialog( + base.getActiveEditor(), this.getSketch().getName(), + true); + Container container = autoSaveDialog.getContentPane(); + + JPanel panelMain = new JPanel(); + panelMain.setBorder(BorderFactory.createEmptyBorder(4, 0, + 2, 2)); + panelMain.setLayout(new BoxLayout(panelMain, + BoxLayout.PAGE_AXIS)); + + JPanel panelLabel = new JPanel(new FlowLayout( + FlowLayout.LEFT)); + JLabel label = new JLabel( + " There are unsaved" + + " changes in your sketch.
" + + "    Do you want to save it before" + + " running? "); + label.setFont(new Font(label.getFont().getName(), + Font.PLAIN, label.getFont().getSize() + 1)); + panelLabel.add(label); + panelMain.add(panelLabel); + final JCheckBox dontRedisplay = new JCheckBox( + "Remember this decision"); + + JPanel panelButtons = new JPanel(new FlowLayout( + FlowLayout.CENTER, 8, 2)); + JButton btnRunSave = new JButton("Save and Run"); + btnRunSave.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + handleSave(true); + if (dontRedisplay.isSelected()) { + ExperimentalMode.autoSavePromptEnabled = !dontRedisplay + .isSelected(); + ExperimentalMode.defaultAutoSaveEnabled = true; + dmode.savePreferences(); + } + autoSaveDialog.dispose(); + } + }); + panelButtons.add(btnRunSave); + JButton btnRunNoSave = new JButton("Run, Don't Save"); + btnRunNoSave.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (dontRedisplay.isSelected()) { + ExperimentalMode.autoSavePromptEnabled = !dontRedisplay + .isSelected(); + ExperimentalMode.defaultAutoSaveEnabled = false; + dmode.savePreferences(); + } + autoSaveDialog.dispose(); + } + }); + panelButtons.add(btnRunNoSave); + panelMain.add(panelButtons); + + JPanel panelCheck = new JPanel(); + panelCheck + .setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0)); + panelCheck.add(dontRedisplay); + panelMain.add(panelCheck); + + container.add(panelMain); + + autoSaveDialog.setResizable(false); + autoSaveDialog.pack(); + autoSaveDialog + .setLocationRelativeTo(base.getActiveEditor()); + autoSaveDialog.setVisible(true); + + } else if (ExperimentalMode.defaultAutoSaveEnabled) + handleSave(true); + } + } catch (Exception e) { + statusError(e); + } + + } + /** * Access variable inspector window. * @@ -1283,6 +1504,54 @@ public void statusHalted() { statusNotice("Debugger halted."); } + public static final int STATUS_EMPTY = 100, STATUS_COMPILER_ERR = 200, + STATUS_WARNING = 300, STATUS_INFO = 400, STATUS_ERR = 500; + public int statusMessageType = STATUS_EMPTY; + public String statusMessage; + public void statusMessage(final String what, int type){ + // Don't re-display the old message again + if(type != STATUS_EMPTY) { + if(what.equals(statusMessage) && type == statusMessageType) { + return; + } + } + statusMessage = new String(what); + statusMessageType = type; + switch (type) { + case STATUS_COMPILER_ERR: + case STATUS_ERR: + super.statusError(what); + break; + case STATUS_INFO: + case STATUS_WARNING: + statusNotice(what); + break; + } + // Don't need to clear compiler error messages + if(type == STATUS_COMPILER_ERR) return; + + // Clear the message after a delay + SwingWorker s = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + try { + Thread.sleep(2 * 1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + statusEmpty(); + return null; + } + }; + s.execute(); + } + + public void statusEmpty(){ + statusMessage = null; + statusMessageType = STATUS_EMPTY; + super.statusEmpty(); + } + ErrorCheckerService errorCheckerService; /** @@ -1335,17 +1604,27 @@ synchronized public boolean updateTable(final TableModel tableModel) { return errorTable.updateTable(tableModel); } + /** + * Handle whether the tiny red error indicator is shown near the error button + * at the bottom of the PDE + */ public void updateErrorToggle(){ btnShowErrors.updateMarker(errorCheckerService.hasErrors(), errorBar.errorColor); } + /** + * Handle refactor operation + */ private void handleRefactor() { log("Caret at:"); log(ta.getLineText(ta.getCaretLine())); errorCheckerService.getASTGenerator().handleRefactor(); } + /** + * Handle show usage operation + */ private void handleShowUsage() { log("Caret at:"); log(ta.getLineText(ta.getCaretLine())); diff --git a/src/processing/mode/experimental/ErrorCheckerService.java b/src/processing/mode/experimental/ErrorCheckerService.java index b6b18e6..61cc974 100644 --- a/src/processing/mode/experimental/ErrorCheckerService.java +++ b/src/processing/mode/experimental/ErrorCheckerService.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2012-14 Manindra Moharana + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + */ package processing.mode.experimental; import static processing.mode.experimental.ExperimentalMode.log; @@ -19,11 +36,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.swing.JCheckBoxMenuItem; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.table.DefaultTableModel; -import javax.swing.text.AbstractDocument; +import javax.swing.text.BadLocationException; +import javax.swing.text.Element; +import javax.swing.text.PlainDocument; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.compiler.IProblem; @@ -36,12 +54,19 @@ import processing.app.Base; import processing.app.Editor; +import processing.app.EditorStatus; import processing.app.Library; import processing.app.SketchCode; import processing.app.syntax.SyntaxDocument; import processing.core.PApplet; import processing.mode.java.preproc.PdePreprocessor; +/** + * The main error checking service + * + * @author Manindra Moharana <me@mkmoharana.com> + * + */ public class ErrorCheckerService implements Runnable{ protected DebugEditor editor; @@ -65,7 +90,7 @@ public class ErrorCheckerService implements Runnable{ */ protected AtomicBoolean pauseThread; - protected ErrorWindow errorWindow; + //protected ErrorWindow errorWindow; /** * IProblem[] returned by parser stored in here @@ -113,7 +138,17 @@ public class ErrorCheckerService implements Runnable{ /** * Compilation Unit for current sketch */ - protected CompilationUnit cu; + protected CompilationUnit cu; + + /** + * The Compilation Unit generated during compile check + */ + protected CompilationUnit compileCheckCU; + + /** + * This Compilation Unit points to the last error free CU + */ + protected CompilationUnit lastCorrectCU; /** * If true, compilation checker will be reloaded with updated classpath @@ -189,6 +224,7 @@ public class ErrorCheckerService implements Runnable{ protected ErrorMessageSimplifier errorMsgSimplifier; public ErrorCheckerService(DebugEditor debugEditor) { + ensureMinP5Version(); this.editor = debugEditor; stopThread = new AtomicBoolean(false); pauseThread = new AtomicBoolean(false); @@ -197,7 +233,7 @@ public ErrorCheckerService(DebugEditor debugEditor) { classpathJars = new ArrayList(); initParser(); - initializeErrorWindow(); + //initializeErrorWindow(); xqpreproc = new XQPreprocessor(); PdePreprocessor pdePrepoc = new PdePreprocessor(null); defaultImportsOffset = pdePrepoc.getCoreImports().length + @@ -233,7 +269,7 @@ protected void initParser() { /** * Initialiazes the Error Window */ - public void initializeErrorWindow() { + /*public void initializeErrorWindow() { if (editor == null) { return; @@ -258,6 +294,14 @@ public void run() { } } }); + }*/ + + public void ensureMinP5Version(){ + // Processing 2.1.2 - Revision 0225 + if(Base.getRevision() < 225){ +// System.err.println("ERROR: PDE X requires Processing 2.1.2 or higher."); + Base.showWarning("Error", "ERROR: PDE X requires Processing 2.1.2 or higher.", null); + } } public void run() { @@ -385,19 +429,24 @@ public void changedUpdate(DocumentEvent e) { } + public int compilationUnitState = 0; + protected boolean checkCode() { //log("checkCode() " + textModified.get() ); log("checkCode() " + textModified.get()); lastTimeStamp = System.currentTimeMillis(); try { sourceCode = preprocessCode(editor.getSketch().getMainProgram()); - + compilationUnitState = 0; syntaxCheck(); log(editor.getSketch().getName() + "1 MCO " + mainClassOffset); // No syntax errors, proceed for compilation check, Stage 2. //if(hasSyntaxErrors()) astGenerator.buildAST(null); + if (!hasSyntaxErrors()) { + + } if (problems.length == 0 && editor.compilationCheckEnabled) { //mainClassOffset++; // just a hack. @@ -410,12 +459,14 @@ protected boolean checkCode() { // } // log(sourceCode); // log("--------------------------"); - compileCheck(); - astGenerator.buildAST(cu); + compileCheck(); log(editor.getSketch().getName() + "2 MCO " + mainClassOffset); } + + astGenerator.buildAST(cu); if(ExperimentalMode.errorCheckEnabled){ + calcPDEOffsetsForProbList(); updateErrorTable(); editor.updateErrorBar(problemsList); updateEditorStatus(); @@ -479,7 +530,7 @@ protected void syntaxCheck() { cu = (CompilationUnit) parser.createAST(null); } } - + compilationUnitState = 1; synchronized (problemsList) { // Store errors returned by the ast parser @@ -488,9 +539,8 @@ protected void syntaxCheck() { // Populate the probList problemsList = new ArrayList(); for (int i = 0; i < problems.length; i++) { - int a[] = calculateTabIndexAndLineNumber(problems[i]); - Problem p = new Problem(problems[i], a[0], a[1] + 1); - //TODO: ^Why do cheeky stuff? + int a[] = calculateTabIndexAndLineNumber(problems[i].getSourceLineNumber()); + Problem p = new Problem(problems[i], a[0], a[1]); problemsList.add(p); // log(problems[i].getMessage()); // for (String j : problems[i].getArguments()) { @@ -502,6 +552,10 @@ protected void syntaxCheck() { if (problems.length == 0) { syntaxErrors.set(false); containsErrors.set(false); + parser.setSource(sourceCode.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + parser.setCompilerOptions(options); + lastCorrectCU = (CompilationUnit) parser.createAST(null); } else { syntaxErrors.set(true); containsErrors.set(true); @@ -512,7 +566,33 @@ protected void syntaxCheck() { protected URLClassLoader classLoader; protected void compileCheck() { + + // CU needs to be updated coz before compileCheck xqpreprocessor is run on + // the source code which makes some further changes + //TODO Check if this breaks things + + parser.setSource(sourceCode.toCharArray()); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + + Map options = JavaCore.getOptions(); + JavaCore.setComplianceOptions(JavaCore.VERSION_1_6, options); + options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_6); + options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED); + parser.setCompilerOptions(options); + + if (compileCheckCU == null) + compileCheckCU = (CompilationUnit) parser.createAST(null); + else { + synchronized (compileCheckCU) { + compileCheckCU = (CompilationUnit) parser.createAST(null); + } + } + if(!hasSyntaxErrors()) + lastCorrectCU = compileCheckCU; + cu = compileCheckCU; + + compilationUnitState = 2; // Currently (Sept, 2012) I'm using Java's reflection api to load the // CompilationChecker class(from CompilationChecker.jar) that houses the // Eclispe JDT compiler, and call its getErrorsAsObj method to obtain @@ -607,7 +687,10 @@ public boolean accept(File file) { ((Integer) errorList[i][4]).intValue(), ((Integer) errorList[i][5]).intValue(), ((Integer) errorList[i][6]).intValue(), - ((Integer) errorList[i][7]).intValue(), 0); + ((Integer) errorList[i][7]).intValue() - 1, 0); + // added a -1 to line number because in compile check code + // an extra package statement is added, so all line numbers + // are increased by 1 // System.out // .println("ECS: " + problems[i].getMessage() + "," @@ -619,7 +702,7 @@ public boolean accept(File file) { // for (String j : problem.getArguments()) { // log("arg " + j); // } - int a[] = calculateTabIndexAndLineNumber(problem); + int a[] = calculateTabIndexAndLineNumber(problem.getSourceLineNumber()); Problem p = new Problem(problem, a[0], a[1]); if ((Boolean) errorList[i][8]) { p.setType(Problem.ERROR); @@ -663,6 +746,90 @@ public boolean accept(File file) { // log("Compilecheck, Done."); } + /** + * Calculates PDE Offsets from Java Offsets for Problems + */ + private void calcPDEOffsetsForProbList() { + try { + PlainDocument javaSource = new PlainDocument(); + // Code in pde tabs stored as PlainDocument + PlainDocument pdeTabs[] = new PlainDocument[editor.getSketch() + .getCodeCount()]; + log("calcPDEOffsetsForProbList() mco: " + mainClassOffset + " CU state: " + + compilationUnitState); + + javaSource.insertString(0, sourceCode, null); + for (int i = 0; i < pdeTabs.length; i++) { + SketchCode sc = editor.getSketch().getCode(i); + pdeTabs[i] = new PlainDocument(); + if (editor.getSketch().getCurrentCode().equals(sc)) { + pdeTabs[i].insertString(0, + sc.getDocument().getText(0, + sc.getDocument() + .getLength()), + null); + } else { + pdeTabs[i].insertString(0, + sc.getProgram(), + null); + } + } + int pkgNameOffset = ("package " + className + ";\n").length(); + // package name is added only during compile check + if(compilationUnitState != 2) pkgNameOffset = 0; + + for (Problem p : problemsList) { + int prbStart = p.getIProblem().getSourceStart() - pkgNameOffset, prbEnd = p + .getIProblem().getSourceEnd() - pkgNameOffset; + log(p.toString()); + log("IProblem Start " + prbStart + ", End " + prbEnd); + int javaLineNumber = p.getIProblem().getSourceLineNumber() - 1; + Element lineElement = javaSource.getDefaultRootElement() + .getElement(javaLineNumber); + if (lineElement == null) { + log("calcPDEOffsetsForProbList(): Couldn't fetch javalinenum " + + javaLineNumber); + continue; + } + String javaLine = javaSource + .getText(lineElement.getStartOffset(), lineElement.getEndOffset() + - lineElement.getStartOffset()); + + Element pdeLineElement = pdeTabs[p.getTabIndex()] + .getDefaultRootElement().getElement(p.getLineNumber()); + if (pdeLineElement == null) { + log("calcPDEOffsetsForProbList(): Couldn't fetch pdelinenum " + + javaLineNumber); + continue; + } + String pdeLine = pdeTabs[p.getTabIndex()] + .getText(pdeLineElement.getStartOffset(), pdeLineElement.getEndOffset() + - pdeLineElement.getStartOffset()); + //log("calcPDEOffsetsForProbList(): P " + pdeLine); + //log("calcPDEOffsetsForProbList(): J " + javaLine); + OffsetMatcher ofm = new OffsetMatcher(pdeLine, javaLine); + //log(""); + int pdeOffset = ofm.getPdeOffForJavaOff(prbStart + - lineElement.getStartOffset(), (prbEnd - prbStart + 1)); +// astGenerator.highlightPDECode(p.getTabIndex(), p.getLineNumber(), +// pdeOffset, (prbEnd - prbStart + 1)); + p.setPDEOffsets(pdeOffset, pdeOffset + prbEnd - prbStart); + } + } catch (BadLocationException ble) { + ble.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public CompilationUnit getLastCorrectCU(){ + return lastCorrectCU; + } + + public CompilationUnit getLatestCU(){ + return compileCheckCU; + } + private int loadClassCounter = 0; public URLClassLoader getSketchClassLoader() { loadClassCounter++; @@ -843,26 +1010,29 @@ public void updateErrorTable() { String[][] errorData = new String[problemsList.size()][3]; for (int i = 0; i < problemsList.size(); i++) { errorData[i][0] = problemsList.get(i).getMessage(); ////TODO: this is temporary - //+ " : " + errorMsgSimplifier.getIDName(problemsList.get(i).getIProblem().getID()); + //+ " : " + errorMsgSimplifier.getIDName(problemsList.get(i).getIProblem().getID()); errorData[i][1] = editor.getSketch() .getCode(problemsList.get(i).getTabIndex()).getPrettyName(); - errorData[i][2] = problemsList.get(i).getLineNumber() + ""; - + errorData[i][2] = (problemsList.get(i).getLineNumber() + 1) + ""; + // Added +1 because lineNumbers internally are 0-indexed + //TODO: This is temporary - if(tempErrorLog.size() < 200) - tempErrorLog.put(problemsList.get(i).getMessage(),problemsList.get(i).getIProblem()); + if (tempErrorLog.size() < 200) + tempErrorLog.put(problemsList.get(i).getMessage(), problemsList + .get(i).getIProblem()); } + + DefaultTableModel tm = new DefaultTableModel(errorData, + XQErrorTable.columnNames); + // Update error table in the editor + editor.updateTable(tm); + /* if (errorWindow != null) { - DefaultTableModel tm = new DefaultTableModel(errorData, - XQErrorTable.columnNames); if (errorWindow.isVisible()) { errorWindow.updateTable(tm); } - // Update error table in the editor - editor.updateTable(tm); - // A rotating slash animation on the title bar to show // that error checker thread is running @@ -877,7 +1047,7 @@ public void updateErrorTable() { errorWindow.setTitle("Problems - " + editor.getSketch().getName() + " " + info); } - } + }*/ } catch (Exception e) { log("Exception at updateErrorTable() " + e); @@ -903,36 +1073,48 @@ public void updatePaintedThingys() { } } + protected int lastCaretLine = -1; + /** * Updates editor status bar, depending on whether the caret is on an error * line or not */ public void updateEditorStatus() { + + if(editor.getStatusMode() == EditorStatus.EDIT) return; // editor.statusNotice("Position: " + // editor.getTextArea().getCaretLine()); if(ExperimentalMode.errorCheckEnabled) synchronized (editor.errorBar.errorPoints) { for (ErrorMarker emarker : editor.errorBar.errorPoints) { if (emarker.getProblem().getLineNumber() == editor.getTextArea() - .getCaretLine() + 1) { + .getCaretLine()) { if (emarker.getType() == ErrorMarker.Warning) { - editor.statusNotice(emarker.getProblem().getMessage()); + editor.statusMessage(emarker.getProblem().getMessage(), + DebugEditor.STATUS_INFO); //+ " : " + errorMsgSimplifier.getIDName(emarker.problem.getIProblem().getID())); //TODO: this is temporary } else { - editor.statusError(emarker.getProblem().getMessage()); + editor.statusMessage(emarker.getProblem().getMessage(), + DebugEditor.STATUS_COMPILER_ERR); //+ " : " + errorMsgSimplifier.getIDName(emarker.problem.getIProblem().getID())); } return; } } } - if (editor.ta.getCaretLine() != lastCaretLine) { + + // This line isn't an error line anymore, so probably just clear it + if (editor.statusMessageType == DebugEditor.STATUS_COMPILER_ERR) { editor.statusEmpty(); - lastCaretLine = editor.ta.getCaretLine(); + return; } +// if (editor.ta.getCaretLine() != lastCaretLine) { +// editor.statusEmpty(); +// lastCaretLine = editor.ta.getCaretLine(); +// } } /** @@ -1032,16 +1214,16 @@ public String getPDECodeAtLine(int tab, int linenumber){ * - IProblem * @return int[0] - tab number, int[1] - line number */ - public int[] calculateTabIndexAndLineNumber(IProblem problem) { + public int[] calculateTabIndexAndLineNumber(int javalineNumber) { // String[] lines = {};// = PApplet.split(sourceString, '\n'); int codeIndex = 0; - int x = problem.getSourceLineNumber() - mainClassOffset; + int x = javalineNumber - mainClassOffset; if (x < 0) { // log("Negative line number " // + problem.getSourceLineNumber() + " , offset " // + mainClassOffset); - x = problem.getSourceLineNumber() - 2; // Another -1 for 0 index + x = javalineNumber - 2; // Another -1 for 0 index if (x < programImports.size() && x >= 0) { ImportStatement is = programImports.get(x); // log(is.importName + ", " + is.tab + ", " @@ -1105,6 +1287,16 @@ public int[] calculateTabIndexAndLineNumber(IProblem problem) { return new int[] { codeIndex, x }; } + + public int getJavaLineNumFromPDElineNum(int tab, int pdeLineNum){ + int jLineNum = programImports.size() + 1; + for (int i = 0; i < tab; i++) { + SketchCode sc = editor.getSketch().getCode(i); + int len = Base.countLines(sc.getProgram()) + 1; + jLineNum += len; + } + return jLineNum; + } /** * Fetches code from the editor tabs and pre-processes it into parsable pure @@ -1255,9 +1447,13 @@ protected String preprocessCode(String pdeCode) { * @return true - if highlighting happened correctly. */ public boolean highlightNode(ASTNodeWrapper awrap){ + log("Highlighting: " + awrap); try { int pdeoffsets[] = awrap.getPDECodeOffsets(this); int javaoffsets[] = awrap.getJavaCodeOffsets(this); + log("offsets: " +pdeoffsets[0] + "," + + pdeoffsets[1]+ "," +javaoffsets[1]+ "," + + javaoffsets[2]); scrollToErrorLine(editor, pdeoffsets[0], pdeoffsets[1],javaoffsets[1], javaoffsets[2]); @@ -1299,17 +1495,12 @@ public void scrollToErrorLine(Problem p) { if (p == null) return; try { - editor.toFront(); - editor.getSketch().setCurrentCode(p.getTabIndex()); - - editor - .setSelection(editor.getTextArea() - .getLineStartNonWhiteSpaceOffset(p.getLineNumber() - 1) - + editor.getTextArea() - .getLineText(p.getLineNumber() - 1).trim().length(), - editor.getTextArea() - .getLineStartNonWhiteSpaceOffset(p.getLineNumber() - 1)); - editor.getTextArea().scrollTo(p.getLineNumber() - 1, 0); + astGenerator.highlightPDECode(p.getTabIndex(), + p.getLineNumber(), + p.getPDELineStartOffset(), + (p.getPDELineStopOffset() + - p.getPDELineStartOffset() + 1)); + editor.getTextArea().scrollTo(p.getLineNumber(), 0); editor.repaint(); } catch (Exception e) { System.err.println(e @@ -1360,9 +1551,9 @@ public static boolean scrollToErrorLine(Editor edt, int tabIndex, int lineNoInTa * compiler classpath needs to be updated. */ protected void checkForChangedImports() { - log("Imports: " + programImports.size() + - " Prev Imp: " - + previousImports.size()); +// log("Imports: " + programImports.size() + +// " Prev Imp: " +// + previousImports.size()); if (programImports.size() != previousImports.size()) { // log(1); loadCompClass = true; diff --git a/src/processing/mode/experimental/ExperimentalMode.java b/src/processing/mode/experimental/ExperimentalMode.java index 9417f94..26cb99f 100755 --- a/src/processing/mode/experimental/ExperimentalMode.java +++ b/src/processing/mode/experimental/ExperimentalMode.java @@ -117,13 +117,27 @@ public File[] getKeywordFiles() { }; } + public File getContentFile(String path) { + // workaround for #45 + if (path.startsWith("application" + File.separator)) { + return new File(Base.getContentFile("modes" + File.separator + "java") + .getAbsolutePath() + File.separator + path); + } + return new File(folder, path); + } + volatile public static boolean errorCheckEnabled = true, warningsEnabled = true, - codeCompletionsEnabled = true, debugOutputEnabled = false, errorLogsEnabled = false; + codeCompletionsEnabled = true, debugOutputEnabled = false, errorLogsEnabled = false, + autoSaveEnabled = true, autoSavePromptEnabled = true, + defaultAutoSaveEnabled = true; // ,untitledAutoSaveEnabled; + public static int autoSaveInterval = 3; //in minutes - public final String prefErrorCheck = "pdex.errorCheckEnabled", + public static final String prefErrorCheck = "pdex.errorCheckEnabled", prefWarnings = "pdex.warningsEnabled", prefCodeCompletionEnabled = "pdex.ccEnabled", - prefDebugOP = "pdex.dbgOutput", prefErrorLogs = "pdex.writeErrorLogs"; + prefDebugOP = "pdex.dbgOutput", prefErrorLogs = "pdex.writeErrorLogs", prefAutoSaveInterval = "pdex.autoSaveInterval", + prefAutoSave = "pdex.autoSave.autoSaveEnabled", // prefUntitledAutoSave = "pdex.autoSave.untitledAutoSaveEnabled", + prefAutoSavePrompt = "pdex.autoSave.promptDisplay", prefDefaultAutoSave = "pdex.autoSave.autoSaveByDefault"; public void loadPreferences(){ log("Load PDEX prefs"); @@ -133,6 +147,11 @@ public void loadPreferences(){ codeCompletionsEnabled = Preferences.getBoolean(prefCodeCompletionEnabled); DEBUG = Preferences.getBoolean(prefDebugOP); errorLogsEnabled = Preferences.getBoolean(prefErrorLogs); + autoSaveInterval = Preferences.getInteger(prefAutoSaveInterval); +// untitledAutoSaveEnabled = Preferences.getBoolean(prefUntitledAutoSave); + autoSaveEnabled = Preferences.getBoolean(prefAutoSave); + autoSavePromptEnabled = Preferences.getBoolean(prefAutoSavePrompt); + defaultAutoSaveEnabled = Preferences.getBoolean(prefDefaultAutoSave); } public void savePreferences(){ @@ -142,6 +161,11 @@ public void savePreferences(){ Preferences.setBoolean(prefCodeCompletionEnabled, codeCompletionsEnabled); Preferences.setBoolean(prefDebugOP, DEBUG); Preferences.setBoolean(prefErrorLogs,errorLogsEnabled); + Preferences.setInteger(prefAutoSaveInterval,autoSaveInterval); +// Preferences.setBoolean(prefUntitledAutoSave,untitledAutoSaveEnabled); + Preferences.setBoolean(prefAutoSave,autoSaveEnabled); + Preferences.setBoolean(prefAutoSavePrompt, autoSavePromptEnabled); + Preferences.setBoolean(prefDefaultAutoSave, defaultAutoSaveEnabled); } public void ensurePrefsExist(){ @@ -155,6 +179,16 @@ public void ensurePrefsExist(){ Preferences.setBoolean(prefDebugOP,DEBUG); if(Preferences.get(prefErrorLogs) == null) Preferences.setBoolean(prefErrorLogs,errorLogsEnabled); + if(Preferences.get(prefAutoSaveInterval) == null) + Preferences.setInteger(prefAutoSaveInterval,autoSaveInterval); +// if(Preferences.get(prefUntitledAutoSave) == null) +// Preferences.setBoolean(prefUntitledAutoSave,untitledAutoSaveEnabled); + if(Preferences.get(prefAutoSave) == null) + Preferences.setBoolean(prefAutoSave,autoSaveEnabled); + if(Preferences.get(prefAutoSavePrompt) == null) + Preferences.setBoolean(prefAutoSavePrompt,autoSavePromptEnabled); + if(Preferences.get(prefDefaultAutoSave) == null) + Preferences.setBoolean(prefDefaultAutoSave,defaultAutoSaveEnabled); } @@ -253,4 +287,14 @@ public static final void log2(Object message){ if(ExperimentalMode.DEBUG) System.out.print(message); } + + public String[] getIgnorable() { + return new String[] { + "applet", + "application.macosx", + "application.windows", + "application.linux", + "_autosave" + }; + } } diff --git a/src/processing/mode/experimental/LineBreakpoint.java b/src/processing/mode/experimental/LineBreakpoint.java index 8d006fd..e35cea2 100755 --- a/src/processing/mode/experimental/LineBreakpoint.java +++ b/src/processing/mode/experimental/LineBreakpoint.java @@ -25,6 +25,10 @@ import java.util.logging.Level; import java.util.logging.Logger; +import static processing.mode.experimental.ExperimentalMode.log; +import static processing.mode.experimental.ExperimentalMode.logE; +import static processing.mode.experimental.ExperimentalMode.log2; + /** * Model/Controller of a line breakpoint. Can be set before or while debugging. * Adds a highlight using the debuggers view ({@link DebugEditor}). @@ -53,6 +57,7 @@ public LineBreakpoint(LineID line, Debugger dbg) { this.dbg = dbg; theClass = dbg.getClass(className()); // try to get the class immediately, may return null if not yet loaded set(); // activate the breakpoint (show highlight, attach if debugger is running) + Logger.getLogger(LineBreakpoint.class.getName()).log(Level.INFO, "LBP Created " +toString() + " class: " + className(), new Object[]{}); } /** @@ -108,6 +113,7 @@ protected void attach() { return; } try { + Logger.getLogger(LineBreakpoint.class.getName()).log(Level.WARNING, "BPs of class: {0} , line " + (javaLine.lineIdx() + 1), new Object[]{theClass}); List locations = theClass.locationsOfLine(javaLine.lineIdx() + 1); if (locations.isEmpty()) { Logger.getLogger(LineBreakpoint.class.getName()).log(Level.WARNING, "no location found for line {0} -> {1}", new Object[]{line, javaLine}); @@ -187,6 +193,7 @@ protected String className() { if (line.fileName().endsWith(".pde")) { // standard tab ReferenceType mainClass = dbg.getMainClass(); + //System.out.println(dbg.getMainClass().name()); if (mainClass == null) { return null; } @@ -210,9 +217,13 @@ protected String className() { @Override public void classLoaded(ReferenceType theClass) { // check if our class is being loaded + log("Class Loaded: " + theClass.name()); if (theClass.name().equals(className())) { this.theClass = theClass; attach(); } + for (ReferenceType ct : theClass.nestedTypes()) { + log("Nested " + ct.name()); + } } } diff --git a/src/processing/mode/experimental/OffsetMatcher.java b/src/processing/mode/experimental/OffsetMatcher.java new file mode 100644 index 0000000..af48dfe --- /dev/null +++ b/src/processing/mode/experimental/OffsetMatcher.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2012-14 Manindra Moharana + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package processing.mode.experimental; + +import java.util.ArrayList; +import static processing.mode.experimental.ExperimentalMode.log; + +/** + * Performs offset matching between PDE and Java code (one line of code only) + * + * @author Manindra Moharana + * + */ + +public class OffsetMatcher { + + public ArrayList offsetMatch; + + String pdeCodeLine, javaCodeLine; + + boolean matchingNeeded = false; + + public OffsetMatcher(String pdeCode, String javaCode) { + this.pdeCodeLine = pdeCode; + this.javaCodeLine = javaCode; + if(pdeCodeLine.trim().equals(javaCodeLine.trim())){ //TODO: trim() needed here? + matchingNeeded = false; + offsetMatch = new ArrayList(); + log("Offset Matching not needed"); + } + else + { + matchingNeeded = true; + minDistance(); + } + +// log("PDE <-> Java"); + for (int i = 0; i < offsetMatch.size(); i++) { +// log(offsetMatch.get(i).pdeOffset + " <-> " +// + offsetMatch.get(i).javaOffset + +// ", " + word1.charAt(offsetMatch.get(i).pdeOffset) +// + " <-> " + word2.charAt(offsetMatch.get(i).javaOffset)); + } +// log("Length " + offsetMatch.size()); + } + + public int getPdeOffForJavaOff(int start, int length) { + log("PDE :" + pdeCodeLine + "\nJAVA:" + javaCodeLine); + if(!matchingNeeded) return start; + int ans = getPdeOffForJavaOff(start), end = getPdeOffForJavaOff(start + length - 1); + log(start + " java start off, pde start off " + + ans); + log((start + length - 1) + " java end off, pde end off " + + end); + log("J: " + javaCodeLine.substring(start, start + length) + "\nP: " + + pdeCodeLine.substring(ans, end + 1)); + return ans; + } + + public int getJavaOffForPdeOff(int start, int length) { + if(!matchingNeeded) return start; + int ans = getJavaOffForPdeOff(start); + log(start + " pde start off, java start off " + + getJavaOffForPdeOff(start)); + log((start + length - 1) + " pde end off, java end off " + + getJavaOffForPdeOff(start + length - 1)); + return ans; + } + + public int getPdeOffForJavaOff(int javaOff) { + if(!matchingNeeded) return javaOff; + for (int i = offsetMatch.size() - 1; i >= 0; i--) { + if (offsetMatch.get(i).javaOffset < javaOff) { + continue; + } else if (offsetMatch.get(i).javaOffset == javaOff) { +// int j = i; + while (offsetMatch.get(--i).javaOffset == javaOff) { + log("MP " + offsetMatch.get(i).javaOffset + " " + + offsetMatch.get(i).pdeOffset); + } + int pdeOff = offsetMatch.get(++i).pdeOffset; + while (offsetMatch.get(--i).pdeOffset == pdeOff) + ; + int j = i + 1; + if (j > -1 && j < offsetMatch.size()) + return offsetMatch.get(j).pdeOffset; + } + + } + return -1; + } + + public int getJavaOffForPdeOff(int pdeOff) { + if(!matchingNeeded) return pdeOff; + for (int i = offsetMatch.size() - 1; i >= 0; i--) { + if (offsetMatch.get(i).pdeOffset < pdeOff) { + continue; + } else if (offsetMatch.get(i).pdeOffset == pdeOff) { +// int j = i; + while (offsetMatch.get(--i).pdeOffset == pdeOff) { +// log("MP " + offsetMatch.get(i).javaOffset + " " +// + offsetMatch.get(i).pdeOffset); + } + int javaOff = offsetMatch.get(++i).javaOffset; + while (offsetMatch.get(--i).javaOffset == javaOff) + ; + int j = i + 1; + if (j > -1 && j < offsetMatch.size()) + return offsetMatch.get(j).javaOffset; + } + + } + return -1; + } + + /** + * Finds 'distance' between two Strings. + * See Edit Distance Problem + * https://secweb.cs.odu.edu/~zeil/cs361/web/website/Lectures/styles/pages/editdistance.html + * http://www.stanford.edu/class/cs124/lec/med.pdf + * + */ + private int minDistance() { + +// word1 = reverse(word1); +// word2 = reverse(word2); + int len1 = pdeCodeLine.length(); + int len2 = javaCodeLine.length(); + log(pdeCodeLine + " len: " + len1); + log(javaCodeLine + " len: " + len2); + // len1+1, len2+1, because finally return dp[len1][len2] + int[][] dp = new int[len1 + 1][len2 + 1]; + + for (int i = 0; i <= len1; i++) { + dp[i][0] = i; + } + + for (int j = 0; j <= len2; j++) { + dp[0][j] = j; + } + + //iterate though, and check last char + for (int i = 0; i < len1; i++) { + char c1 = pdeCodeLine.charAt(i); + for (int j = 0; j < len2; j++) { + char c2 = javaCodeLine.charAt(j); + //System.out.print(c1 + "<->" + c2); + //if last two chars equal + if (c1 == c2) { + //update dp value for +1 length + dp[i + 1][j + 1] = dp[i][j]; +// log(); + } else { + int replace = dp[i][j] + 1; + int insert = dp[i][j + 1] + 1; + int delete = dp[i + 1][j] + 1; +// if (replace < delete) { +// log(" --- D"); +// } else +// log(" --- R"); + int min = replace > insert ? insert : replace; + min = delete > min ? min : delete; + dp[i + 1][j + 1] = min; + } + } + } + + ArrayList alist = new ArrayList(); + offsetMatch = alist; + minDistInGrid(dp, len1, len2, 0, 0, pdeCodeLine.toCharArray(), + javaCodeLine.toCharArray(), alist); + return dp[len1][len2]; + } + + private void minDistInGrid(int g[][], int i, int j, int fi, int fj, + char s1[], char s2[], ArrayList set) { +// if(i < s1.length)System.out.print(s1[i] + " <->"); +// if(j < s2.length)System.out.print(s2[j]); + if (i < s1.length && j < s2.length) { +// pdeCodeMap[k] = i; +// javaCodeMap[k] = j; + //System.out.print(s1[i] + " " + i + " <-> " + j + " " + s2[j]); + set.add(new OffsetPair(i, j)); +// if (s1[i] != s2[j]) +// System.out.println("--"); + } + //System.out.println(); + if (i == fi && j == fj) { + //System.out.println("Reached end."); + } else { + int a = Integer.MAX_VALUE, b = a, c = a; + if (i > 0) + a = g[i - 1][j]; + if (j > 0) + b = g[i][j - 1]; + if (i > 0 && j > 0) + c = g[i - 1][j - 1]; + int mini = Math.min(a, Math.min(b, c)); + if (mini == a) { + //System.out.println(s1[i + 1] + " " + s2[j]); + minDistInGrid(g, i - 1, j, fi, fj, s1, s2, set); + } else if (mini == b) { + //System.out.println(s1[i] + " " + s2[j + 1]); + minDistInGrid(g, i, j - 1, fi, fj, s1, s2, set); + } else if (mini == c) { + //System.out.println(s1[i + 1] + " " + s2[j + 1]); + minDistInGrid(g, i - 1, j - 1, fi, fj, s1, s2, set); + } + } + } + + private class OffsetPair { + public final int pdeOffset, javaOffset; + + public OffsetPair(int pde, int java) { + pdeOffset = pde; + javaOffset = java; + } + } + + public static void main(String[] args) { +// minDistance("c = #qwerty;", "c = 0xffqwerty;"); + OffsetMatcher a; + +// a = new OffsetMatcher("int a = int(can); int ball;", +// "int a = PApplet.parseInt(can); int ball;"); +// a.getPdeOffForJavaOff(25, 3); +// a.getJavaOffForPdeOff(12, 3); +// minDistance("static void main(){;", "public static void main(){;"); +// minDistance("#bb00aa", "0xffbb00aa"); + a = new OffsetMatcher("void test(ArrayList boids){", + "public void test(ArrayList boids){"); + a.getJavaOffForPdeOff(20,4); + log("--"); +// a = new OffsetMatcher("color abc = #qwerty;", "int abc = 0xffqwerty;"); +// a.getPdeOffForJavaOff(4, 3); +// a.getJavaOffForPdeOff(6, 3); +// distance("c = #bb00aa;", "c = 0xffbb00aa;"); + } +} diff --git a/src/processing/mode/experimental/Problem.java b/src/processing/mode/experimental/Problem.java index aaa8cca..4f7e9c4 100644 --- a/src/processing/mode/experimental/Problem.java +++ b/src/processing/mode/experimental/Problem.java @@ -49,6 +49,10 @@ public class Problem { * Line number(pde code) of the error */ private int lineNumber; + + private int lineStartOffset; + + private int lineStopOffset; /** * Error Message. Processed form of IProblem.getMessage() @@ -80,10 +84,24 @@ else if(iProblem.isWarning()) { this.lineNumber = lineNumber; this.message = process(iProblem); } + + public void setPDEOffsets(int startOffset, int stopOffset){ + lineStartOffset = startOffset; + lineStopOffset = stopOffset; + } + + public int getPDELineStartOffset(){ + return lineStartOffset; + } + + public int getPDELineStopOffset(){ + return lineStopOffset; + } public String toString() { - return new String("TAB " + tabIndex + ",LN " + lineNumber + ",PROB: " - + message); + return new String("TAB " + tabIndex + ",LN " + lineNumber + "LN START OFF: " + + lineStartOffset + ",LN STOP OFF: " + lineStopOffset + ",PROB: " + + message); } public boolean isError(){ diff --git a/src/processing/mode/experimental/SketchOutline.java b/src/processing/mode/experimental/SketchOutline.java index 6577adf..13cb729 100644 --- a/src/processing/mode/experimental/SketchOutline.java +++ b/src/processing/mode/experimental/SketchOutline.java @@ -67,7 +67,8 @@ public SketchOutline(DefaultMutableTreeNode codeTree, ErrorCheckerService ecs) { //TODO: ^Absolute dimensions are bad bro - int minWidth = 200; + int minWidth = (int) (editor.getMinimumSize().width * 0.7f), + maxWidth = (int) (editor.getMinimumSize().width * 0.9f); frmOutlineView.setLayout(new BoxLayout(frmOutlineView.getContentPane(), BoxLayout.Y_AXIS)); JPanel panelTop = new JPanel(), panelBottom = new JPanel(); @@ -95,19 +96,20 @@ public SketchOutline(DefaultMutableTreeNode codeTree, ErrorCheckerService ecs) { jsp.setViewportView(soTree); jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - jsp.setMinimumSize(new Dimension(minWidth, 100)); + jsp.setMinimumSize(new Dimension(minWidth, editor.ta.getHeight() - 10)); + jsp.setMaximumSize(new Dimension(maxWidth, editor.ta.getHeight() - 10)); panelBottom.add(jsp); frmOutlineView.add(panelTop); frmOutlineView.add(panelBottom); frmOutlineView.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frmOutlineView.pack(); frmOutlineView.setBounds(tp.x + errorCheckerService.getEditor().ta .getWidth() - minWidth, tp.y, minWidth, - Math.min(editor.ta.getHeight(), 150)); + Math.min(editor.ta.getHeight(), frmOutlineView.getHeight())); frmOutlineView.setMinimumSize(new Dimension(minWidth, Math - .min(errorCheckerService.getEditor().ta.getHeight(), 150))); - frmOutlineView.pack(); + .min(errorCheckerService.getEditor().ta.getHeight(), frmOutlineView.getHeight()))); frmOutlineView.setLocation(tp.x + errorCheckerService.getEditor().ta .getWidth() - frmOutlineView.getWidth(), diff --git a/src/processing/mode/experimental/TextArea.java b/src/processing/mode/experimental/TextArea.java index 8025f96..b0971b3 100644 --- a/src/processing/mode/experimental/TextArea.java +++ b/src/processing/mode/experimental/TextArea.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Martin Leopold + * Copyright (C) 2012-14 Martin Leopold and Manindra Moharana * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software @@ -127,6 +127,10 @@ public void setECSandThemeforTextArea(ErrorCheckerService ecs, customPainter.setECSandTheme(ecs, mode); } + /** + * Handles KeyEvents for TextArea + * Code completion begins from here. + */ public void processKeyEvent(KeyEvent evt) { if(evt.getKeyCode() == KeyEvent.VK_ESCAPE){ @@ -201,11 +205,12 @@ public void processKeyEvent(KeyEvent evt) { final KeyEvent evt2 = evt; SwingWorker worker = new SwingWorker() { protected Object doInBackground() throws Exception { + log("[KeyEvent]" + evt2.getKeyChar() + " |Prediction started: " + System.currentTimeMillis()); errorCheckerService.runManualErrorCheck(); // Provide completions only if it's enabled if(ExperimentalMode.codeCompletionsEnabled) - log(" Typing: " + fetchPhrase(evt2) + " " - + (evt2.getKeyChar() == KeyEvent.VK_ENTER)); + log("Typing: " + fetchPhrase(evt2) + " " + + (evt2.getKeyChar() == KeyEvent.VK_ENTER) + " T: " + System.currentTimeMillis()); return null; } }; @@ -215,7 +220,11 @@ protected Object doInBackground() throws Exception { } - + /** + * Retrieves the word on which the mouse pointer is present + * @param evt - the MouseEvent which triggered this method + * @return + */ private String fetchPhrase(MouseEvent evt) { log("--handle Mouse Right Click--"); int off = xyToOffset(evt.getX(), evt.getY()); @@ -247,6 +256,7 @@ else if (s.length() == 0) if (x1 >= 0 && x1 < s.length()) { if (Character.isLetter(s.charAt(x1)) || s.charAt(x1) == '_') { word = s.charAt(x1--) + word; + xLS--; } else x1 = -1; } else @@ -271,11 +281,18 @@ else if (s.length() == 0) if (Character.isDigit(word.charAt(0))) return null; log("Mouse click, word: " + word.trim()); - errorCheckerService.getASTGenerator().setLastClickedWord(line - + errorCheckerService.mainClassOffset, word, xLS); + errorCheckerService.getASTGenerator().setLastClickedWord(line, word, xLS); return word.trim(); } } + + /** + * Retrieves the current word typed just before the caret. + * Then triggers code completion for that word. + * + * @param evt - the KeyEvent which triggered this method + * @return + */ private String fetchPhrase(KeyEvent evt) { int off = getCaretPosition(); @@ -361,29 +378,29 @@ else if (s.charAt(x1) == ']') { break; } -// if (x2 >= 0 && x2 < s.length()) { -// if (Character.isLetterOrDigit(s.charAt(x2)) || s.charAt(x2) == '_' -// || s.charAt(x2) == '$') -// word = word + s.charAt(x2++); -// else -// x2 = -1; -// } else -// x2 = -1; - -// if (x1 < 0 )//&& x2 < 0 -// break; + // if (x2 >= 0 && x2 < s.length()) { + // if (Character.isLetterOrDigit(s.charAt(x2)) || s.charAt(x2) == '_' + // || s.charAt(x2) == '$') + // word = word + s.charAt(x2++); + // else + // x2 = -1; + // } else + // x2 = -1; + + // if (x1 < 0 )//&& x2 < 0 + // break; if (i > 200) { // time out! break; } } -// if (keyChar != KeyEvent.CHAR_UNDEFINED) + // if (keyChar != KeyEvent.CHAR_UNDEFINED) if (Character.isDigit(word.charAt(0))) return null; word = word.trim(); -// if (word.endsWith(".")) -// word = word.substring(0, word.length() - 1); + // if (word.endsWith(".")) + // word = word.substring(0, word.length() - 1); int lineStartNonWSOffset = 0; if(word.length() > 1) errorCheckerService.getASTGenerator().preparePredictions(word, line @@ -664,7 +681,7 @@ public void mouseMoved(MouseEvent me) { //JEditTextArea textarea; - // worthless + /* No longer used private void addCompletionPopupListner() { this.addKeyListener(new KeyListener() { @@ -695,7 +712,7 @@ public void keyReleased(KeyEvent e) { public void keyPressed(KeyEvent e) { } }); - } + }*/ public void showSuggestionLater(final DefaultListModel defListModel, final String word) { SwingUtilities.invokeLater(new Runnable() { @@ -707,6 +724,12 @@ public void run() { }); } + /** + * Calculates location of caret and displays the suggestion popup at the location. + * + * @param defListModel + * @param subWord + */ protected void showSuggestion(DefaultListModel defListModel,String subWord) { hideSuggestion(); if (defListModel.size() == 0) { @@ -745,6 +768,9 @@ protected void showSuggestion(DefaultListModel defListModel,String subWord) { // }); } + /** + * Hides suggestion popup + */ protected void hideSuggestion() { if (suggestion != null) { suggestion.hide(); diff --git a/src/processing/mode/experimental/TextAreaPainter.java b/src/processing/mode/experimental/TextAreaPainter.java index 4e8b888..4961510 100644 --- a/src/processing/mode/experimental/TextAreaPainter.java +++ b/src/processing/mode/experimental/TextAreaPainter.java @@ -116,6 +116,7 @@ else if (s.length() == 0) if (x1 >= 0 && x1 < s.length()) { if (Character.isLetter(s.charAt(x1)) || s.charAt(x1) == '_') { word = s.charAt(x1--) + word; + xLS--; } else x1 = -1; } else @@ -140,11 +141,11 @@ else if (s.length() == 0) } if (Character.isDigit(word.charAt(0))) return; - + log(errorCheckerService.mainClassOffset + line + "|" + line + "| offset " + xLS + word + " <= \n"); - errorCheckerService.getASTGenerator().scrollToDeclaration(line - + errorCheckerService.mainClassOffset, word, xLS); + errorCheckerService.getASTGenerator() + .scrollToDeclaration(line, word, xLS); } } @@ -313,15 +314,18 @@ protected void paintErrorLine(Graphics gfx, int line, int x) { boolean notFound = true; boolean isWarning = false; - + Problem problem = null; + // Check if current line contains an error. If it does, find if it's an // error or warning for (ErrorMarker emarker : errorCheckerService.getEditor().errorBar.errorPoints) { - if (emarker.getProblem().getLineNumber() == line + 1) { + if (emarker.getProblem().getLineNumber() == line) { notFound = false; if (emarker.getType() == ErrorMarker.Warning) { isWarning = true; } + problem = emarker.getProblem(); + //log(problem.toString()); break; } } @@ -336,15 +340,19 @@ protected void paintErrorLine(Graphics gfx, int line, int x) { int y = ta.lineToY(line); y += fm.getLeading() + fm.getMaxDescent(); int height = fm.getHeight(); - int start = ta.getLineStartOffset(line); - + int start = ta.getLineStartOffset(line) + problem.getPDELineStartOffset(); + int pLength = problem.getPDELineStopOffset() + 1 + - problem.getPDELineStartOffset(); + try { - String linetext = null; - + String badCode = null; + String goodCode = null; try { - linetext = ta.getDocument().getText(start, - ta.getLineStopOffset(line) - start - - 1); + badCode = ta.getDocument().getText(start, pLength); + goodCode = ta.getDocument().getText(ta.getLineStartOffset(line), + problem.getPDELineStartOffset()); + //log("paintErrorLine() LineText GC: " + goodCode); + //log("paintErrorLine() LineText BC: " + badCode); } catch (BadLocationException bl) { // Error in the import statements or end of code. // System.out.print("BL caught. " + ta.getLineCount() + " ," @@ -354,11 +362,12 @@ protected void paintErrorLine(Graphics gfx, int line, int x) { } // Take care of offsets - int aw = fm.stringWidth(trimRight(linetext)) + ta.getHorizontalOffset(); // apparent width. Whitespaces + int aw = fm.stringWidth(trimRight(badCode)) + ta.getHorizontalOffset(); // apparent width. Whitespaces // to the left of line + text // width - int rw = fm.stringWidth(linetext.trim()); // real width - int x1 = 0 + (aw - rw), y1 = y + fm.getHeight() - 2, x2 = x1 + rw; + int rw = fm.stringWidth(badCode.trim()); // real width + int x1 = fm.stringWidth(goodCode) + (aw - rw), y1 = y + fm.getHeight() + - 2, x2 = x1 + rw; // Adding offsets for the gutter x1 += ta.getGutterWidth(); x2 += ta.getGutterWidth(); @@ -457,6 +466,7 @@ else if (s.length() == 0) if (x1 >= 0 && x1 < s.length()) { if (Character.isLetter(s.charAt(x1)) || s.charAt(x1) == '_') { word = s.charAt(x1--) + word; + xLS--; } else x1 = -1; } else @@ -482,8 +492,7 @@ else if (s.length() == 0) if (Character.isDigit(word.charAt(0))) return null; String tooltipText = errorCheckerService.getASTGenerator() - .getLabelForASTNode(line + errorCheckerService.mainClassOffset, word, - xLS); + .getLabelForASTNode(line, word, xLS); log(errorCheckerService.mainClassOffset + " MCO " + "|" + line + "| offset " + xLS + word + " <= offf: "+off+ "\n"); diff --git a/src/processing/mode/experimental/Utils.java b/src/processing/mode/experimental/Utils.java new file mode 100644 index 0000000..7992676 --- /dev/null +++ b/src/processing/mode/experimental/Utils.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2012-14 Manindra Moharana + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package processing.mode.experimental; + +import java.util.ArrayList; + +/** + * A class containing multiple utility methods + * + * @author Manindra Moharana + * + */ + +public class Utils { + + public ArrayList offsetMatch; + String word1, word2; + public static String reverse(String s) { + char w[] = s.toCharArray(); + for (int i = 0; i < w.length / 2; i++) { + char t = w[i]; + w[i] = w[w.length - 1 - i]; + w[w.length - 1 - i] = t; + } + return new String(w); + } + + public void getPdeOffForJavaOff(int start, int length){ + System.out.println("PDE <-> Java" ); + for (int i = 0; i < offsetMatch.size(); i++) { + System.out.print(offsetMatch.get(i).pdeOffset + " <-> " + offsetMatch.get(i).javaOffset); + System.out.println(", " + word1.charAt(offsetMatch.get(i).pdeOffset) + " <-> " + + word2.charAt(offsetMatch.get(i).javaOffset)); + } + System.out.println("Length " + offsetMatch.size()); + System.out.println(start + " java start off, pde start off " + + getPdeOffForJavaOff(start)); + System.out.println((start + length - 1) + " java end off, pde end off " + + getPdeOffForJavaOff(start + length - 1)); + } + + public void getJavaOffForPdeOff(int start, int length){ +// System.out.println("PDE <-> Java" ); +// for (int i = 0; i < offsetMatch.size(); i++) { +// System.out.print(offsetMatch.get(i).pdeOffset + " <-> " + offsetMatch.get(i).javaOffset); +// System.out.println(", " + word1.charAt(offsetMatch.get(i).pdeOffset) + " <-> " +// + word2.charAt(offsetMatch.get(i).javaOffset)); +// } +// System.out.println("Length " + offsetMatch.size()); + System.out.println(start + " pde start off, java start off " + + getJavaOffForPdeOff(start)); + System.out.println((start + length - 1) + " pde end off, java end off " + + getJavaOffForPdeOff(start + length - 1)); + } + + public int getPdeOffForJavaOff(int javaOff){ + for (int i = offsetMatch.size() - 1; i >= 0;i--) { + if(offsetMatch.get(i).javaOffset < javaOff){ + continue; + } + else + if(offsetMatch.get(i).javaOffset == javaOff){ +// int j = i; + while(offsetMatch.get(--i).javaOffset == javaOff){ + System.out.println("MP " + offsetMatch.get(i).javaOffset + " " + + offsetMatch.get(i).pdeOffset); + } + int pdeOff = offsetMatch.get(++i).pdeOffset; + while(offsetMatch.get(--i).pdeOffset == pdeOff); + int j = i + 1; + if (j > -1 && j < offsetMatch.size()) + return offsetMatch.get(j).pdeOffset; + } + + } + return -1; + } + + public int getJavaOffForPdeOff(int pdeOff){ + for (int i = offsetMatch.size() - 1; i >= 0;i--) { + if(offsetMatch.get(i).pdeOffset < pdeOff){ + continue; + } + else + if(offsetMatch.get(i).pdeOffset == pdeOff){ +// int j = i; + while(offsetMatch.get(--i).pdeOffset == pdeOff){ +// System.out.println("MP " + offsetMatch.get(i).javaOffset + " " +// + offsetMatch.get(i).pdeOffset); + } + int javaOff = offsetMatch.get(++i).javaOffset; + while(offsetMatch.get(--i).javaOffset == javaOff); + int j = i + 1; + if (j > -1 && j < offsetMatch.size()) + return offsetMatch.get(j).javaOffset; + } + + } + return -1; + } + + public int minDistance(String word1, String word2) { + this.word1 = word1; + this.word2 = word2; +// word1 = reverse(word1); +// word2 = reverse(word2); + int len1 = word1.length(); + int len2 = word2.length(); + System.out.println(word1 + " len: " + len1); + System.out.println(word2 + " len: " + len2); + // len1+1, len2+1, because finally return dp[len1][len2] + int[][] dp = new int[len1 + 1][len2 + 1]; + + for (int i = 0; i <= len1; i++) { + dp[i][0] = i; + } + + for (int j = 0; j <= len2; j++) { + dp[0][j] = j; + } + + //iterate though, and check last char + for (int i = 0; i < len1; i++) { + char c1 = word1.charAt(i); + for (int j = 0; j < len2; j++) { + char c2 = word2.charAt(j); + //System.out.print(c1 + "<->" + c2); + //if last two chars equal + if (c1 == c2) { + //update dp value for +1 length + dp[i + 1][j + 1] = dp[i][j]; +// System.out.println(); + } else { + int replace = dp[i][j] + 1; + int insert = dp[i][j + 1] + 1; + int delete = dp[i + 1][j] + 1; +// if (replace < delete) { +// System.out.println(" --- D"); +// } else +// System.out.println(" --- R"); + int min = replace > insert ? insert : replace; + min = delete > min ? min : delete; + dp[i + 1][j + 1] = min; + } + } + } + +// for (int i = 0; i < dp.length; i++) { +// for (int j = 0; j < dp[0].length; j++) { +// System.out.print(dp[i][j] + " "); +// } +// System.out.println(); +// } +// int maxLen = Math.max(len1, len2)+2; +// int pdeCodeMap[] = new int[maxLen], javaCodeMap[] = new int[maxLen]; +// System.out.println("Edit distance1: " + dp[len1][len2]); + ArrayList alist = new ArrayList(); + offsetMatch = alist; + minDistInGrid(dp, len1, len2, 0, 0, word1.toCharArray(), + word2.toCharArray(), alist); +// System.out.println("PDE-to-Java"); +// for (int i = 0; i < maxLen; i++) { +// System.out.print(pdeCodeMap[i] + " <-> " + javaCodeMap[i]); +// System.out.println(", " + word1.charAt(pdeCodeMap[i]) + " <-> " +// + word2.charAt(javaCodeMap[i])); +// } +// for (int i = 0; i < alist.size(); i++) { +// System.out.print(alist.get(i).pdeOffset + " <-> " + alist.get(i).javaOffset); +// System.out.println(", " + word1.charAt(alist.get(i).pdeOffset) + " <-> " +// + word2.charAt(alist.get(i).javaOffset)); +// } +// System.out.println("Length " + alist.size()); + return dp[len1][len2]; + } + + public static int distance(String a, String b) { +// a = a.toLowerCase(); +// b = b.toLowerCase(); + + // i == 0 + int[] costs = new int[b.length() + 1]; + for (int j = 0; j < costs.length; j++) + costs[j] = j; + for (int i = 1; i <= a.length(); i++) { + // j == 0; nw = lev(i - 1, j) + costs[0] = i; + int nw = i - 1; + for (int j = 1; j <= b.length(); j++) { + int cj = Math.min(1 + Math.min(costs[j], costs[j - 1]), + a.charAt(i - 1) == b.charAt(j - 1) ? nw : nw + 1); + nw = costs[j]; + costs[j] = cj; + } + } + System.out.println("Edit distance2: " + costs[b.length()]); + return costs[b.length()]; + } + + public void minDistInGrid(int g[][], int i, int j, int fi, int fj, + char s1[], char s2[], ArrayList set) { +// if(i < s1.length)System.out.print(s1[i] + " <->"); +// if(j < s2.length)System.out.print(s2[j]); + if (i < s1.length && j < s2.length) { +// pdeCodeMap[k] = i; +// javaCodeMap[k] = j; + //System.out.print(s1[i] + " " + i + " <-> " + j + " " + s2[j]); + set.add(new OfsSetTemp(i, j)); +// if (s1[i] != s2[j]) +// System.out.println("--"); + } + //System.out.println(); + if (i == fi && j == fj) { + //System.out.println("Reached end."); + } else { + int a = Integer.MAX_VALUE, b = a, c = a; + if (i > 0) + a = g[i - 1][j]; + if (j > 0) + b = g[i][j - 1]; + if (i > 0 && j > 0) + c = g[i - 1][j - 1]; + int mini = Math.min(a, Math.min(b, c)); + if (mini == a) { + //System.out.println(s1[i + 1] + " " + s2[j]); + minDistInGrid(g, i - 1, j, fi, fj, s1, s2,set); + } else if (mini == b) { + //System.out.println(s1[i] + " " + s2[j + 1]); + minDistInGrid(g, i, j - 1, fi, fj, s1, s2, set); + } else if (mini == c) { + //System.out.println(s1[i + 1] + " " + s2[j + 1]); + minDistInGrid(g, i - 1, j - 1, fi, fj, s1, s2, set); + } + } + } + + public class OfsSetTemp { + public final int pdeOffset, javaOffset; + public OfsSetTemp(int pde, int java){ + pdeOffset = pde; + javaOffset = java; + } + } + +// public class OffsetMatch{ +// public final ArrayList pdeOffset, javaOffset; +// +// public OffsetMatch(){ +// pdeOffset = new ArrayList(); +// javaOffset = new ArrayList(); +// } +// } + + public static void main(String[] args) { +// minDistance("c = #qwerty;", "c = 0xffqwerty;"); + Utils a = new Utils(); + + a.minDistance("int a = int(can); int ball;", "int a = PApplet.parseInt(can); int ball;"); + a.getPdeOffForJavaOff(25, 3); + a.getJavaOffForPdeOff(12,3); +// minDistance("static void main(){;", "public static void main(){;"); +// minDistance("#bb00aa", "0xffbb00aa"); + //a.minDistance("color g = #qwerty;", "int g = 0xffqwerty;"); + System.out.println("--"); + a.minDistance("color abc = #qwerty;", "int abc = 0xffqwerty;"); + a.getPdeOffForJavaOff(4, 3); + a.getJavaOffForPdeOff(6,3); +// distance("c = #bb00aa;", "c = 0xffbb00aa;"); + } +} diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..c177853 --- /dev/null +++ b/todo.txt @@ -0,0 +1,50 @@ +TODO List for PDE X +=================== + +This would also be a break down of my thought process and ideas as I tackle +various tasks. Previously, a different todo file was used for GSoC 2013. + +Manindra Moharana (me@mkmoharana.com) + +[ ]: Todo, [x] : Done, ? : Undecided Todo, ! : Critical, + : Minor Todo + + +Critical Bugs +------------- + +-[ ] Better memory management. #1 + +-[ ] Breakpoints in classes. #47 + + +Normal Bugs +----------- +-[x] Sketch NOC 6_09: steer PVector, doesn't show completion. #68 + +-[x] Sketch NOC 6_09: Classname in Template, doesn't scroll to decl. This is +happening due certain post processing offsets not being accounted for - "public void" + +-[x] New offset matching now used by Show Usage + +-[x] New offset matching now used by Refactoring + +Enhancements/New Features +------------------------- + +-[x] Precise error highlighting(PEH). Now working for one error per line. Hell yeah! + +-[ ] Gotta fix PEH for multiple errors per line. Will be slightly meticulous. + +-[ ] When viewing Outline View, instead of showing the beginning of the list, +it should select the current node element within which the cursor is presently +positioned. + +-[ ] Begin work on code snippets. + +-[ ] JUnit Testing? + +-[ ] Preferences panel + +-[ ] Line Numbers + +