diff --git a/.gitignore b/.gitignore index 60f6819..e79cb31 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ bin mode/ExperimentalMode.jar dist +build.properties \ No newline at end of file diff --git a/README.md b/README.md index 4b944de..a49be85 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,30 @@ -Experimental Mode for the PDE -========================= +**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. -This project is in the process of being moved out of the main project so that it can be developed in parallel with the PDE. Right now, it's in a messy state and not stable, forking isn't recommened. +PDE X +===== -Stay tuned for updates. +PDE X is a [Processing](http://processing.org/) Mode that brings powerful new features to the Processing Development Environment: + +* Intelligent Code Completion +* Quick Renaming(Refactoring) +* Quick Navigation +* Import Suggestions +* Live Error Checker +* Integrated Debugger + +Find out more at [Getting Started](https://github.com/processing/processing-experimental/wiki/Getting-Started). Or checkout the [FAQ](https://github.com/processing/processing-experimental/wiki/FAQ) + +####How to Install + +![Mode Button](http://i.imgur.com/cag1y10.png) + +Click on the down arrow besides the mode switch button, and select Add Mode. In the Mode Manager window, select PDE X and click 'Install'. You'll need Processing 2.0.2 or higher. + +For installing it manually, download the latest version from [here](http://download.processing.org/pdeX.zip). Extract the zip contents into `/modes` folder. Restart Processing. Manindra Moharana -29 June 2013 +21 October 2013 + +-- +PDE X is supported by [Google Summer of Code 2013](http://www.google-melange.com/gsoc/homepage/google/gsoc2013) \ No newline at end of file diff --git a/Todo, GSoC 2013.txt b/Todo, GSoC 2013.txt index 83602d2..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 =============== @@ -15,122 +15,156 @@ The big stuff: - Making very good progress here. The elegance of recurion - Hats off! - 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) -* 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 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. -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. -* Icons for completions? Or overkill right now? +[ ]Diamond operator isn't supported for now. Bummer. -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. - -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]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 +=============== + +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 :'( + General Stuff ============= -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 -* update build.xml to produce dists -* Make this a contributed mode - mode.txt, github releases feature, version numbering, git tags, etc +[ ][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 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 2c812c9..ed26d76 100644 --- a/build.properties +++ b/build.properties @@ -2,8 +2,9 @@ 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.6 +java.target.version=1.7 lib.name=ExperimentalMode +prettyName=PDE X dist=dist -release=2 -prettyVersion=1.1 \ No newline at end of file +release=7 +prettyVersion=1.0.4b diff --git a/build.xml b/build.xml index 1f06d8b..2fdfc8b 100644 --- a/build.xml +++ b/build.xml @@ -85,12 +85,23 @@ + + + + + + + + diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..5ba33d3 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,890 @@ +# For an explanation of these tags, see Token.java +# trunk/processing/app/src/processing/app/syntax/Token.java + +ADD LITERAL2 blend_ +ALIGN_CENTER LITERAL2 +ALIGN_LEFT LITERAL2 +ALIGN_RIGHT LITERAL2 +ALPHA LITERAL2 +ALPHA_MASK LITERAL2 +ALT LITERAL2 +AMBIENT LITERAL2 +ARC LITERAL2 createShape_ +ARROW LITERAL2 cursor_ +ARGB LITERAL2 +BACKSPACE LITERAL2 keyCode +BASELINE LITERAL2 textAlign_ +BEVEL LITERAL2 strokeJoin_ +BLEND LITERAL2 blend_ +BLUE_MASK LITERAL2 +BLUR LITERAL2 filter_ +BOTTOM LITERAL2 textAlign_ +BOX LITERAL2 createShape_ +BURN LITERAL2 blend_ +CENTER LITERAL2 +CHATTER LITERAL2 +CHORD LITERAL2 arc_ +CLAMP LITERAL2 +CLICK LITERAL2 +CLOSE LITERAL2 +CMYK LITERAL2 +CODED LITERAL2 key +COMPLAINT LITERAL2 +COMPOSITE LITERAL2 +COMPONENT LITERAL2 +CONCAVE_POLYGON LITERAL2 +CONTROL LITERAL2 +CONVEX_POLYGON LITERAL2 +CORNER LITERAL2 textAlign_ +CORNERS LITERAL2 +CROSS LITERAL2 cursor_ +CUSTOM LITERAL2 +DARKEST LITERAL2 blend_ +DEGREES LITERAL2 +DEG_TO_RAD LITERAL2 +DELETE LITERAL2 +DIAMETER LITERAL2 +DIFFERENCE LITERAL2 blend_ +DIFFUSE LITERAL2 +DILATE LITERAL2 filter_ +DIRECTIONAL LITERAL2 +DISABLE_ACCURATE_2D LITERAL2 +DISABLE_DEPTH_MASK LITERAL2 +DISABLE_DEPTH_SORT LITERAL2 +DISABLE_DEPTH_TEST LITERAL2 +DISABLE_NATIVE_FONTS LITERAL2 +DISABLE_OPENGL_ERRORS LITERAL2 +DISABLE_PURE_STROKE LITERAL2 +DISABLE_TEXTURE_MIPMAPS LITERAL2 +DISABLE_TRANSFORM_CACHE LITERAL2 +DISABLE_STROKE_PERSPECTIVE LITERAL2 +DISABLED LITERAL2 +DODGE LITERAL2 blend_ +DOWN LITERAL2 keyCode +DRAG LITERAL2 +DXF LITERAL2 size_ +ELLIPSE LITERAL2 createShape_ +ENABLE_ACCURATE_2D LITERAL2 +ENABLE_DEPTH_MASK LITERAL2 +ENABLE_DEPTH_SORT LITERAL2 +ENABLE_DEPTH_TEST LITERAL2 +ENABLE_NATIVE_FONTS LITERAL2 +ENABLE_OPENGL_ERRORS LITERAL2 +ENABLE_PURE_STROKE LITERAL2 +ENABLE_TEXTURE_MIPMAPS LITERAL2 +ENABLE_TRANSFORM_CACHE LITERAL2 +ENABLE_STROKE_PERSPECTIVE LITERAL2 +ENTER LITERAL2 keyCode +EPSILON LITERAL2 +ERODE LITERAL2 filter_ +ESC LITERAL2 keyCode +EXCLUSION LITERAL2 blend_ +EXIT LITERAL2 +GIF LITERAL2 +GRAY LITERAL2 filter_ +GREEN_MASK LITERAL2 +GROUP LITERAL2 +HALF LITERAL2 +HALF_PI LITERAL2 HALF_PI +HAND LITERAL2 cursor_ +HARD_LIGHT LITERAL2 blend_ +HINT_COUNT LITERAL2 +HSB LITERAL2 colorMode_ +IMAGE LITERAL2 textureMode_ +INVERT LITERAL2 filter_ +JPEG LITERAL2 +LEFT LITERAL2 keyCode +LIGHTEST LITERAL2 blend_ +LINE LITERAL2 createShape_ +LINES LITERAL2 beginShape_ +LINUX LITERAL2 +MACOSX LITERAL2 +MAX_FLOAT LITERAL2 +MAX_INT LITERAL2 +MIN_FLOAT LITERAL2 +MIN_INT LITERAL2 +MITER LITERAL2 stokeJoin_ +MODEL LITERAL2 textMode_ +MOVE LITERAL2 cursor_ +MULTIPLY LITERAL2 blend_ +NORMAL LITERAL2 +NORMALIZED LITERAL2 textureMode_ +NO_DEPTH_TEST LITERAL2 +NTSC LITERAL2 +ONE LITERAL2 +OPAQUE LITERAL2 filter_ +OPEN LITERAL2 +ORTHOGRAPHIC LITERAL2 +OVERLAY LITERAL2 blend_ +PAL LITERAL2 +PDF LITERAL2 size_ +P2D LITERAL2 size_ +P3D LITERAL2 size_ +PERSPECTIVE LITERAL2 +PI LITERAL2 PI +PIE LITERAL2 +PIXEL_CENTER LITERAL2 +POINT LITERAL2 +POINTS LITERAL2 +POSTERIZE LITERAL2 filter_ +PRESS LITERAL2 +PROBLEM LITERAL2 +PROJECT LITERAL2 strokeCap_ +QUAD LITERAL2 createShape_ +QUAD_STRIP LITERAL2 beginShape_ +QUADS LITERAL2 beginShape_ +QUARTER_PI LITERAL2 QUARTER_PI +RAD_TO_DEG LITERAL2 +RADIUS LITERAL2 +RADIANS LITERAL2 +RECT LITERAL2 +RED_MASK LITERAL2 +RELEASE LITERAL2 +REPEAT LITERAL2 +REPLACE LITERAL2 +RETURN LITERAL2 +RGB LITERAL2 colorMode_ +RIGHT LITERAL2 keyCode +ROUND LITERAL2 strokeCap_ +SCREEN LITERAL2 blend_ +SECAM LITERAL2 +SHAPE LITERAL2 textMode_ +SHIFT LITERAL2 +SPECULAR LITERAL2 +SPHERE LITERAL2 createShape_ +SOFT_LIGHT LITERAL2 blend_ +SQUARE LITERAL2 strokeCap_ +SUBTRACT LITERAL2 blend_ +SVIDEO LITERAL2 +TAB LITERAL2 keyCode +TARGA LITERAL2 +TAU LITERAL2 TAU +TEXT LITERAL2 cursor_ +TFF LITERAL2 +THIRD_PI LITERAL2 +THRESHOLD LITERAL2 filter_ +TIFF LITERAL2 +TOP LITERAL2 textAlign_ +TRIANGLE LITERAL2 createShape_ +TRIANGLE_FAN LITERAL2 beginShape_ +TRIANGLES LITERAL2 beginShape_ +TRIANGLE_STRIP LITERAL2 beginShape_ +TUNER LITERAL2 +TWO LITERAL2 +TWO_PI LITERAL2 TWO_PI +UP LITERAL2 keyCode +WAIT LITERAL2 cursor_ +WHITESPACE LITERAL2 + + +# Java keywords (void, import, , etc.) + +abstract KEYWORD1 +assert KEYWORD1 +break KEYWORD1 break +case KEYWORD1 case +class KEYWORD1 class +continue KEYWORD1 continue +default KEYWORD1 default +enum KEYWORD1 +extends KEYWORD1 extends +false KEYWORD1 false +final KEYWORD1 final +finally KEYWORD1 +implements KEYWORD1 implements +import KEYWORD1 import +instanceof KEYWORD1 +interface KEYWORD1 +native KEYWORD1 +new KEYWORD1 new +null KEYWORD1 null +package KEYWORD1 +private KEYWORD1 private +protected KEYWORD1 +public KEYWORD1 public +return KEYWORD1 return +static KEYWORD1 static +strictfp KEYWORD1 +super KEYWORD1 super +this KEYWORD1 this +throw KEYWORD1 +throws KEYWORD1 +transient KEYWORD1 +true KEYWORD1 true +void KEYWORD1 void +volatile KEYWORD1 + + +# Datatypes + +Array KEYWORD5 Array +ArrayList KEYWORD5 ArrayList +Boolean KEYWORD5 +Byte KEYWORD5 +BufferedReader KEYWORD5 BufferedReader +Character KEYWORD5 +Class KEYWORD5 class +Double KEYWORD5 +Float KEYWORD5 +Integer KEYWORD5 +HashMap KEYWORD5 HashMap +PrintWriter KEYWORD5 PrintWriter +String KEYWORD5 String +StringBuffer KEYWORD5 +Thread KEYWORD5 +boolean KEYWORD5 boolean +byte KEYWORD5 byte +char KEYWORD5 char +color KEYWORD5 color_datatype +double KEYWORD5 double +float KEYWORD5 float +int KEYWORD5 int +long KEYWORD5 long +short KEYWORD5 + + +# Flow structures + +catch KEYWORD3 catch +do KEYWORD3 +for KEYWORD3 for +if KEYWORD3 if +else KEYWORD3 else +switch KEYWORD3 switch +synchronized KEYWORD3 +while KEYWORD3 while +try KEYWORD3 try + +catch FUNCTION3 catch +do FUNCTION3 +for FUNCTION3 for +if FUNCTION3 if +#else FUNCTION3 else +switch FUNCTION3 switch +synchronized FUNCTION3 +while FUNCTION3 while +#try FUNCTION3 try + + +# These items are a part of Processing but, but pages don't generate + +boolean FUNCTION1 booleanconvert_ +byte FUNCTION1 byteconvert_ +cache FUNCTION2 +char FUNCTION1 charconvert_ +start FUNCTION1 +stop FUNCTION1 +breakShape FUNCTION1 +createPath FUNCTION1 +float FUNCTION1 floatconvert_ +int FUNCTION1 intconvert_ +str FUNCTION1 strconvert_ +loadMatrix FUNCTION1 +parseBoolean FUNCTION1 +parseByte FUNCTION1 +parseChar FUNCTION1 +parseFloat FUNCTION1 +parseInt FUNCTION1 +saveFile FUNCTION1 +savePath FUNCTION1 +sketchFile FUNCTION1 +sketchPath FUNCTION1 + +readLine FUNCTION2 BufferedReader_readLine_ +close FUNCTION2 PrintWriter_close_ +flush FUNCTION2 PrintWriter_flush_ +print FUNCTION2 PrintWriter_print_ +println FUNCTION2 PrintWriter_println_ +charAt FUNCTION2 String_charAt_ +equals FUNCTION2 String_equals_ +indexOf FUNCTION2 String_indexOf_ +length FUNCTION2 String_length_ +substring FUNCTION2 String_substring_ +toLowerCase FUNCTION2 String_toLowerCase_ +toUpperCase FUNCTION2 String_toUpperCase_ + +length KEYWORD2 String + + +# Temporary additions 3 September 2012 as the reference is getting updated + +end FUNCTION1 +addChild FUNCTION1 + +# Operators are without KEYWORDS + ++= addassign ++ addition +[] arrayaccess += assign +& bitwiseAND +| bitwiseOR +, comma +// comment +? conditional +{} curlybraces +-- decrement +/ divide +/= divideassign +/** doccomment +. dot +== equality +> greaterthan +>= greaterthanorequalto +++ increment +!= inequality +<< leftshift +< lessthan +<= lessthanorequalto +&& logicalAND +! logicalNOT +|| logicalOR +- minus +% modulo +/* multilinecomment +* multiply +*= multiplyassign +() parentheses +>> rightshift +; semicolon +-= subtractassign + +# Suppressed from Generate to avoid conflicts with variables inside methods + +width KEYWORD4 width_ +height KEYWORD4 height_ + +PVector FUNCTION1 PVector +ArrayList FUNCTION1 ArrayList +HashMap FUNCTION1 HashMap + + +# THE TEXT ABOVE IS HAND-WRITTEN AND FOUND IN THE FILE "keywords_base.txt" +# THE TEXT BELOW IS AUTO-GENERATED +# +# SO DON'T +# TOUCH IT + + +abs FUNCTION1 abs_ +acos FUNCTION1 acos_ +alpha FUNCTION1 alpha_ +ambient FUNCTION1 ambient_ +ambientLight FUNCTION1 ambientLight_ +append FUNCTION1 append_ +applyMatrix FUNCTION1 applyMatrix_ +arc FUNCTION1 arc_ +arrayCopy FUNCTION1 arrayCopy_ +asin FUNCTION1 asin_ +atan FUNCTION1 atan_ +atan2 FUNCTION1 atan2_ +background FUNCTION1 background_ +beginCamera FUNCTION1 beginCamera_ +beginContour FUNCTION1 beginContour_ +beginRaw FUNCTION1 beginRaw_ +beginRecord FUNCTION1 beginRecord_ +beginShape FUNCTION1 beginShape_ +bezier FUNCTION1 bezier_ +bezierDetail FUNCTION1 bezierDetail_ +bezierPoint FUNCTION1 bezierPoint_ +bezierTangent FUNCTION1 bezierTangent_ +bezierVertex FUNCTION1 bezierVertex_ +binary FUNCTION1 binary_ +blend FUNCTION1 blend_ +blendColor FUNCTION1 blendColor_ +blendMode FUNCTION1 blendMode_ +blue FUNCTION1 blue_ +box FUNCTION1 box_ +brightness FUNCTION1 brightness_ +camera FUNCTION1 camera_ +ceil FUNCTION1 ceil_ +clear FUNCTION1 clear_ +clip FUNCTION1 clip_ +color FUNCTION1 color_ +colorMode FUNCTION1 colorMode_ +concat FUNCTION1 concat_ +constrain FUNCTION1 constrain_ +copy FUNCTION1 copy_ +cos FUNCTION1 cos_ +createFont FUNCTION1 createFont_ +createGraphics FUNCTION1 createGraphics_ +createImage FUNCTION1 createImage_ +createInput FUNCTION1 createInput_ +createOutput FUNCTION1 createOutput_ +createReader FUNCTION1 createReader_ +createShape FUNCTION1 createShape_ +createWriter FUNCTION1 createWriter_ +cursor FUNCTION1 cursor_ +curve FUNCTION1 curve_ +curveDetail FUNCTION1 curveDetail_ +curvePoint FUNCTION1 curvePoint_ +curveTangent FUNCTION1 curveTangent_ +curveTightness FUNCTION1 curveTightness_ +curveVertex FUNCTION1 curveVertex_ +day FUNCTION1 day_ +degrees FUNCTION1 degrees_ +directionalLight FUNCTION1 directionalLight_ +displayHeight KEYWORD4 displayHeight +displayWidth KEYWORD4 displayWidth +dist FUNCTION1 dist_ +draw FUNCTION4 draw +ellipse FUNCTION1 ellipse_ +ellipseMode FUNCTION1 ellipseMode_ +emissive FUNCTION1 emissive_ +endCamera FUNCTION1 endCamera_ +endContour FUNCTION1 endContour_ +endRaw FUNCTION1 endRaw_ +endRecord FUNCTION1 endRecord_ +endShape FUNCTION1 endShape_ +exit FUNCTION1 exit_ +exp FUNCTION1 exp_ +expand FUNCTION1 expand_ +fill FUNCTION1 fill_ +filter FUNCTION1 filter_ +FloatDict KEYWORD5 FloatDict +add FUNCTION2 FloatDict_add_ +clear FUNCTION2 FloatDict_clear_ +div FUNCTION2 FloatDict_div_ +get FUNCTION2 FloatDict_get_ +hasKey FUNCTION2 FloatDict_hasKey_ +keyArray FUNCTION2 FloatDict_keyArray_ +keys FUNCTION2 FloatDict_keys_ +mult FUNCTION2 FloatDict_mult_ +remove FUNCTION2 FloatDict_remove_ +set FUNCTION2 FloatDict_set_ +size FUNCTION2 FloatDict_size_ +sortKeys FUNCTION2 FloatDict_sortKeys_ +sortKeysReverse FUNCTION2 FloatDict_sortKeysReverse_ +sortValues FUNCTION2 FloatDict_sortValues_ +sortValuesReverse FUNCTION2 FloatDict_sortValuesReverse_ +sub FUNCTION2 FloatDict_sub_ +valueArray FUNCTION2 FloatDict_valueArray_ +values FUNCTION2 FloatDict_values_ +FloatList KEYWORD5 FloatList +add FUNCTION2 FloatList_add_ +append FUNCTION2 FloatList_append_ +array FUNCTION2 FloatList_array_ +clear FUNCTION2 FloatList_clear_ +div FUNCTION2 FloatList_div_ +get FUNCTION2 FloatList_get_ +hasValue FUNCTION2 FloatList_hasValue_ +max FUNCTION2 FloatList_max_ +min FUNCTION2 FloatList_min_ +mult FUNCTION2 FloatList_mult_ +remove FUNCTION2 FloatList_remove_ +reverse FUNCTION2 FloatList_reverse_ +set FUNCTION2 FloatList_set_ +shuffle FUNCTION2 FloatList_shuffle_ +size FUNCTION2 FloatList_size_ +sort FUNCTION2 FloatList_sort_ +sortReverse FUNCTION2 FloatList_sortReverse_ +sub FUNCTION2 FloatList_sub_ +floor FUNCTION1 floor_ +focused KEYWORD4 focused +frameCount KEYWORD4 frameCount +frameRate KEYWORD4 frameRate +frameRate FUNCTION1 frameRate_ +frustum FUNCTION1 frustum_ +get FUNCTION1 get_ +green FUNCTION1 green_ +HALF_PI LITERAL2 HALF_PI +hex FUNCTION1 hex_ +hint FUNCTION1 hint_ +hour FUNCTION1 hour_ +hue FUNCTION1 hue_ +image FUNCTION1 image_ +imageMode FUNCTION1 imageMode_ +IntDict KEYWORD5 IntDict +add FUNCTION2 IntDict_add_ +clear FUNCTION2 IntDict_clear_ +div FUNCTION2 IntDict_div_ +get FUNCTION2 IntDict_get_ +hasKey FUNCTION2 IntDict_hasKey_ +increment FUNCTION2 IntDict_increment_ +keyArray FUNCTION2 IntDict_keyArray_ +keys FUNCTION2 IntDict_keys_ +mult FUNCTION2 IntDict_mult_ +remove FUNCTION2 IntDict_remove_ +set FUNCTION2 IntDict_set_ +size FUNCTION2 IntDict_size_ +sortKeys FUNCTION2 IntDict_sortKeys_ +sortKeysReverse FUNCTION2 IntDict_sortKeysReverse_ +sortValues FUNCTION2 IntDict_sortValues_ +sortValuesReverse FUNCTION2 IntDict_sortValuesReverse_ +sub FUNCTION2 IntDict_sub_ +valueArray FUNCTION2 IntDict_valueArray_ +values FUNCTION2 IntDict_values_ +IntList KEYWORD5 IntList +add FUNCTION2 IntList_add_ +append FUNCTION2 IntList_append_ +array FUNCTION2 IntList_array_ +clear FUNCTION2 IntList_clear_ +div FUNCTION2 IntList_div_ +get FUNCTION2 IntList_get_ +hasValue FUNCTION2 IntList_hasValue_ +increment FUNCTION2 IntList_increment_ +max FUNCTION2 IntList_max_ +min FUNCTION2 IntList_min_ +mult FUNCTION2 IntList_mult_ +remove FUNCTION2 IntList_remove_ +reverse FUNCTION2 IntList_reverse_ +set FUNCTION2 IntList_set_ +shuffle FUNCTION2 IntList_shuffle_ +size FUNCTION2 IntList_size_ +sort FUNCTION2 IntList_sort_ +sortReverse FUNCTION2 IntList_sortReverse_ +sub FUNCTION2 IntList_sub_ +join FUNCTION1 join_ +JSONArray KEYWORD5 JSONArray +append FUNCTION2 JSONArray_append_ +getBoolean FUNCTION2 JSONArray_getBoolean_ +getFloat FUNCTION2 JSONArray_getFloat_ +getInt FUNCTION2 JSONArray_getInt_ +getIntArray FUNCTION2 JSONArray_getIntArray_ +getJSONArray FUNCTION2 JSONArray_getJSONArray_ +getJSONObject FUNCTION2 JSONArray_getJSONObject_ +getString FUNCTION2 JSONArray_getString_ +getStringArray FUNCTION2 JSONArray_getStringArray_ +remove FUNCTION2 JSONArray_remove_ +setBoolean FUNCTION2 JSONArray_setBoolean_ +setFloat FUNCTION2 JSONArray_setFloat_ +setInt FUNCTION2 JSONArray_setInt_ +getJSONArray FUNCTION2 JSONArray_setJSONArray_ +getJSONObject FUNCTION2 JSONArray_setJSONObject_ +setString FUNCTION2 JSONArray_setString_ +size FUNCTION2 JSONArray_size_ +JSONObject KEYWORD5 JSONObject +getBoolean FUNCTION2 JSONObject_getBoolean_ +getFloat FUNCTION2 JSONObject_getFloat_ +getInt FUNCTION2 JSONObject_getInt_ +getJSONArray FUNCTION2 JSONObject_getJSONArray_ +getJSONObject FUNCTION2 JSONObject_getJSONObject_ +getString FUNCTION2 JSONObject_getString_ +setBoolean FUNCTION2 JSONObject_setBoolean_ +setFloat FUNCTION2 JSONObject_setFloat_ +setInt FUNCTION2 JSONObject_setInt_ +setJSONArray FUNCTION2 JSONObject_setJSONArray_ +setJSONObject FUNCTION2 JSONObject_setJSONObject_ +setString FUNCTION2 JSONObject_setString_ +key KEYWORD4 key +keyCode KEYWORD4 keyCode +keyPressed FUNCTION4 keyPressed +keyPressed KEYWORD4 keyPressed +keyReleased FUNCTION4 keyReleased +keyTyped FUNCTION4 keyTyped +lerp FUNCTION1 lerp_ +lerpColor FUNCTION1 lerpColor_ +lightFalloff FUNCTION1 lightFalloff_ +lights FUNCTION1 lights_ +lightSpecular FUNCTION1 lightSpecular_ +line FUNCTION1 line_ +loadBytes FUNCTION1 loadBytes_ +loadFont FUNCTION1 loadFont_ +loadImage FUNCTION1 loadImage_ +loadJSONArray FUNCTION1 loadJSONArray_ +loadJSONObject FUNCTION1 loadJSONObject_ +loadPixels FUNCTION1 loadPixels_ +loadShader FUNCTION1 loadShader_ +loadShape FUNCTION1 loadShape_ +loadStrings FUNCTION1 loadStrings_ +loadTable FUNCTION1 loadTable_ +loadXML FUNCTION1 loadXML_ +log FUNCTION1 log_ +loop FUNCTION1 loop_ +mag FUNCTION1 mag_ +map FUNCTION1 map_ +match FUNCTION1 match_ +matchAll FUNCTION1 matchAll_ +max FUNCTION1 max_ +millis FUNCTION1 millis_ +min FUNCTION1 min_ +minute FUNCTION1 minute_ +modelX FUNCTION1 modelX_ +modelY FUNCTION1 modelY_ +modelZ FUNCTION1 modelZ_ +month FUNCTION1 month_ +mouseButton KEYWORD4 mouseButton +mouseClicked FUNCTION4 mouseClicked +mouseDragged FUNCTION4 mouseDragged +mouseMoved FUNCTION4 mouseMoved +mousePressed FUNCTION4 mousePressed +mousePressed KEYWORD4 mousePressed +mouseReleased FUNCTION1 mouseReleased_ +mouseWheel FUNCTION4 mouseWheel +mouseX KEYWORD4 mouseX +mouseY KEYWORD4 mouseY +nf FUNCTION1 nf_ +nfc FUNCTION1 nfc_ +nfp FUNCTION1 nfp_ +nfs FUNCTION1 nfs_ +noClip FUNCTION1 noClip_ +noCursor FUNCTION1 noCursor_ +noFill FUNCTION1 noFill_ +noise FUNCTION1 noise_ +noiseDetail FUNCTION1 noiseDetail_ +noiseSeed FUNCTION1 noiseSeed_ +noLights FUNCTION1 noLights_ +noLoop FUNCTION1 noLoop_ +norm FUNCTION1 norm_ +normal FUNCTION1 normal_ +noSmooth FUNCTION1 noSmooth_ +noStroke FUNCTION1 noStroke_ +noTint FUNCTION1 noTint_ +open FUNCTION1 open_ +ortho FUNCTION1 ortho_ +parseXML FUNCTION1 parseXML_ +perspective FUNCTION1 perspective_ +PFont KEYWORD5 PFont +list FUNCTION1 PFont_list_ +PGraphics KEYWORD5 PGraphics +beginDraw FUNCTION2 PGraphics_beginDraw_ +endDraw FUNCTION2 PGraphics_endDraw_ +PI LITERAL2 PI +PImage KEYWORD5 PImage +blend FUNCTION2 PImage_blend_ +copy FUNCTION2 PImage_copy_ +filter FUNCTION2 PImage_filter_ +get FUNCTION2 PImage_get_ +loadPixels FUNCTION2 PImage_loadPixels_ +mask FUNCTION2 PImage_mask_ +pixels KEYWORD2 PImage_pixels +resize FUNCTION2 PImage_resize_ +save FUNCTION2 PImage_save_ +set FUNCTION2 PImage_set_ +updatePixels FUNCTION2 PImage_updatePixels_ +pixels KEYWORD4 pixels +pmouseX KEYWORD4 pmouseX +pmouseY KEYWORD4 pmouseY +point FUNCTION1 point_ +pointLight FUNCTION1 pointLight_ +popMatrix FUNCTION1 popMatrix_ +popStyle FUNCTION1 popStyle_ +pow FUNCTION1 pow_ +print FUNCTION1 print_ +printCamera FUNCTION1 printCamera_ +println FUNCTION1 println_ +printMatrix FUNCTION1 printMatrix_ +printProjection FUNCTION1 printProjection_ +PShader KEYWORD5 PShader +PShader FUNCTION2 PShader_set_ +PShape KEYWORD5 PShape +addChild FUNCTION2 PShape_addChild_ +beginContour FUNCTION2 PShape_beginContour_ +disableStyle FUNCTION2 PShape_disableStyle_ +enableStyle FUNCTION2 PShape_enableStyle_ +endContour FUNCTION2 PShape_endContour_ +endShape FUNCTION2 PShape_endShape_ +getChild FUNCTION2 PShape_getChild_ +getChildCount FUNCTION2 PShape_getChildCount_ +getVertex FUNCTION2 PShape_getVertex_ +getVertexCount FUNCTION2 PShape_getVertexCount_ +isVisible FUNCTION2 PShape_isVisible_ +resetMatrix FUNCTION2 PShape_resetMatrix_ +rotate FUNCTION2 PShape_rotate_ +rotateX FUNCTION2 PShape_rotateX_ +rotateY FUNCTION2 PShape_rotateY_ +rotateZ FUNCTION2 PShape_rotateZ_ +scale FUNCTION2 PShape_scale_ +setVertex FUNCTION2 PShape_setVertex_ +setVisible FUNCTION2 PShape_setVisible_ +translate FUNCTION2 PShape_translate_ +pushMatrix FUNCTION1 pushMatrix_ +pushStyle FUNCTION1 pushStyle_ +PVector KEYWORD5 PVector +add FUNCTION2 PVector_add_ +angleBetween FUNCTION2 PVector_angleBetween_ +array FUNCTION2 PVector_array_ +copy FUNCTION2 PVector_copy_ +cross FUNCTION2 PVector_cross_ +dist FUNCTION2 PVector_dist_ +div FUNCTION2 PVector_div_ +dot FUNCTION2 PVector_dot_ +fromAngle FUNCTION2 PVector_fromAngle_ +get FUNCTION2 PVector_get_ +heading FUNCTION2 PVector_heading_ +lerp FUNCTION2 PVector_lerp_ +limit FUNCTION2 PVector_limit_ +mag FUNCTION2 PVector_mag_ +magSq FUNCTION2 PVector_magSq_ +mult FUNCTION2 PVector_mult_ +normalize FUNCTION2 PVector_normalize_ +random2D FUNCTION2 PVector_random2D_ +random3D FUNCTION2 PVector_random3D_ +rotate FUNCTION2 PVector_rotate_ +set FUNCTION2 PVector_set_ +setMag FUNCTION2 PVector_setMag_ +sub FUNCTION2 PVector_sub_ +quad FUNCTION1 quad_ +quadraticVertex FUNCTION1 quadraticVertex_ +QUARTER_PI LITERAL2 QUARTER_PI +radians FUNCTION1 radians_ +random FUNCTION1 random_ +randomGaussian FUNCTION1 randomGaussian_ +randomSeed FUNCTION1 randomSeed_ +rect FUNCTION1 rect_ +rectMode FUNCTION1 rectMode_ +red FUNCTION1 red_ +redraw FUNCTION1 redraw_ +requestImage FUNCTION1 requestImage_ +resetMatrix FUNCTION1 resetMatrix_ +resetShader FUNCTION1 resetShader_ +reverse FUNCTION1 reverse_ +rotate FUNCTION1 rotate_ +rotateX FUNCTION1 rotateX_ +rotateY FUNCTION1 rotateY_ +rotateZ FUNCTION1 rotateZ_ +round FUNCTION1 round_ +saturation FUNCTION1 saturation_ +save FUNCTION1 save_ +saveBytes FUNCTION1 saveBytes_ +saveFrame FUNCTION1 saveFrame_ +saveJSONArray FUNCTION1 saveJSONArray_ +saveJSONObject FUNCTION1 saveJSONObject_ +saveStream FUNCTION1 saveStream_ +saveStrings FUNCTION1 saveStrings_ +loadTable FUNCTION1 saveTable_ +saveXML FUNCTION1 saveXML_ +scale FUNCTION1 scale_ +screenX FUNCTION1 screenX_ +screenY FUNCTION1 screenY_ +screenZ FUNCTION1 screenZ_ +second FUNCTION1 second_ +selectFolder FUNCTION1 selectFolder_ +selectInput FUNCTION1 selectInput_ +selectOutput FUNCTION1 selectOutput_ +set FUNCTION1 set_ +setup FUNCTION4 setup +shader FUNCTION1 shader_ +shape FUNCTION1 shape_ +shapeMode FUNCTION1 shapeMode_ +shearX FUNCTION1 shearX_ +shearY FUNCTION1 shearY_ +shininess FUNCTION1 shininess_ +shorten FUNCTION1 shorten_ +sin FUNCTION1 sin_ +size FUNCTION1 size_ +smooth FUNCTION1 smooth_ +sort FUNCTION1 sort_ +specular FUNCTION1 specular_ +sphere FUNCTION1 sphere_ +sphereDetail FUNCTION1 sphereDetail_ +splice FUNCTION1 splice_ +split FUNCTION1 split_ +splitTokens FUNCTION1 splitTokens_ +spotLight FUNCTION1 spotLight_ +sq FUNCTION1 sq_ +sqrt FUNCTION1 sqrt_ +StringDict KEYWORD5 StringDict +clear FUNCTION2 StringDict_clear_ +get FUNCTION2 StringDict_get_ +hasKey FUNCTION2 StringDict_hasKey_ +keyArray FUNCTION2 StringDict_keyArray_ +keys FUNCTION2 StringDict_keys_ +remove FUNCTION2 StringDict_remove_ +set FUNCTION2 StringDict_set_ +size FUNCTION2 StringDict_size_ +sortKeys FUNCTION2 StringDict_sortKeys_ +sortKeysReverse FUNCTION2 StringDict_sortKeysReverse_ +sortValues FUNCTION2 StringDict_sortValues_ +sortValuesReverse FUNCTION2 StringDict_sortValuesReverse_ +valueArray FUNCTION2 StringDict_valueArray_ +values FUNCTION2 StringDict_values_ +StringList KEYWORD5 StringList +append FUNCTION2 StringList_append_ +array FUNCTION2 StringList_array_ +clear FUNCTION2 StringList_clear_ +get FUNCTION2 StringList_get_ +hasValue FUNCTION2 StringList_hasValue_ +lower FUNCTION2 StringList_lower_ +remove FUNCTION2 StringList_remove_ +reverse FUNCTION2 StringList_reverse_ +set FUNCTION2 StringList_set_ +shuffle FUNCTION2 StringList_shuffle_ +size FUNCTION2 StringList_size_ +sort FUNCTION2 StringList_sort_ +sortReverse FUNCTION2 StringList_sortReverse_ +upper FUNCTION2 StringList_upper_ +stroke FUNCTION1 stroke_ +strokeCap FUNCTION1 strokeCap_ +strokeJoin FUNCTION1 strokeJoin_ +strokeWeight FUNCTION1 strokeWeight_ +subset FUNCTION1 subset_ +Table KEYWORD5 Table +addColumn FUNCTION2 Table_addColumn_ +addRow FUNCTION2 Table_addRow_ +clearRows FUNCTION2 Table_clearRows_ +findRow FUNCTION2 Table_findRow_ +findRows FUNCTION2 Table_findRows_ +getColumnCount FUNCTION2 Table_getColumnCount_ +getFloat FUNCTION2 Table_getFloat_ +getInt FUNCTION2 Table_getInt_ +getRow FUNCTION2 Table_getRow_ +getRowCount FUNCTION2 Table_getRowCount_ +getString FUNCTION2 Table_getString_ +getStringColumn FUNCTION2 Table_getStringColumn_ +matchRow FUNCTION2 Table_matchRow_ +matchRows FUNCTION2 Table_matchRows_ +removeColumn FUNCTION2 Table_removeColumn_ +removeRow FUNCTION2 Table_removeRow_ +removeTokens FUNCTION2 Table_removeTokens_ +rows FUNCTION2 Table_rows_ +setFloat FUNCTION2 Table_setFloat_ +setInt FUNCTION2 Table_setInt_ +setString FUNCTION2 Table_setString_ +trim FUNCTION2 Table_trim_ +TableRow KEYWORD5 TableRow +getFloat FUNCTION2 TableRow_getFloat_ +getFloat FUNCTION2 TableRow_getInt_ +getString FUNCTION2 TableRow_getString_ +setFloat FUNCTION2 TableRow_setFloat_ +setInt FUNCTION2 TableRow_setInt_ +setString FUNCTION2 TableRow_setString_ +tan FUNCTION1 tan_ +TAU LITERAL2 TAU +text FUNCTION1 text_ +textAlign FUNCTION1 textAlign_ +textAscent FUNCTION1 textAscent_ +textDescent FUNCTION1 textDescent_ +textFont FUNCTION1 textFont_ +textLeading FUNCTION1 textLeading_ +textMode FUNCTION1 textMode_ +textSize FUNCTION1 textSize_ +texture FUNCTION1 texture_ +textureMode FUNCTION1 textureMode_ +textureWrap FUNCTION1 textureWrap_ +textWidth FUNCTION1 textWidth_ +tint FUNCTION1 tint_ +translate FUNCTION1 translate_ +triangle FUNCTION1 triangle_ +trim FUNCTION1 trim_ +TWO_PI LITERAL2 TWO_PI +unbinary FUNCTION1 unbinary_ +unhex FUNCTION1 unhex_ +updatePixels FUNCTION1 updatePixels_ +vertex FUNCTION1 vertex_ +XML KEYWORD5 XML +addChild FUNCTION2 XML_addChild_ +format FUNCTION2 XML_format_ +getAttributeCount FUNCTION2 XML_getAttributeCount_ +getChild FUNCTION2 XML_getChild_ +getChildren FUNCTION2 XML_getChildren_ +getContent FUNCTION2 XML_getContent_ +getFloat FUNCTION2 XML_getFloat_ +getContent FUNCTION2 XML_getFloatContent_ +getInt FUNCTION2 XML_getInt_ +getContent FUNCTION2 XML_getIntContent_ +getName FUNCTION2 XML_getName_ +getParent FUNCTION2 XML_getParent_ +getString FUNCTION2 XML_getString_ +hasAttribute FUNCTION2 XML_hasAttribute_ +hasChildren FUNCTION2 XML_hasChildren_ +listAttributes FUNCTION2 XML_listAttributes_ +listChildren FUNCTION2 XML_listChildren_ +removeChild FUNCTION2 XML_removeChild_ +setContent FUNCTION2 XML_setContent_ +setFloat FUNCTION2 XML_setFloat_ +setInt FUNCTION2 XML_setInt_ +setName FUNCTION2 XML_setName_ +setString FUNCTION2 XML_setString_ +toString FUNCTION2 XML_toString_ +year FUNCTION1 year_ diff --git a/mode.properties b/mode.properties new file mode 100644 index 0000000..e70030c --- /dev/null +++ b/mode.properties @@ -0,0 +1,7 @@ +name=PDE X +authorList=[The Processing Foundation](http://processing.org) +url=https://github.com/processing/processing-experimental +sentence=The next generation of PDE +paragraph=Intelligent Code Completion, Quick Navigation, Refactoring, Live Error Checker, Debugger, etc. +version=@@version@@ +prettyVersion=@@pretty-version@@ diff --git a/pdeX.txt b/pdeX.txt new file mode 100644 index 0000000..7c2a3aa --- /dev/null +++ b/pdeX.txt @@ -0,0 +1,7 @@ +name=PDE X +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=7 +prettyVersion=1.0.4b diff --git a/revisions.txt b/revisions.txt new file mode 100644 index 0000000..647859a --- /dev/null +++ b/revisions.txt @@ -0,0 +1,85 @@ + +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 + ++ Code completion window gets stuck when Processing loses focus + https://github.com/processing/processing-experimental/issues/21 + ++ Live-error checker is more efficient with memory now. + You can have upto 7 editor windows open at a time with PDE X. + https://github.com/processing/processing-experimental/issues/1 + ++ Cmd + Left Click should be working again in OS X with Processing 2.1 + https://github.com/processing/processing-experimental/issues/11 + ++ TextAreaPainter updated for Processing 2.1 + +. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + +PDE X v1.0.1b - 25 September, 2013 + +Bug fix + ++ Fixed a major issue where completion list was going blank. + https://github.com/processing/processing-experimental/issues/19 + +Changes + +- Removed the tiny markers shown at the start of error lines. Too. Much. Red. + +. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + +PDE X v1.0.0b - 22 September, 2013 + +Boom! First Public Beta Release! + diff --git a/src/processing/mode/experimental/ASTGenerator.java b/src/processing/mode/experimental/ASTGenerator.java index 6a71610..d254db7 100644 --- a/src/processing/mode/experimental/ASTGenerator.java +++ b/src/processing/mode/experimental/ASTGenerator.java @@ -3,14 +3,11 @@ import static processing.mode.experimental.ExperimentalMode.log; import static processing.mode.experimental.ExperimentalMode.logE; +import java.awt.BorderLayout; import java.awt.Dimension; -import java.awt.Point; import java.awt.Rectangle; -import java.awt.ScrollPane; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.WindowEvent; -import java.awt.event.WindowFocusListener; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -26,11 +23,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; 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; @@ -50,14 +47,14 @@ import javax.swing.JTextField; import javax.swing.JTree; import javax.swing.ListSelectionModel; -import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.UIManager; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; -import javax.swing.table.DefaultTableModel; 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; @@ -93,6 +90,7 @@ import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import processing.app.Base; +import processing.app.Library; import processing.app.SketchCode; import processing.app.Toolkit; import processing.mode.java.preproc.PdePreprocessor; @@ -109,68 +107,71 @@ public class ASTGenerator { public DefaultMutableTreeNode codeTree = new DefaultMutableTreeNode(); - private DefaultMutableTreeNode currentParent = null; + protected DefaultMutableTreeNode currentParent = null; /** * AST Window */ - private JFrame frame2; + protected JFrame frmASTView; - private JFrame frameAutoComp; + protected JFrame frameAutoComp; /** * Swing component wrapper for AST, used for internal testing */ - private JTree jtree; + protected JTree jtree; /** * JTree used for testing refactoring operations */ - private JTree treeRename; + protected JTree treeRename; - private CompilationUnit compilationUnit; + protected CompilationUnit compilationUnit; - private JTable tableAuto; + protected JTable tableAuto; - private JEditorPane javadocPane; + protected JEditorPane javadocPane; - private JScrollPane scrollPane; + protected JScrollPane scrollPane; - private JFrame frmRename; + protected JFrame frmRename; - private JButton btnRename; + protected JButton btnRename; - private JButton btnListOccurrence; + protected JButton btnListOccurrence; - private JTextField txtRenameField; + protected JTextField txtRenameField; - private JFrame frmOccurenceList; + protected JFrame frmOccurenceList; - private JLabel lblRefactorOldName; + protected JLabel lblRefactorOldName; public ASTGenerator(ErrorCheckerService ecs) { this.errorCheckerService = ecs; this.editor = ecs.getEditor(); setupGUI(); //addCompletionPopupListner(); - addListeners(); + addListeners(); + //loadJavaDoc(); + predictionOngoing = new AtomicBoolean(false); } - private void setupGUI(){ - frame2 = new JFrame(); + protected void setupGUI(){ + 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"); frmRename = new JFrame(); frmRename.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); - frmRename.setBounds(new Rectangle(680, 50, 250, 130)); + frmRename.setSize(250, 130); frmRename.setLayout(new BoxLayout(frmRename.getContentPane(), BoxLayout.Y_AXIS)); Toolkit.setIcon(frmRename); JPanel panelTop = new JPanel(), panelBottom = new JPanel(); @@ -192,13 +193,18 @@ private void setupGUI(){ panelTop.add(Box.createRigidArea(new Dimension(0, 10))); panelTop.add(lblRefactorOldName); frmRename.add(panelTop); - frmRename.add(panelBottom); - + frmRename.add(panelBottom); frmRename.setMinimumSize(frmRename.getSize()); + frmRename.setLocation(editor.getX() + + (editor.getWidth() - frmRename.getWidth()) / 2, + editor.getY() + + (editor.getHeight() - frmRename.getHeight()) + / 2); + frmOccurenceList = new JFrame(); frmOccurenceList.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); - frmOccurenceList.setBounds(new Rectangle(1100, 50, 350, 500)); + frmOccurenceList.setSize(300, 400); Toolkit.setIcon(frmOccurenceList); JScrollPane sp2 = new JScrollPane(); treeRename = new JTree(); @@ -206,30 +212,37 @@ private void setupGUI(){ frmOccurenceList.add(sp2); //occurenceListFrame.setVisible(true); - frameAutoComp = new JFrame(); - frameAutoComp.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - frameAutoComp.setBounds(new Rectangle(1280, 100, 460, 620)); - Toolkit.setIcon(frameAutoComp); - tableAuto = new JTable(); - JScrollPane sp3 = new JScrollPane(); - sp3.setViewportView(tableAuto); - frameAutoComp.add(sp3); - -// jdocWindow = new JFrame(); -// jdocWindow.setTitle("P5 InstaHelp"); +// frameAutoComp = new JFrame(); +// frameAutoComp.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); +// frameAutoComp.setBounds(new Rectangle(1280, 100, 460, 620)); +// Toolkit.setIcon(frameAutoComp); +// tableAuto = new JTable(); +// JScrollPane sp3 = new JScrollPane(); +// sp3.setViewportView(tableAuto); +// frameAutoComp.add(sp3); + +// frmJavaDoc = new JFrame(); +// frmJavaDoc.setTitle("P5 InstaHelp"); // //jdocWindow.setUndecorated(true); -// jdocWindow.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); +// frmJavaDoc.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); // javadocPane = new JEditorPane(); // javadocPane.setContentType("text/html"); +// javadocPane.setText(" "); // javadocPane.setEditable(false); // scrollPane = new JScrollPane(); // scrollPane.setViewportView(javadocPane); -// jdocWindow.add(scrollPane); -// jdocMap = new TreeMap(); -//// loadJars(); +// frmJavaDoc.add(scrollPane); + //frmJavaDoc.setUndecorated(true); + + } + + /** + * Toggle AST View window + */ + public static final boolean SHOWAST = true; - private DefaultMutableTreeNode buildAST(String source, CompilationUnit cu) { + protected DefaultMutableTreeNode buildAST(String source, CompilationUnit cu) { if (cu == null) { ASTParser parser = ASTParser.newParser(AST.JLS4); parser.setSource(source.toCharArray()); @@ -243,14 +256,14 @@ private 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!"); } @@ -264,31 +277,32 @@ 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(); -// 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); // // } -// if (!jdocWindow.isVisible()) { +// if (!frmJavaDoc.isVisible()) { // long t = System.currentTimeMillis(); -// loadJars(); // loadJavaDoc(); // log("Time taken: " // + (System.currentTimeMillis() - t)); -// jdocWindow.setBounds(new Rectangle(errorCheckerService.getEditor() +// frmJavaDoc.setBounds(new Rectangle(errorCheckerService.getEditor() // .getX() + errorCheckerService.getEditor().getWidth(), // errorCheckerService.getEditor() // .getY(), 450, 600)); -// jdocWindow.setVisible(true); +// frmJavaDoc.setVisible(true); // } -// jtree.validate(); } } }; @@ -299,14 +313,14 @@ protected void done() { return codeTree; } - private ClassPathFactory factory; + protected ClassPathFactory factory; /** * Used for searching for package declaration of a class */ - private ClassPath classPath; + protected ClassPath classPath; - private JFrame jdocWindow; + //protected JFrame frmJavaDoc; /** * Loads up .jar files and classes defined in it for completion lookup @@ -341,9 +355,11 @@ public void run() { + File.separator + "rt.jar" + File.pathSeparatorChar); } if (errorCheckerService.classpathJars != null) { - for (URL jarPath : errorCheckerService.classpathJars) { - //log(jarPath.getPath()); - tehPath.append(jarPath.getPath() + File.pathSeparatorChar); + synchronized (errorCheckerService.classpathJars) { + for (URL jarPath : errorCheckerService.classpathJars) { + //log(jarPath.getPath()); + tehPath.append(jarPath.getPath() + File.pathSeparatorChar); + } } } @@ -388,10 +404,10 @@ public void run() { t.start(); } - private TreeMap jdocMap; - - private void loadJavaDoc() { + protected TreeMap jdocMap; + protected void loadJavaDoc() { + jdocMap = new TreeMap(); // presently loading only p5 reference for PApplet Thread t = new Thread(new Runnable() { @@ -557,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; @@ -598,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; @@ -751,12 +771,12 @@ public static ASTNode getParentExpression(ASTNode expression) { return null; } - private void trimCandidates(String newWord){ + protected void trimCandidates(String newWord){ ArrayList newCandidate = new ArrayList(); newWord = newWord.toLowerCase(); for (CompletionCandidate comp : candidates) { if(comp.toString().toLowerCase().startsWith(newWord)){ - log("Adding " + comp); + // log("Adding " + comp); newCandidate.add(comp); } } @@ -766,22 +786,45 @@ private void trimCandidates(String newWord){ /** * List of CompletionCandidates */ - private ArrayList candidates; - private String lastPredictedWord = " "; + protected ArrayList candidates; + protected String lastPredictedWord = " "; + //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; + + 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 - protected Object doInBackground() throws Exception { - return null; - } - - protected void done() { + +// SwingWorker worker = new SwingWorker() { +// +// @Override +// protected Object doInBackground() throws Exception { +// return null; +// } +// +// protected void done() { + // If the parsed code contains pde enhancements, take 'em out. String word2 = ASTNodeWrapper.getJavaCode(word); @@ -793,7 +836,7 @@ protected void done() { noCompare = true; } - if (word2.length() > 2 && !noCompare + if (word2.length() >= predictionMinLength && !noCompare && word2.length() > lastPredictedWord.length()) { if (word2.startsWith(lastPredictedWord)) { log(word + " starts with " + lastPredictedWord); @@ -806,6 +849,7 @@ protected void done() { } showPredictions(word); lastPredictedWord = word2; + predictionOngoing.set(false); return; } } @@ -848,12 +892,19 @@ protected void done() { 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)); @@ -936,9 +987,10 @@ protected void done() { matchedClass2 = matchedClass2.replace('/', '.'); String matchedClass = matchedClass2.substring(0, matchedClass2 .length() - 6); - if (ignorableImport(matchedClass2)) - continue; int d = matchedClass.lastIndexOf('.'); + if (ignorableImport(matchedClass2,matchedClass.substring(d + 1))) + continue; + matchedClass = matchedClass.substring(d + 1); candidates .add(new CompletionCandidate(matchedClass, matchedClass @@ -981,32 +1033,32 @@ protected void done() { } showPredictions(word); - - } - }; - - worker.execute(); + predictionOngoing.set(false); +// } +// }; +// +// worker.execute(); } - private void showPredictions(final String word) { + protected void showPredictions(final String word) { if (sketchOutline != null) if (sketchOutline.isVisible()) return; Collections.sort(candidates); - CompletionCandidate[][] candi = new CompletionCandidate[candidates.size()][1]; +// CompletionCandidate[][] candi = new CompletionCandidate[candidates.size()][1]; DefaultListModel defListModel = new DefaultListModel(); - for (int i = 0; i < candi.length; i++) { - candi[i][0] = candidates.get(i); + for (int i = 0; i < candidates.size(); i++) { +// candi[i][0] = candidates.get(i); defListModel.addElement(candidates.get(i)); } log("Total preds = " + candidates.size()); - DefaultTableModel tm = new DefaultTableModel(candi, - new String[] { "Suggestions" }); - if (tableAuto.isVisible()) { - tableAuto.setModel(tm); - tableAuto.validate(); - tableAuto.repaint(); - } +// DefaultTableModel tm = new DefaultTableModel(candi, +// new String[] { "Suggestions" }); +// if (tableAuto.isVisible()) { +// tableAuto.setModel(tm); +// tableAuto.validate(); +// tableAuto.repaint(); +// } errorCheckerService.getEditor().textArea() .showSuggestion(defListModel, word); } @@ -1137,13 +1189,74 @@ 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 * @param className * @return */ - private Class findClassIfExists(String className){ + protected Class findClassIfExists(String className){ if(className == null){ return null; } @@ -1214,7 +1327,7 @@ private Class findClassIfExists(String className){ return tehClass; } - private Class loadClass(String className){ + protected Class loadClass(String className){ Class tehClass = null; if(className instanceof String){ try { @@ -1299,41 +1412,48 @@ public ClassMember definedIn3rdPartyClass(ClassMember tehClass,String memberName public void updateJavaDoc(final CompletionCandidate candidate) { //TODO: Work on this later. - return; - /*String methodmatch = candidate.toString(); + return; + /* String methodmatch = candidate.toString(); if (methodmatch.indexOf('(') != -1) { methodmatch = methodmatch.substring(0, methodmatch.indexOf('(')); } //log("jdoc match " + methodmatch); + String temp = " "; for (final String key : jdocMap.keySet()) { if (key.startsWith(methodmatch) && key.length() > 3) { log("Matched jdoc " + key); - - //visitRecur((ASTNode) compilationUnit.types().get(0), codeTree); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - - log("Class: " + candidate.getDefiningClass()); - if (candidate.getDefiningClass().equals("processing.core.PApplet")) { - javadocPane.setText(jdocMap.get(key)); - //jdocWindow.setVisible(true); - //editor.textArea().requestFocus() - } else - javadocPane.setText(""); - javadocPane.setCaretPosition(0); + if (candidate.getWrappedObject() != null) { + String definingClass = ""; + if (candidate.getWrappedObject() instanceof Field) + definingClass = ((Field) candidate.getWrappedObject()) + .getDeclaringClass().getName(); + else if (candidate.getWrappedObject() instanceof Method) + definingClass = ((Method) candidate.getWrappedObject()) + .getDeclaringClass().getName(); + if (definingClass.equals("processing.core.PApplet")) { + temp = (jdocMap.get(key)); + break; } - }); - break; + } } - }*/ - //jdocWindow.setVisible(false); - + } + + final String jdocString = temp; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + javadocPane.setText(jdocString); + scrollPane.getVerticalScrollBar().setValue(0); + //frmJavaDoc.setVisible(!jdocString.equals(" ")); + editor.toFront(); + editor.ta.requestFocus(); + } + }); +*/ } @SuppressWarnings("unchecked") - private static ASTNode findClosestParentNode(int lineNumber, ASTNode node) { + protected static ASTNode findClosestParentNode(int lineNumber, ASTNode node) { Iterator it = node .structuralPropertiesForType().iterator(); // logE("Props of " + node.getClass().getName()); @@ -1373,19 +1493,21 @@ else if (prop.isChildListProperty()) { return node; } - private static ASTNode findClosestNode(int lineNumber, ASTNode node) { + protected static ASTNode findClosestNode(int lineNumber, ASTNode node) { + log("findClosestNode to line " + lineNumber); ASTNode parent = findClosestParentNode(lineNumber, node); + log("findClosestParentNode returned " + getNodeAsString(parent)); if (parent == null) return null; - if (getLineNumber(parent) == lineNumber) + if (getLineNumber(parent) == lineNumber){ + log(parent + "|PNode " + getLineNumber(parent) + ", lfor " + lineNumber ); return parent; + } List nodes = null; if (parent instanceof TypeDeclaration) { - nodes = (List) ((TypeDeclaration) parent) - .getStructuralProperty(TypeDeclaration.BODY_DECLARATIONS_PROPERTY); + nodes = (List) ((TypeDeclaration) parent).bodyDeclarations(); } else if (parent instanceof Block) { - nodes = (List) ((Block) parent) - .getStructuralProperty(Block.STATEMENTS_PROPERTY); + nodes = (List) ((Block) parent).statements(); } else { System.err.println("THIS CONDITION SHOULD NOT OCCUR - findClosestNode " + getNodeAsString(parent)); @@ -1396,6 +1518,7 @@ private static ASTNode findClosestNode(int lineNumber, ASTNode node) { ASTNode retNode = parent; for (int i = 0; i < nodes.size(); i++) { ASTNode cNode = nodes.get(i); + log(cNode + "|cNode " + getLineNumber(cNode) + ", lfor " + lineNumber ); if (getLineNumber(cNode) <= lineNumber) retNode = cNode; } @@ -1414,7 +1537,7 @@ public String getLabelForASTNode(int lineNumber, String name, int offset) { //return ""; } - private String getLabelIfType(ASTNodeWrapper node, SimpleName sn){ + protected String getLabelIfType(ASTNodeWrapper node, SimpleName sn){ ASTNode current = node.getNode().getParent(); String type = ""; StringBuffer fullName = new StringBuffer(); @@ -1483,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 @@ -1493,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()); @@ -1501,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. @@ -1620,7 +1746,7 @@ public ASTNodeWrapper getASTNodeAt(int lineNumber, String name, int offset, * @param name - The name we're looking for. * @return SimpleName */ - private static ASTNode getNodeName(ASTNode node, String name){ + protected static ASTNode getNodeName(ASTNode node, String name){ List vdfs = null; switch (node.getNodeType()) { case ASTNode.TYPE_DECLARATION: @@ -1713,7 +1839,8 @@ public static void traversal2() { } } - private void addListeners(){ + final ASTGenerator thisASTGenerator = this; + protected void addListeners(){ jtree.addTreeSelectionListener(new TreeSelectionListener() { @Override @@ -1733,13 +1860,37 @@ protected void done() { } DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) jtree .getLastSelectedPathComponent(); - if(tnode.getUserObject() == null){ - return; - } - 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(); + } } } }; @@ -1806,13 +1957,11 @@ protected void done() { } DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) treeRename .getLastSelectedPathComponent(); - if(tnode.getUserObject() == null){ - return; - } if (tnode.getUserObject() instanceof ASTNodeWrapper) { ASTNodeWrapper awrap = (ASTNodeWrapper) tnode.getUserObject(); - errorCheckerService.highlightNode(awrap); + //errorCheckerService.highlightNode(awrap); + awrap.highlightNode(thisASTGenerator); } } }; @@ -1821,16 +1970,19 @@ protected void done() { }); } - private void refactorIt(){ + 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 @@ -1840,75 +1992,100 @@ private void refactorIt(){ //else log("New name looks K."); errorCheckerService.pauseThread(); - treeRename.setModel(new DefaultTreeModel(defCU)); - ((DefaultTreeModel) treeRename.getModel()).reload(); - frmOccurenceList.setTitle("Usage of " + selText); - frmOccurenceList.setVisible(true); + if(treeRename.isVisible()){ + treeRename.setModel(new DefaultTreeModel(defCU)); + ((DefaultTreeModel) treeRename.getModel()).reload(); + } +// 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(); - errorCheckerService.runManualErrorCheck(); - 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) @@ -1916,7 +2093,9 @@ 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; lastClickedWordNode = null; @@ -1935,7 +2114,7 @@ public void setLastClickedWord(int lineNumber, String lastClickedWord, int offse log("Last clicked node: " + lastClickedWordNode); } - private DefaultMutableTreeNode findAllOccurrences(){ + protected DefaultMutableTreeNode findAllOccurrences(){ log("Last clicked word:" + lastClickedWord); String selText = lastClickedWord == null ? editor.ta.getSelectedText() : lastClickedWord; @@ -2071,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()))); @@ -2080,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); @@ -2088,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); @@ -2112,20 +2353,25 @@ public int javaCodeOffsetToLineStartOffset(int line, int jOffset){ return -1; } - private 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; } - private boolean isInstanceOfType(ASTNode node,ASTNode decl, String name){ + protected boolean isInstanceOfType(ASTNode node,ASTNode decl, String name){ if(node instanceof SimpleName){ SimpleName sn = (SimpleName) node; @@ -2159,15 +2405,32 @@ private 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()){ + frmRename.setLocation(editor.getX() + + (editor.getWidth() - frmRename.getWidth()) / 2, + editor.getY() + + (editor.getHeight() - frmRename.getHeight()) + / 2); frmRename.setVisible(true); SwingUtilities.invokeLater(new Runnable() { @Override @@ -2178,6 +2441,7 @@ public void run() { + selText); lblRefactorOldName.setText("Current name: " + selText); + txtRenameField.setText(""); txtRenameField.requestFocus(); } }); @@ -2218,7 +2482,7 @@ else if (prop.isChildListProperty()) { } @SuppressWarnings("unchecked") - private static ASTNode findLineOfNode(ASTNode node, int lineNumber, + protected static ASTNode findLineOfNode(ASTNode node, int lineNumber, int offset, String name) { CompilationUnit root = (CompilationUnit) node.getRoot(); @@ -2278,10 +2542,10 @@ private 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()) { @@ -2335,7 +2599,7 @@ public static ASTNode pinpointOnLine(ASTNode node, int offset, * @return */ @SuppressWarnings("unchecked") - private static ASTNode findDeclaration(Name findMe) { + protected static ASTNode findDeclaration(Name findMe) { // WARNING: You're entering the Rube Goldberg territory of Experimental Mode. // To debug this code, thou must take the Recursive Leap of Faith. @@ -2530,7 +2794,7 @@ else if (parent instanceof Expression) { * @param alternateParent * @return */ - private static ASTNode findDeclaration2(Name findMe, ASTNode alternateParent) { + protected static ASTNode findDeclaration2(Name findMe, ASTNode alternateParent) { ASTNode declaringClass = null; ASTNode parent = findMe.getParent(); ASTNode ret = null; @@ -2714,7 +2978,7 @@ private static ASTNode findDeclaration2(Name findMe, ASTNode alternateParent) { } - private List getCodeComments(){ + protected List getCodeComments(){ List commentList = compilationUnit.getCommentList(); // log("Total comments: " + commentList.size()); // int i = 0; @@ -2911,7 +3175,7 @@ public static Type extracTypeInfo2(ASTNode node) { } @SuppressWarnings("unchecked") - private static ASTNode definedIn(ASTNode node, String name, + protected static ASTNode definedIn(ASTNode node, String name, ArrayList constrains, ASTNode declaringClass) { if (node == null) @@ -3005,7 +3269,7 @@ private static ASTNode definedIn(ASTNode node, String name, } return null; } - JFrame frmImportSuggest; + protected JFrame frmImportSuggest; public void suggestImports(final String className){ if(frmImportSuggest != null) if(frmImportSuggest.isVisible()) @@ -3019,25 +3283,52 @@ public void suggestImports(final String className){ Pattern.CASE_INSENSITIVE)); String[] resources = classPath .findResources("", regf); - if(resources.length == 0){ - log("Couldn't find import for class " + className); - return; + ArrayList candidates = new ArrayList(); + for (String res : resources) { + candidates.add(res); } + + // log("Couldn't find import for class " + className); + + for (Library lib : editor.dmode.contribLibraries) { + ClassPath cp = factory.createFromPath(lib.getClassPath()); + resources = cp.findResources("", regf); + for (String res : resources) { + candidates.add(res); + log("Res: " + res); + } + } + + if (editor.getSketch().hasCodeFolder()) { + File codeFolder = editor.getSketch().getCodeFolder(); + // get a list of .jar files in the "code" folder + // (class files in subfolders should also be picked up) + ClassPath cp = factory.createFromPath(Base + .contentsToClassPath(codeFolder)); + resources = cp.findResources("", regf); + for (String res : resources) { + candidates.add(res); + log("Res: " + res); + } + } + + resources = new String[candidates.size()]; for (int i = 0; i < resources.length; i++) { - resources[i] = resources[i].replace('/', '.') - .substring(0, resources[i].length() - 6); + resources[i] = candidates.get(i).replace('/', '.') + .substring(0, candidates.get(i).length() - 6); } if (resources.length >= 1) { final JList classList = new JList(resources); classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); frmImportSuggest = new JFrame(); - frmImportSuggest.setBounds(300, 300, 400, 300); + frmImportSuggest.setSize(350, 200); + Toolkit.setIcon(frmImportSuggest); frmImportSuggest.setLayout(new BoxLayout(frmImportSuggest .getContentPane(), BoxLayout.Y_AXIS)); ((JComponent) frmImportSuggest.getContentPane()).setBorder(BorderFactory .createEmptyBorder(5, 5, 5, 5)); JLabel lbl = new JLabel("The class \"" + className - + "\" couldn't be determined, choose the import you need from the following list."); + + "\" couldn't be determined. You are probably missing one of the following imports:"); JScrollPane jsp = new JScrollPane(); jsp.setViewportView(classList); JButton btnInsertImport = new JButton("Insert import"); @@ -3061,31 +3352,78 @@ public void actionPerformed(ActionEvent evt) { } } }); - - frmImportSuggest.add(lbl); - frmImportSuggest.add(jsp); - frmImportSuggest.add(btnInsertImport); + + JButton btnCancel = new JButton("Cancel"); + btnCancel.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + frmImportSuggest.setVisible(false); + } + }); + + JPanel panelTop = new JPanel(), panelBottom = new JPanel(), panelLabel = new JPanel(); + panelTop.setLayout(new BoxLayout(panelTop, BoxLayout.Y_AXIS)); + panelTop.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + panelLabel.setLayout(new BorderLayout()); + panelLabel.add(lbl,BorderLayout.CENTER); + panelTop.add(panelLabel); + panelTop.add(Box.createRigidArea(new Dimension(1, 5))); + panelTop.add(jsp); + panelBottom.setLayout(new BoxLayout(panelBottom, BoxLayout.X_AXIS)); + panelBottom.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + panelBottom .setLayout(new BoxLayout(panelBottom, BoxLayout.X_AXIS)); + panelBottom.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + panelBottom.add(Box.createHorizontalGlue()); + panelBottom.add(btnInsertImport); + panelBottom.add(Box.createRigidArea(new Dimension(15, 0))); + panelBottom.add(btnCancel); + +// frmImportSuggest.add(lbl); +// frmImportSuggest.add(jsp); +// frmImportSuggest.add(btnInsertImport); + frmImportSuggest.add(panelTop); + frmImportSuggest.add(panelBottom); frmImportSuggest.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + frmImportSuggest.setTitle("Import Suggestion"); + frmImportSuggest.setLocation(editor.getX() + + (editor.getWidth() - frmImportSuggest.getWidth()) / 2, + editor.getY() + + (editor.getHeight() - frmImportSuggest.getHeight()) + / 2); + editor.ta.hideSuggestion(); frmImportSuggest.setVisible(true); } } + + public void disposeAllWindows() { + disposeWindow(frmASTView, frameAutoComp, frmImportSuggest, + frmOccurenceList, frmRename); + } + + public static void disposeWindow(JFrame... f) { + for (JFrame jFrame : f) { + if(jFrame != null) + jFrame.dispose(); + } + } public static final String ignoredImports[] = { "com.oracle.", "sun.", "sunw.", "com.sun.", "javax.", "sunw.", "org.ietf.", "org.jcp.", "org.omg.", "org.w3c.", "org.xml.", "org.eclipse.", "com.ibm.", "org.netbeans.", "org.jsoup.", "org.junit.", "org.apache.", "antlr." }; - private boolean ignorableImport(String impName) { + public static final String allowedImports[] = {"java.lang.", "java.util.", "java.io.", + "java.math.", "processing.core.", "processing.data.", "processing.event.", "processing.opengl."}; + protected boolean ignorableImport(String impName, String className) { //TODO: Trie man. for (ImportStatement impS : errorCheckerService.getProgramImports()) { if(impName.startsWith(impS.getPackageName())) return false; } - for (String impS : ignoredImports) { - if(impName.startsWith(impS)) - return true; + for (String impS : allowedImports) { + if(impName.startsWith(impS) && className.indexOf('.') == -1) + return false; } - return false; + return true; } public static boolean isAddableASTNode(ASTNode node) { @@ -3139,7 +3477,7 @@ public int[] getASTNodeAllOffsets(ASTNode node){ - static private String getNodeAsString(ASTNode node) { + static protected String getNodeAsString(ASTNode node) { if (node == null) return "NULL"; String className = node.getClass().getName(); @@ -3191,7 +3529,7 @@ else if (className.endsWith("Type")) * @param node * @return */ - static private String getNodeAsString2(ASTNode node) { + static protected String getNodeAsString2(ASTNode node) { if (node == null) return "NULL"; String className = node.getClass().getName(); @@ -3238,7 +3576,7 @@ else if (className.endsWith("Type")) } public void jdocWindowVisible(boolean visible) { - jdocWindow.setVisible(visible); + // frmJavaDoc.setVisible(visible); } public static String readFile(String path) { 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/CompletionCandidate.java b/src/processing/mode/experimental/CompletionCandidate.java index 5c3761d..ef29d59 100644 --- a/src/processing/mode/experimental/CompletionCandidate.java +++ b/src/processing/mode/experimental/CompletionCandidate.java @@ -5,6 +5,7 @@ import java.util.List; import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.TypeDeclaration; @@ -17,6 +18,8 @@ public class CompletionCandidate implements Comparable{ private String label; // the toString value private String completionString; + + private Object wrappedObject; private int type; @@ -27,7 +30,6 @@ public class CompletionCandidate implements Comparable{ public CompletionCandidate(Method method) { method.getDeclaringClass().getName(); elementName = method.getName(); - type = LOCAL_METHOD; StringBuffer label = new StringBuffer(method.getName() + "("); StringBuffer cstr = new StringBuffer(method.getName() + "("); for (int i = 0; i < method.getParameterTypes().length; i++) { @@ -48,19 +50,30 @@ public CompletionCandidate(Method method) { this.label = label.toString(); this.completionString = cstr.toString(); type = PREDEF_METHOD; + wrappedObject = method; } + public Object getWrappedObject() { + return wrappedObject; + } + public CompletionCandidate(SingleVariableDeclaration svd) { completionString = svd.getName().toString(); elementName = svd.getName().toString(); - type = LOCAL_VAR; + if(svd.getParent() instanceof FieldDeclaration) + type = LOCAL_FIELD; + else + type = LOCAL_VAR; label = svd.getName() + " : " + svd.getType(); } public CompletionCandidate(VariableDeclarationFragment vdf) { completionString = vdf.getName().toString(); elementName = vdf.getName().toString(); - type = LOCAL_VAR; + if(vdf.getParent() instanceof FieldDeclaration) + type = LOCAL_FIELD; + else + type = LOCAL_VAR; label = vdf.getName() + " : " + ASTGenerator.extracTypeInfo2(vdf); } @@ -104,6 +117,7 @@ public CompletionCandidate(Field f) { label = f.getName() + " : " + f.getType().getSimpleName() + " - " + f.getDeclaringClass().getSimpleName(); completionString = elementName; + wrappedObject = f; } public CompletionCandidate(String name, String labelStr, String completionStr, int type) { diff --git a/src/processing/mode/experimental/CompletionPanel.java b/src/processing/mode/experimental/CompletionPanel.java index f054ccf..00ee4e1 100644 --- a/src/processing/mode/experimental/CompletionPanel.java +++ b/src/processing/mode/experimental/CompletionPanel.java @@ -1,40 +1,93 @@ +/* + * 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.Dimension; +import java.awt.Component; +import java.awt.FontMetrics; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import javax.swing.BorderFactory; import javax.swing.DefaultListModel; +import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; 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; - public CompletionPanel(JEditTextArea textarea, int position, String subWord, - DefaultListModel items, Point location) { + /** + * 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; + editor = dedit; this.insertionPosition = position; if (subWord.indexOf('.') != -1) this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1); @@ -47,12 +100,13 @@ public CompletionPanel(JEditTextArea textarea, int position, String subWord, scrollPane = new JScrollPane(); scrollPane.setViewportView(completionList = createSuggestionList(position, items)); popupMenu.add(scrollPane, BorderLayout.CENTER); - popupMenu.setPopupSize(280, 250); //TODO: Eradicate this evil - this.textarea.errorCheckerService.astGenerator + popupMenu.setPopupSize(280, setHeight(items.getSize())); //TODO: Eradicate this evil + this.textarea.errorCheckerService.getASTGenerator() .updateJavaDoc((CompletionCandidate) completionList.getSelectedValue()); + 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() { @@ -63,12 +117,44 @@ public void setVisible(boolean v){ log("Pred popup visible."); popupMenu.setVisible(v); } + + 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 + protected int setWidth(){ + if(scrollPane.getVerticalScrollBar().isVisible()) return 280; + float min = 280; + FontMetrics fm = textarea.getFontMetrics(textarea.getFont()); + for (int i = 0; i < completionList.getModel().getSize(); i++) { + float h = fm.stringWidth(completionList.getModel().getElementAt(i).toString()); + min = Math.min(min, h); + } + min += fm.stringWidth(" "); + log("popup width " + Math.min(280,min)); + 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) { JList list = new JList(items); - list.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1)); + //list.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1)); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.setSelectedIndex(0); list.addMouseListener(new MouseAdapter() { @@ -76,69 +162,184 @@ private JList createSuggestionList(final int position, public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { insertSelection(); - hideSuggestion(); + hide(); } } }); + list.setCellRenderer(new CustomListRenderer()); + list.setFocusable(false); return list; } - public boolean updateList(final DefaultListModel items, String newSubword, int position){ - scrollPane.getViewport().removeAll(); - Dimension dimen = popupMenu.getSize(); - completionList.setModel(items); - completionList.validate(); - completionList.setSelectedIndex(0); - scrollPane.setViewportView(completionList); - scrollPane.validate(); - popupMenu.setSize(dimen); - + // possibly defunct + public boolean updateList(final DefaultListModel items, String newSubword, + final Point location, int position) { this.subWord = new String(newSubword); if (subWord.indexOf('.') != -1) this.subWord = subWord.substring(subWord.lastIndexOf('.') + 1); insertionPosition = position; - log("Suggestion updated" + System.nanoTime()); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + scrollPane.getViewport().removeAll(); + completionList.setModel(items); + completionList.setSelectedIndex(0); + scrollPane.setViewportView(completionList); + popupMenu.setPopupSize(popupMenu.getSize().width, setHeight(items.getSize())); + //log("Suggestion updated" + System.nanoTime()); + textarea.requestFocusInWindow(); + popupMenu.show(textarea, location.x, textarea.getBaseline(0, 0) + + location.y); + completionList.validate(); + scrollPane.validate(); + popupMenu.validate(); + } + }); 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(); } - hideSuggestion(); + 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; - public void hideSuggestion() { + 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()); - //textarea.errorCheckerService.astGenerator.jdocWindowVisible(false); + //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()); @@ -154,11 +355,14 @@ public void moveUp() { .getVerticalScrollBar() .getValue() - step); - textarea.errorCheckerService.astGenerator + textarea.errorCheckerService.getASTGenerator() .updateJavaDoc((CompletionCandidate) completionList.getSelectedValue()); } + /** + * 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); @@ -169,7 +373,7 @@ public void moveDown() { .getSize() - 1); selectIndex(index); } - textarea.errorCheckerService.astGenerator + textarea.errorCheckerService.getASTGenerator() .updateJavaDoc((CompletionCandidate) completionList.getSelectedValue()); int step = scrollPane.getVerticalScrollBar().getMaximum() / completionList.getModel().getSize(); @@ -189,4 +393,55 @@ private void selectIndex(int index) { // }; // }); } + + + /** + * 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; + + public Component getListCellRendererComponent(JList list, Object value, + int index, + boolean isSelected, + boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, + index, + isSelected, + cellHasFocus); + if (value instanceof CompletionCandidate) { + CompletionCandidate cc = (CompletionCandidate) value; + switch (cc.getType()) { + case CompletionCandidate.LOCAL_VAR: + label.setIcon(editor.dmode.localVarIcon); + break; + case CompletionCandidate.LOCAL_FIELD: + case CompletionCandidate.PREDEF_FIELD: + label.setIcon(editor.dmode.fieldIcon); + break; + case CompletionCandidate.LOCAL_METHOD: + case CompletionCandidate.PREDEF_METHOD: + label.setIcon(editor.dmode.methodIcon); + break; + case CompletionCandidate.LOCAL_CLASS: + case CompletionCandidate.PREDEF_CLASS: + label.setIcon(editor.dmode.classIcon); + break; + + default: + log("(CustomListRenderer)Unknown CompletionCandidate type " + cc.getType()); + break; + } + + } + else + log("(CustomListRenderer)Unknown CompletionCandidate object " + value); + + return label; + } + } + } \ No newline at end of file diff --git a/src/processing/mode/experimental/DebugEditor.java b/src/processing/mode/experimental/DebugEditor.java index ce341b7..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 @@ -21,27 +21,46 @@ import java.awt.BorderLayout; 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; import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; 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; @@ -52,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; @@ -166,6 +186,13 @@ public class DebugEditor extends JavaEditor implements ActionListener { */ protected JCheckBoxMenuItem writeErrorLog; + /** + * Enable/Disable code completion + */ + protected JCheckBoxMenuItem completionsEnabled; + + protected AutoSaveUtil autosaver; + public DebugEditor(Base base, String path, EditorState state, Mode mode) { super(base, path, state, mode); @@ -231,8 +258,8 @@ public void actionPerformed(ActionEvent e) { initializeErrorChecker(); ta.setECSandThemeforTextArea(errorCheckerService, dmode); addXQModeUI(); - //TODO: Remove this later - setBounds(160, 300, getWidth(), getHeight()); + debugToolbarEnabled = new AtomicBoolean(false); + //log("Sketch Path: " + path); } private void addXQModeUI(){ @@ -285,6 +312,16 @@ private void addXQModeUI(){ consoleProblemsPane.add(errorTableScrollPane, XQConsoleToggle.ERRORSLIST); consoleProblemsPane.add(console, XQConsoleToggle.CONSOLE); consolePanel.add(consoleProblemsPane, BorderLayout.CENTER); + + // ensure completion gets hidden on editor losing focus + addWindowFocusListener(new WindowFocusListener() { + public void windowLostFocus(WindowEvent e) { + ta.hideSuggestion(); + } + public void windowGainedFocus(WindowEvent e) { + + } + }); } // /** @@ -308,22 +345,28 @@ private void addXQModeUI(){ public void dispose() { //System.out.println("window dispose"); // quit running debug session - dbg.stopDebug(); + dbg.stopDebug(); // remove var.inspector vi.dispose(); - + errorCheckerService.stopThread(); // original dispose super.dispose(); } // Added temporarily to dump error log. TODO: Remove this later public void internalCloseRunner(){ - if(enableErrorLogging) writeErrorsToFile(); + if(ExperimentalMode.errorLogsEnabled) writeErrorsToFile(); +// if(autosaver != null && !viewingAutosaveBackup) { +// log("stopping autosaver in internalCloseRunner"); +// autosaver.stop(); +// } super.internalCloseRunner(); } - protected boolean enableErrorLogging = false; - + /** + * Writes all error messages to a csv file. + * For analytics purposes only. + */ private void writeErrorsToFile(){ if (errorCheckerService.tempErrorLog.size() == 0) return; @@ -367,7 +410,7 @@ private void writeErrorsToFile(){ * * @return the sketch menu */ - @Override + /*@Override public JMenu buildSketchMenu() { JMenuItem runItem = Toolkit.newJMenuItemShift(DebugToolbar.getTitle(DebugToolbar.RUN, false), KeyEvent.VK_R); runItem.addActionListener(new ActionListener() { @@ -393,6 +436,59 @@ 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()){ + // switch to java + if(javaToolbar == null) + javaToolbar = createToolbar(); + nextToolbar = javaToolbar; + debugToolbarEnabled.set(false); + log("Switching to Java Mode Toolbar"); + } + else{ + // switch to debug + if(debugToolbar == null) + debugToolbar = new DebugToolbar(this, getBase()); + nextToolbar = debugToolbar; + debugToolbarEnabled.set(true); + log("Switching to Debugger Toolbar"); + } + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + Box upper = (Box)splitPane.getComponent(0); + upper.remove(0); + upper.add(nextToolbar, 0); + upper.validate(); + nextToolbar.repaint(); + toolbar = nextToolbar; + // The toolbar responds to shift down/up events + // in order to show the alt version of toolbar buttons. + // With toolbar switch, KeyListener has to be changed as well + for (KeyListener kl : textarea.getKeyListeners()) { + if(kl instanceof EditorToolbar) + { + textarea.removeKeyListener(kl); + textarea.addKeyListener(toolbar); + break; + } + } + ta.repaint(); + } + }); } /** @@ -402,9 +498,18 @@ public void actionPerformed(ActionEvent e) { * @return The debug menu */ protected JMenu buildDebugMenu() { - debugMenu = new JMenu("Debug"); + //debugMenu = new JMenu("Debug"); + debugMenu = new JMenu("PDE X"); - debugMenuItem = Toolkit.newJMenuItem("Debug", KeyEvent.VK_R); + JCheckBoxMenuItem toggleDebugger = new JCheckBoxMenuItem("Show Debug Toolbar"); + toggleDebugger.setSelected(false); + toggleDebugger.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + switchToolbars(); + } + }); + debugMenu.add(toggleDebugger); + debugMenuItem = Toolkit.newJMenuItemAlt("Debug", KeyEvent.VK_R); debugMenuItem.addActionListener(this); continueMenuItem = Toolkit.newJMenuItem("Continue", KeyEvent.VK_U); continueMenuItem.addActionListener(this); @@ -460,36 +565,20 @@ protected JMenu buildDebugMenu() { // XQMode menu items JCheckBoxMenuItem item; - final DebugEditor thisEditor = this; item = new JCheckBoxMenuItem("Error Checker Enabled"); - item.setSelected(true); + item.setSelected(ExperimentalMode.errorCheckEnabled); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - - if (!((JCheckBoxMenuItem) e.getSource()).isSelected()) { - // unticked Menu Item - errorCheckerService.pauseThread(); - System.out.println(thisEditor.getSketch().getName() - + " - Error Checker paused."); - errorBar.errorPoints.clear(); - errorCheckerService.problemsList.clear(); - errorCheckerService.updateErrorTable(); - errorCheckerService.updateEditorStatus(); - getTextArea().repaint(); - errorBar.repaint(); - } else { - errorCheckerService.resumeThread(); - System.out.println(thisEditor.getSketch().getName() - + " - Error Checker resumed."); - errorCheckerService.runManualErrorCheck(); - } + ExperimentalMode.errorCheckEnabled = ((JCheckBoxMenuItem) e.getSource()).isSelected(); + errorCheckerService.handleErrorCheckingToggle(); + dmode.savePreferences(); } }); debugMenu.add(item); - problemWindowMenuCB = new JCheckBoxMenuItem("Show Problem Window"); + /*problemWindowMenuCB = new JCheckBoxMenuItem("Show Problem Window"); // problemWindowMenuCB.setSelected(true); problemWindowMenuCB.addActionListener(new ActionListener() { @@ -505,21 +594,34 @@ public void actionPerformed(ActionEvent e) { showProblemListView(XQConsoleToggle.CONSOLE); } }); - debugMenu.add(problemWindowMenuCB); + debugMenu.add(problemWindowMenuCB);*/ showWarnings = new JCheckBoxMenuItem("Warnings Enabled"); - showWarnings.setSelected(true); + showWarnings.setSelected(ExperimentalMode.warningsEnabled); showWarnings.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - errorCheckerService.warningsEnabled = ((JCheckBoxMenuItem) e + ExperimentalMode.warningsEnabled = ((JCheckBoxMenuItem) e .getSource()).isSelected(); errorCheckerService.runManualErrorCheck(); + dmode.savePreferences(); } }); debugMenu.add(showWarnings); + completionsEnabled = new JCheckBoxMenuItem("Code Completion Enabled"); + completionsEnabled.setSelected(ExperimentalMode.codeCompletionsEnabled); + completionsEnabled.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ExperimentalMode.codeCompletionsEnabled = (((JCheckBoxMenuItem) e + .getSource()).isSelected()); + dmode.savePreferences(); + } + }); + debugMenu.add(completionsEnabled); + debugMessagesEnabled = new JCheckBoxMenuItem("Show Debug Messages"); debugMessagesEnabled.setSelected(ExperimentalMode.DEBUG); debugMessagesEnabled.addActionListener(new ActionListener() { @@ -527,6 +629,7 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { ExperimentalMode.DEBUG = ((JCheckBoxMenuItem) e .getSource()).isSelected(); + dmode.savePreferences(); } }); debugMenu.add(debugMessagesEnabled); @@ -536,16 +639,27 @@ public void actionPerformed(ActionEvent e) { debugMenu.add(showOutline); writeErrorLog = new JCheckBoxMenuItem("Write Errors to Log"); - writeErrorLog.setSelected(enableErrorLogging); + writeErrorLog.setSelected(ExperimentalMode.errorLogsEnabled); writeErrorLog.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - enableErrorLogging = !enableErrorLogging; + ExperimentalMode.errorLogsEnabled = ((JCheckBoxMenuItem) e + .getSource()).isSelected(); + dmode.savePreferences(); } }); debugMenu.add(writeErrorLog); - + debugMenu.addSeparator(); + JMenuItem jitem = new JMenuItem("PDE X on GitHub"); + jitem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Base.openURL("https://github.com/processing/processing-experimental"); + } + }); + debugMenu.add(jitem); + return debugMenu; } @@ -611,7 +725,7 @@ public void actionPerformed(ActionEvent ae) { toggleVariableInspector(); } else if (source.equals(showOutline)){ log("Show Outline :D"); - errorCheckerService.astGenerator.showSketchOutline(); + errorCheckerService.getASTGenerator().showSketchOutline(); } } @@ -641,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) @@ -648,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; } @@ -725,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++) { @@ -752,6 +904,8 @@ public void run() { }); } } + // if file location has changed, update autosaver + // autosaver.reloadAutosaveDir(); return saved; } @@ -780,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 @@ -894,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. * @@ -904,7 +1208,9 @@ public VariableInspector variableInspector() { } public DebugToolbar toolbar() { + if(toolbar instanceof DebugToolbar) return (DebugToolbar) toolbar; + return null; } /** @@ -1174,10 +1480,10 @@ public Document currentDocument() { * * @return the toolbar */ - @Override + /*@Override public EditorToolbar createToolbar() { return new DebugToolbar(this, base); - } + }*/ /** * Event Handler for double clicking in the left hand gutter area. @@ -1198,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; /** @@ -1250,16 +1604,31 @@ 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() { - System.out.println("Caret at:"); - System.out.println(ta.getLineText(ta.getCaretLine())); - errorCheckerService.astGenerator.handleRefactor(); + log("Caret at:"); + log(ta.getLineText(ta.getCaretLine())); + errorCheckerService.getASTGenerator().handleRefactor(); } + /** + * Handle show usage operation + */ private void handleShowUsage() { - System.out.println("Caret at:"); - System.out.println(ta.getLineText(ta.getCaretLine())); - errorCheckerService.astGenerator.handleShowUsage(); + log("Caret at:"); + log(ta.getLineText(ta.getCaretLine())); + errorCheckerService.getASTGenerator().handleShowUsage(); } /** diff --git a/src/processing/mode/experimental/DebugToolbar.java b/src/processing/mode/experimental/DebugToolbar.java index 3738b9d..c9616e1 100755 --- a/src/processing/mode/experimental/DebugToolbar.java +++ b/src/processing/mode/experimental/DebugToolbar.java @@ -17,12 +17,16 @@ */ package processing.mode.experimental; +import java.awt.Graphics; import java.awt.Image; import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; import java.util.logging.Level; import java.util.logging.Logger; import processing.app.Base; import processing.app.Editor; +import processing.app.Preferences; +import processing.app.Toolkit; import processing.mode.java.JavaToolbar; /** @@ -64,14 +68,62 @@ public class DebugToolbar extends JavaToolbar { public DebugToolbar(Editor editor, Base base) { super(editor, base); } - + public Image[][] loadDebugImages() { + int res = Toolkit.highResDisplay() ? 2 : 1; + + String suffix = null; + Image allButtons = null; + // Some modes may not have a 2x version. If a mode doesn't have a 1x + // version, this will cause an error... they should always have 1x. + if (res == 2) { + suffix = "-2x.png"; + allButtons = mode.loadImage("theme/buttons-debug" + suffix); + if (allButtons == null) { + res = 1; // take him down a notch + } + } + if (res == 1) { + suffix = ".png"; + allButtons = mode.loadImage("theme/buttons-debug" + suffix); + if (allButtons == null) { + // use the old (pre-2.0b9) file name + suffix = ".gif"; + allButtons = mode.loadImage("theme/buttons-debug" + suffix); + } + } + + // The following three final fields were not accessible, so just copied the values here + // for the time being. TODO: inform Ben, make these fields public + /** Width of each toolbar button. */ + final int BUTTON_WIDTH = 27; + /** Size (both width and height) of the buttons in the source image. */ + final int BUTTON_IMAGE_SIZE = 33; + int count = allButtons.getWidth(this) / BUTTON_WIDTH*res; + final int GRID_SIZE = 32; + + Image[][] buttonImages = new Image[count][3]; + + for (int i = 0; i < count; i++) { + for (int state = 0; state < 3; state++) { + Image image = new BufferedImage(BUTTON_WIDTH*res, GRID_SIZE*res, BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + g.drawImage(allButtons, + -(i*BUTTON_IMAGE_SIZE*res) - 3, + (state-2)*BUTTON_IMAGE_SIZE*res, null); + g.dispose(); + buttonImages[i][state] = image; + } + } + + return buttonImages; + } /** * Initialize buttons. Loads images and adds the buttons to the toolbar. */ @Override public void init() { - Image[][] images = loadImages(); + Image[][] images = loadDebugImages(); for (int idx = 0; idx < buttonSequence.length; idx++) { int id = buttonId(idx); addButton(getTitle(id, false), getTitle(id, true), images[idx], id == NEW || id == TOGGLE_BREAKPOINT); diff --git a/src/processing/mode/experimental/Debugger.java b/src/processing/mode/experimental/Debugger.java index 758f6cf..50b5d98 100755 --- a/src/processing/mode/experimental/Debugger.java +++ b/src/processing/mode/experimental/Debugger.java @@ -168,7 +168,7 @@ public synchronized void startDebug() { // load edits into sketch obj, etc... editor.prepareRun(); - + if(editor.toolbar() != null) editor.toolbar().activate(DebugToolbar.DEBUG); // after prepareRun, since this removes highlights try { @@ -236,9 +236,11 @@ public synchronized void stopDebug() { } stopTrackingLineChanges(); started = false; - editor.toolbar().deactivate(DebugToolbar.DEBUG); - editor.toolbar().deactivate(DebugToolbar.CONTINUE); - editor.toolbar().deactivate(DebugToolbar.STEP); + if(editor.toolbar() != null){ + editor.toolbar().deactivate(DebugToolbar.DEBUG); + editor.toolbar().deactivate(DebugToolbar.CONTINUE); + editor.toolbar().deactivate(DebugToolbar.STEP); + } editor.statusEmpty(); } @@ -246,7 +248,8 @@ public synchronized void stopDebug() { * Resume paused debugging session. Resumes VM. */ public synchronized void continueDebug() { - editor.toolbar().activate(DebugToolbar.CONTINUE); + if(editor.toolbar() != null) + editor.toolbar().activate(DebugToolbar.CONTINUE); editor.variableInspector().lock(); //editor.clearSelection(); //clearHighlight(); @@ -271,7 +274,8 @@ protected void step(int stepDepth) { startDebug(); } else if (isPaused()) { editor.variableInspector().lock(); - editor.toolbar().activate(DebugToolbar.STEP); + if(editor.toolbar() != null) + editor.toolbar().activate(DebugToolbar.STEP); // use global to mark that there is a step request pending requestedStep = runtime.vm().eventRequestManager().createStepRequest(currentThread, StepRequest.STEP_LINE, stepDepth); @@ -588,8 +592,10 @@ public synchronized void vmEvent(EventSet es) { @Override public void run() { editor.setCurrentLine(newCurrentLine); - editor.toolbar().deactivate(DebugToolbar.STEP); - editor.toolbar().deactivate(DebugToolbar.CONTINUE); + if(editor.toolbar() != null){ + editor.toolbar().deactivate(DebugToolbar.STEP); + editor.toolbar().deactivate(DebugToolbar.CONTINUE); + } } }); @@ -616,8 +622,10 @@ public void run() { @Override public void run() { editor.setCurrentLine(newCurrentLine); - editor.toolbar().deactivate(DebugToolbar.STEP); - editor.toolbar().deactivate(DebugToolbar.CONTINUE); + if(editor.toolbar() != null){ + editor.toolbar().deactivate(DebugToolbar.STEP); + editor.toolbar().deactivate(DebugToolbar.CONTINUE); + } } }); diff --git a/src/processing/mode/experimental/ErrorBar.java b/src/processing/mode/experimental/ErrorBar.java index 3413813..974f3ab 100755 --- a/src/processing/mode/experimental/ErrorBar.java +++ b/src/processing/mode/experimental/ErrorBar.java @@ -35,6 +35,7 @@ import javax.swing.JPanel; import javax.swing.SwingWorker; +import javax.swing.text.BadLocationException; import processing.app.Base; import processing.app.SketchCode; @@ -109,12 +110,12 @@ public void paintComponent(Graphics g) { g.fillRect(0, 0, getWidth(), getHeight()); for (ErrorMarker emarker : errorPoints) { - if (emarker.type == ErrorMarker.Error) { + if (emarker.getType() == ErrorMarker.Error) { g.setColor(errorColor); } else { g.setColor(warningColor); } - g.fillRect(2, emarker.y, (getWidth() - 3), errorMarkerHeight); + g.fillRect(2, emarker.getY(), (getWidth() - 3), errorMarkerHeight); } } @@ -152,56 +153,48 @@ synchronized public void updateErrorPoints(final ArrayList problems) { final int fheight = this.getHeight(); SwingWorker worker = new SwingWorker() { - protected Object doInBackground() throws Exception { - return null; - } + protected Object doInBackground() throws Exception { + SketchCode sc = editor.getSketch().getCurrentCode(); + int totalLines = 0, currentTab = editor.getSketch() + .getCurrentCodeIndex(); + try { + totalLines = Base.countLines(sc.getDocument() + .getText(0, sc.getDocument().getLength())) + 1; + } catch (BadLocationException e) { + e.printStackTrace(); + } + // System.out.println("Total lines: " + totalLines); + synchronized (errorPoints) { + errorPointsOld.clear(); + for (ErrorMarker marker : errorPoints) { + errorPointsOld.add(marker); + } + errorPoints.clear(); + + // Each problem.getSourceLine() will have an extra line added + // because of + // class declaration in the beginning as well as default imports + synchronized (problems) { + for (Problem problem : problems) { + if (problem.getTabIndex() == currentTab) { + // Ratio of error line to total lines + float y = (problem.getLineNumber() - errorCheckerService.defaultImportsOffset) + / ((float) totalLines); + // Ratio multiplied by height of the error bar + y *= fheight - 15; // -15 is just a vertical offset + errorPoints + .add(new ErrorMarker(problem, (int) y, + problem.isError() ? ErrorMarker.Error + : ErrorMarker.Warning)); + // System.out.println("Y: " + y); + } + } + } + } + return null; + } protected void done() { - int totalLines = 0; - int currentTab = 0; - for (SketchCode sc : editor.getSketch().getCode()) { - if (sc.isExtension("pde")) { - try { - if (editor.getSketch().getCurrentCode().equals(sc)) { - // Adding + 1 to len because \n gets appended - // for each - // sketchcode extracted during processPDECode() - totalLines = Base.countLines(sc.getDocument() - .getText(0, - sc.getDocument().getLength())) + 1; - break; - } - } catch (Exception e) { - e.printStackTrace(); - } - } - currentTab++; - } - // System.out.println("Total lines: " + totalLines); - - errorPointsOld.clear(); - for (ErrorMarker marker : errorPoints) { - errorPointsOld.add(marker); - } - errorPoints.clear(); - - // Each problem.getSourceLine() will have an extra line added - // because of - // class declaration in the beginning as well as default imports - for (Problem problem : problems) { - if (problem.tabIndex == currentTab) { - // Ratio of error line to total lines - float y = (problem.lineNumber - errorCheckerService.defaultImportsOffset) - / ((float) totalLines); - // Ratio multiplied by height of the error bar - y *= fheight - 15; // -15 is just a vertical offset - errorPoints.add(new ErrorMarker(problem, (int) y, - problem.isError() ? ErrorMarker.Error - : ErrorMarker.Warning)); - // System.out.println("Y: " + y); - } - } - repaint(); } }; @@ -229,7 +222,7 @@ public boolean errorPointsChanged() { else { for (int i = 0; i < errorPoints.size(); i++) { - if (errorPoints.get(i).y != errorPointsOld.get(i).y) { + if (errorPoints.get(i).getY() != errorPointsOld.get(i).getY()) { editor.getTextArea().repaint(); // System.out.println("3 Repaint " + // System.currentTimeMillis()); @@ -252,48 +245,21 @@ protected void addListeners() { @SuppressWarnings("rawtypes") @Override public void mouseClicked(final MouseEvent e) { - SwingWorker worker = new SwingWorker() { - - protected Object doInBackground() throws Exception { - return null; - } - - protected void done() { - for (ErrorMarker eMarker : errorPoints) { - // -2 and +2 are extra allowance, clicks in the - // vicinity of the markers register that way - if (e.getY() >= eMarker.y - 2 - && e.getY() <= eMarker.y + 2 - + errorMarkerHeight) { - int currentTabErrorIndex = errorPoints - .indexOf(eMarker); - // System.out.println("Index: " + - // currentTabErrorIndex); - int currentTab = editor.getSketch() - .getCodeIndex( - editor.getSketch() - .getCurrentCode()); - - int totalErrorIndex = currentTabErrorIndex; - - for (int i = 0; i < errorCheckerService.problemsList - .size(); i++) { - Problem p = errorCheckerService.problemsList - .get(i); - if (p.tabIndex < currentTab) { - totalErrorIndex++; - } - if (p.tabIndex == currentTab) { - break; - } - } - errorCheckerService - .scrollToErrorLine(totalErrorIndex); - } - } - - } - }; + SwingWorker worker = new SwingWorker() { + + protected Object doInBackground() throws Exception { + for (ErrorMarker eMarker : errorPoints) { + // -2 and +2 are extra allowance, clicks in the + // vicinity of the markers register that way + if (e.getY() >= eMarker.getY() - 2 + && e.getY() <= eMarker.getY() + 2 + errorMarkerHeight) { + errorCheckerService.scrollToErrorLine(eMarker.getProblem()); + return null; + } + } + return null; + } + }; try { worker.execute(); @@ -311,59 +277,26 @@ protected void done() { @SuppressWarnings("rawtypes") @Override - public void mouseMoved(final MouseEvent e) { - // System.out.println(e); - SwingWorker worker = new SwingWorker() { - - protected Object doInBackground() throws Exception { - return null; - } - - protected void done() { - - for (ErrorMarker eMarker : errorPoints) { - if (e.getY() >= eMarker.y - 2 - && e.getY() <= eMarker.y + 2 - + errorMarkerHeight) { - // System.out.println("Index: " + - // errorPoints.indexOf(y)); - int currentTab = editor.getSketch() - .getCodeIndex( - editor.getSketch() - .getCurrentCode()); - int currentTabErrorCount = 0; - - for (int i = 0; i < errorPoints.size(); i++) { - Problem p = errorPoints.get(i).problem; - if (p.tabIndex == currentTab) { - if (currentTabErrorCount == errorPoints - .indexOf(eMarker)) { - // System.out.println("Roger that."); - String msg = (p.isError() ? "Error: " - : "Warning: ") - + p.message; - setToolTipText(msg); - setCursor(Cursor - .getPredefinedCursor(Cursor.HAND_CURSOR)); - return; - } else { - currentTabErrorCount++; - // System.out.println("Still looking.."); - } - } - - } - } - // Reset cursor and tooltip - else { - setToolTipText(""); - setCursor(Cursor - .getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - } - } - - } - }; + public void mouseMoved(final MouseEvent evt) { + // System.out.println(e); + SwingWorker worker = new SwingWorker() { + + protected Object doInBackground() throws Exception { + for (ErrorMarker eMarker : errorPoints) { + if (evt.getY() >= eMarker.getY() - 2 + && evt.getY() <= eMarker.getY() + 2 + errorMarkerHeight) { + Problem p = eMarker.getProblem(); + String msg = (p.isError() ? "Error: " : "Warning: ") + + p.getMessage(); + setToolTipText(msg); + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + break; + } + } + return null; + } + }; + try { worker.execute(); } catch (Exception exp) { diff --git a/src/processing/mode/experimental/ErrorCheckerService.java b/src/processing/mode/experimental/ErrorCheckerService.java index 1203bda..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,7 +36,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.table.DefaultTableModel; +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; @@ -32,14 +54,22 @@ 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{ - private DebugEditor editor; + protected DebugEditor editor; /** * Error check happens every sleepTime milliseconds */ @@ -48,24 +78,24 @@ public class ErrorCheckerService implements Runnable{ /** * The amazing eclipse ast parser */ - private ASTParser parser; + protected ASTParser parser; /** * Used to indirectly stop the Error Checker Thread */ - public boolean stopThread = false; + protected AtomicBoolean stopThread; /** * If true, Error Checking is paused. Calls to checkCode() become useless. */ - private boolean pauseThread = false; + protected AtomicBoolean pauseThread; - protected ErrorWindow errorWindow; + //protected ErrorWindow errorWindow; /** * IProblem[] returned by parser stored in here */ - private IProblem[] problems; + protected IProblem[] problems; /** * Class name of current sketch @@ -108,13 +138,23 @@ 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 * items. */ - private boolean loadCompClass = true; + protected boolean loadCompClass = true; /** * Compiler Checker class. Note that methods for compilation checking are @@ -136,14 +176,14 @@ public class ErrorCheckerService implements Runnable{ /** * Timestamp - for measuring total overhead */ - private long lastTimeStamp = System.currentTimeMillis(); + protected long lastTimeStamp = System.currentTimeMillis(); /** * Used for displaying the rotating slash on the Problem Window title bar */ - private String[] slashAnimation = { "|", "/", "--", "\\", "|", "/", "--", + protected String[] slashAnimation = { "|", "/", "--", "\\", "|", "/", "--", "\\" }; - private int slashAnimationIndex = 0; + protected int slashAnimationIndex = 0; /** * Used to detect if the current tab index has changed and thus repaint the @@ -155,7 +195,7 @@ public class ErrorCheckerService implements Runnable{ * Stores the current import statements in the program. Used to compare for * changed import statements and update classpath if needed. */ - private ArrayList programImports; + protected ArrayList programImports; /** * List of imports when sketch was last checked. Used for checking for @@ -184,23 +224,35 @@ public class ErrorCheckerService implements Runnable{ protected ErrorMessageSimplifier errorMsgSimplifier; public ErrorCheckerService(DebugEditor debugEditor) { + ensureMinP5Version(); this.editor = debugEditor; + stopThread = new AtomicBoolean(false); + pauseThread = new AtomicBoolean(false); + + problemsList = new ArrayList(); + classpathJars = new ArrayList(); + initParser(); - initializeErrorWindow(); + //initializeErrorWindow(); xqpreproc = new XQPreprocessor(); PdePreprocessor pdePrepoc = new PdePreprocessor(null); defaultImportsOffset = pdePrepoc.getCoreImports().length + pdePrepoc.getDefaultImports().length + 1; astGenerator = new ASTGenerator(this); syntaxErrors = new AtomicBoolean(true); + containsErrors = new AtomicBoolean(true); errorMsgSimplifier = new ErrorMessageSimplifier(); tempErrorLog = new TreeMap(); + sketchChangedListener = new SketchChangedListener(); +// for (final SketchCode sc : editor.getSketch().getCode()) { +// sc.getDocument().addDocumentListener(sketchChangedListener); +// } } /** * Initializes ASTParser */ - private void initParser() { + protected void initParser() { try { parser = ASTParser.newParser(AST.JLS4); } catch (Exception e) { @@ -217,7 +269,7 @@ private void initParser() { /** * Initialiazes the Error Window */ - public void initializeErrorWindow() { + /*public void initializeErrorWindow() { if (editor == null) { return; @@ -242,15 +294,29 @@ 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() { - stopThread = false; + stopThread.set(false); checkCode(); if(!hasSyntaxErrors()) editor.showProblemListView(XQConsoleToggle.CONSOLE); - while (!stopThread) { + // Make sure astGen has at least one CU to start with + // This is when the loaded sketch already has syntax errors. + // Completion wouldn't be complete, but it'd be still something + // better than nothing + astGenerator.buildAST(cu); + handleErrorCheckingToggle(); + while (!stopThread.get()) { try { // Take a nap. Thread.sleep(sleepTime); @@ -261,8 +327,8 @@ public void run() { updatePaintedThingys(); updateEditorStatus(); - - if (pauseThread) + updateSketchCodeListeners(); + if (pauseThread.get()) continue; if(textModified.get() == 0) continue; @@ -270,9 +336,33 @@ public void run() { checkCode(); checkForMissingImports(); } + + astGenerator.disposeAllWindows(); + compilationChecker = null; + checkerClass = null; + classLoader = null; + System.gc(); + logE("Thread stopped: " + editor.getSketch().getName()); + System.gc(); } - private void checkForMissingImports() { + protected void updateSketchCodeListeners() { + for (final SketchCode sc : editor.getSketch().getCode()) { + boolean flag = false; + for (DocumentListener dl : ((SyntaxDocument)sc.getDocument()).getDocumentListeners()) { + if(dl.equals(sketchChangedListener)){ + flag = true; + break; + } + } + if(!flag){ + log("Adding doc listener to " + sc.getPrettyName()); + sc.getDocument().addDocumentListener(sketchChangedListener); + } + } + } + + protected void checkForMissingImports() { for (Problem p : problemsList) { if(p.getMessage().endsWith(" cannot be resolved to a type"));{ int idx = p.getMessage().indexOf(" cannot be resolved to a type"); @@ -286,6 +376,11 @@ private void checkForMissingImports() { } protected ASTGenerator astGenerator; + + public ASTGenerator getASTGenerator() { + return astGenerator; + } + /** * This thing acts as an event queue counter of sort. * Since error checking happens on demand, anytime this counter @@ -298,22 +393,60 @@ private void checkForMissingImports() { * Triggers error check */ public void runManualErrorCheck() { + log("Error Check."); textModified.incrementAndGet(); } + + protected SketchChangedListener sketchChangedListener; + protected class SketchChangedListener implements DocumentListener{ + + private SketchChangedListener(){ + } + + @Override + public void insertUpdate(DocumentEvent e) { + if (ExperimentalMode.errorCheckEnabled){ + runManualErrorCheck(); + log("doc insert update, man error check.."); + } + } + + @Override + public void removeUpdate(DocumentEvent e) { + if (ExperimentalMode.errorCheckEnabled){ + runManualErrorCheck(); + log("doc remove update, man error check.."); + } + } + + @Override + public void changedUpdate(DocumentEvent e) { + if (ExperimentalMode.errorCheckEnabled){ + runManualErrorCheck(); + log("doc changed update, man error check.."); + } + } + + } - private boolean checkCode() { + 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. - astGenerator.buildAST(cu); + //if(hasSyntaxErrors()) astGenerator.buildAST(null); + if (!hasSyntaxErrors()) { + + } if (problems.length == 0 && editor.compilationCheckEnabled) { //mainClassOffset++; // just a hack. @@ -326,16 +459,25 @@ private boolean checkCode() { // } // log(sourceCode); // log("--------------------------"); - compileCheck(); + compileCheck(); log(editor.getSketch().getName() + "2 MCO " + mainClassOffset); } - updateErrorTable(); - editor.updateErrorBar(problemsList); - updateEditorStatus(); - editor.getTextArea().repaint(); - updatePaintedThingys(); + astGenerator.buildAST(cu); + if(ExperimentalMode.errorCheckEnabled){ + calcPDEOffsetsForProbList(); + updateErrorTable(); + editor.updateErrorBar(problemsList); + updateEditorStatus(); + editor.getTextArea().repaint(); + updatePaintedThingys(); + editor.updateErrorToggle(); + } + else + { + log("Error Check disabled, so not updating UI."); + } int x = textModified.get(); //log("TM " + x); if (x >= 2) { @@ -356,65 +498,113 @@ private boolean checkCode() { return false; } - private AtomicBoolean syntaxErrors; + protected AtomicBoolean syntaxErrors, containsErrors; public boolean hasSyntaxErrors(){ return syntaxErrors.get(); } + public boolean hasErrors(){ + return containsErrors.get(); + } + protected TreeMap tempErrorLog; - private void syntaxCheck() { + protected void syntaxCheck() { syntaxErrors.set(true); + containsErrors.set(true); parser.setSource(sourceCode.toCharArray()); parser.setKind(ASTParser.K_COMPILATION_UNIT); - @SuppressWarnings("unchecked") 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); - cu = (CompilationUnit) parser.createAST(null); - - // Store errors returned by the ast parser - problems = cu.getProblems(); - // log("Problem Count: " + problems.length); - // 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? - problemsList.add(p); + + if (cu == null) + cu = (CompilationUnit) parser.createAST(null); + else { + synchronized (cu) { + cu = (CompilationUnit) parser.createAST(null); + } + } + compilationUnitState = 1; + synchronized (problemsList) { + + // Store errors returned by the ast parser + problems = cu.getProblems(); + // log("Problem Count: " + problems.length); + // Populate the probList + problemsList = new ArrayList(); + for (int i = 0; i < problems.length; i++) { + 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()) { // log("arg " + j); // } - // log(p.toString()); + // log(p.toString()); + } + + 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); + } } - - if (problems.length == 0) - syntaxErrors.set(false); - else - syntaxErrors.set(true); - astGenerator.loadJars(); } + protected URLClassLoader classLoader; - private void compileCheck() { + + 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 + // Eclispe JDT compiler, and call its getErrorsAsObj method to obtain // errors. This way, I'm able to add the paths of contributed libraries // to the classpath of CompilationChecker, dynamically. The eclipse compiler - // needs all referenced libraries in the classpath. + // needs all referenced libraries in the classpath. Totally a hack. If you find + // a better method, do let me know. try { // NOTE TO SELF: If classpath contains null Strings - // URLClassLoader gets angry. Drops NPE bombs. + // URLClassLoader shoots NPE bullets. // If imports have changed, reload classes with new classpath. if (loadCompClass) { @@ -434,7 +624,7 @@ private void compileCheck() { FileFilter fileFilter = new FileFilter() { public boolean accept(File file) { return (file.getName().endsWith(".jar") && !file - .getName().startsWith("ExperimentalMode")); + .getName().startsWith(editor.getMode().getClass().getSimpleName())); } }; @@ -452,7 +642,11 @@ public boolean accept(File file) { for (int i = 0; i < jarFiles.length; i++) { classpath[ii++] = jarFiles[i].toURI().toURL(); } - + + compilationChecker = null; + checkerClass = null; + classLoader = null; + System.gc(); // log("CP Len -- " + classpath.length); classLoader = new URLClassLoader(classpath); // log("1."); @@ -461,7 +655,6 @@ public boolean accept(File file) { // log("2."); compilationChecker = checkerClass.newInstance(); - astGenerator.loadJars(); // Update jar files for completition list loadCompClass = false; } @@ -478,72 +671,174 @@ public boolean accept(File file) { if (errorList == null) { return; } - - problems = new DefaultProblem[errorList.length]; - - for (int i = 0; i < errorList.length; i++) { - - // for (int j = 0; j < errorList[i].length; j++) - // System.out.print(errorList[i][j] + ", "); - - problems[i] = new DefaultProblem((char[]) errorList[i][0], - (String) errorList[i][1], - ((Integer) errorList[i][2]).intValue(), - (String[]) errorList[i][3], - ((Integer) errorList[i][4]).intValue(), - ((Integer) errorList[i][5]).intValue(), - ((Integer) errorList[i][6]).intValue(), - ((Integer) errorList[i][7]).intValue(), 0); - - // System.out - // .println("ECS: " + problems[i].getMessage() + "," - // + problems[i].isError() + "," - // + problems[i].isWarning()); - - IProblem problem = problems[i]; -// log(problem.getMessage()); -// for (String j : problem.getArguments()) { -// log("arg " + j); -// } - int a[] = calculateTabIndexAndLineNumber(problem); - Problem p = new Problem(problem, a[0], a[1]); - if ((Boolean) errorList[i][8]) { - p.setType(Problem.ERROR); - } - - if ((Boolean) errorList[i][9]) { - p.setType(Problem.WARNING); - } - - // If warnings are disabled, skip 'em - if (p.isWarning() && !warningsEnabled) { - continue; + + synchronized (problemsList) { + problems = new DefaultProblem[errorList.length]; + + for (int i = 0; i < errorList.length; i++) { + + // for (int j = 0; j < errorList[i].length; j++) + // System.out.print(errorList[i][j] + ", "); + + problems[i] = new DefaultProblem((char[]) errorList[i][0], + (String) errorList[i][1], + ((Integer) errorList[i][2]).intValue(), + (String[]) errorList[i][3], + ((Integer) errorList[i][4]).intValue(), + ((Integer) errorList[i][5]).intValue(), + ((Integer) errorList[i][6]).intValue(), + ((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() + "," + // + problems[i].isError() + "," + // + problems[i].isWarning()); + + IProblem problem = problems[i]; + // log(problem.getMessage()); + // for (String j : problem.getArguments()) { + // log("arg " + j); + // } + int a[] = calculateTabIndexAndLineNumber(problem.getSourceLineNumber()); + Problem p = new Problem(problem, a[0], a[1]); + if ((Boolean) errorList[i][8]) { + p.setType(Problem.ERROR); + containsErrors.set(true); // set flag + } + + if ((Boolean) errorList[i][9]) { + p.setType(Problem.WARNING); + } + + // If warnings are disabled, skip 'em + if (p.isWarning() && !ExperimentalMode.warningsEnabled) { + continue; + } + problemsList.add(p); } - problemsList.add(p); } - } catch (ClassNotFoundException e) { System.err.println("Compiltation Checker files couldn't be found! " + e + " compileCheck() problem."); - stopThread(); + pauseThread(); } catch (MalformedURLException e) { System.err.println("Compiltation Checker files couldn't be found! " + e + " compileCheck() problem."); - stopThread(); + pauseThread(); } catch (Exception e) { System.err.println("compileCheck() problem." + e); e.printStackTrace(); - stopThread(); + pauseThread(); } catch (NoClassDefFoundError e) { System.err .println(e + " compileCheck() problem. Somebody tried to mess with Experimental Mode files."); - stopThread(); + pauseThread(); + } catch(OutOfMemoryError e) { + System.err.println("Processing has used up its maximum alloted memory. Please close some Processing " + + " windows and then reopen this sketch."); + pauseThread(); } + // 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++; + if(loadClassCounter > 100){ + loadClassCounter = 0; + classLoader = null; + System.gc(); + classLoader = new URLClassLoader(classpath); + } return classLoader; } @@ -554,57 +849,85 @@ public URLClassLoader getSketchClassLoader() { * messed up. * */ - private void prepareCompilerClasspath() { + protected void prepareCompilerClasspath() { if (!loadCompClass) { return; } - // log("1.."); - classpathJars = new ArrayList(); - String entry = ""; - boolean codeFolderChecked = false; - for (ImportStatement impstat : programImports) { - String item = impstat.getImportName(); - int dot = item.lastIndexOf('.'); - entry = (dot == -1) ? item : item.substring(0, dot); - - entry = entry.substring(6).trim(); - // log("Entry--" + entry); - if (ignorableImport(entry)) { - // log("Ignoring: " + entry); - continue; - } - Library library = null; - - // Try to get the library classpath and add it to the list - try { - library = editor.getMode().getLibrary(entry); - // log("lib->" + library.getClassPath() + "<-"); - String libraryPath[] = PApplet.split(library.getClassPath() - .substring(1).trim(), File.pathSeparatorChar); - for (int i = 0; i < libraryPath.length; i++) { - // log(entry + " ::" - // + new File(libraryPath[i]).toURI().toURL()); - classpathJars.add(new File(libraryPath[i]).toURI().toURL()); + synchronized (classpathJars) { + // log("1.."); + classpathJars = new ArrayList(); + String entry = ""; + boolean codeFolderChecked = false; + for (ImportStatement impstat : programImports) { + String item = impstat.getImportName(); + int dot = item.lastIndexOf('.'); + entry = (dot == -1) ? item : item.substring(0, dot); + + entry = entry.substring(6).trim(); + // log("Entry--" + entry); + if (ignorableImport(entry)) { + // log("Ignoring: " + entry); + continue; } - // log("-- "); - // classpath[count] = (new File(library.getClassPath() - // .substring(1))).toURI().toURL(); - // log(" found "); - // log(library.getClassPath().substring(1)); - } catch (Exception e) { - if (library == null && !codeFolderChecked) { - // log(1); - // Look around in the code folder for jar files - if (editor.getSketch().hasCodeFolder()) { - File codeFolder = editor.getSketch().getCodeFolder(); - - // get a list of .jar files in the "code" folder - // (class files in subfolders should also be picked up) - String codeFolderClassPath = Base - .contentsToClassPath(codeFolder); - codeFolderChecked = true; - if (codeFolderClassPath.equalsIgnoreCase("")) { + Library library = null; + + // Try to get the library classpath and add it to the list + try { + library = editor.getMode().getLibrary(entry); + // log("lib->" + library.getClassPath() + "<-"); + String libraryPath[] = PApplet.split(library.getClassPath() + .substring(1).trim(), File.pathSeparatorChar); + for (int i = 0; i < libraryPath.length; i++) { + // log(entry + " ::" + // + new File(libraryPath[i]).toURI().toURL()); + classpathJars.add(new File(libraryPath[i]).toURI().toURL()); + } + // log("-- "); + // classpath[count] = (new File(library.getClassPath() + // .substring(1))).toURI().toURL(); + // log(" found "); + // log(library.getClassPath().substring(1)); + } catch (Exception e) { + if (library == null && !codeFolderChecked) { + // log(1); + // Look around in the code folder for jar files + if (editor.getSketch().hasCodeFolder()) { + File codeFolder = editor.getSketch().getCodeFolder(); + + // get a list of .jar files in the "code" folder + // (class files in subfolders should also be picked up) + String codeFolderClassPath = Base + .contentsToClassPath(codeFolder); + codeFolderChecked = true; + if (codeFolderClassPath.equalsIgnoreCase("")) { + System.err.println("Experimental Mode: Yikes! Can't find \"" + + entry + + "\" library! Line: " + + impstat.getLineNumber() + + " in tab: " + + editor.getSketch().getCode(impstat.getTab()) + .getPrettyName()); + System.out + .println("Please make sure that the library is present in /libraries folder or in the code folder of your sketch"); + + } + String codeFolderPath[] = PApplet.split( + codeFolderClassPath.substring(1).trim(), + File.pathSeparatorChar); + try { + for (int i = 0; i < codeFolderPath.length; i++) { + classpathJars.add(new File(codeFolderPath[i]) + .toURI().toURL()); + } + + } catch (Exception e2) { + System.out + .println("Yikes! codefolder, prepareImports(): " + + e2); + } + } else { System.err.println("Experimental Mode: Yikes! Can't find \"" + entry + "\" library! Line: " @@ -615,47 +938,21 @@ private void prepareCompilerClasspath() { System.out .println("Please make sure that the library is present in /libraries folder or in the code folder of your sketch"); - - } - String codeFolderPath[] = PApplet.split( - codeFolderClassPath.substring(1).trim(), - File.pathSeparatorChar); - try { - for (int i = 0; i < codeFolderPath.length; i++) { - classpathJars.add(new File(codeFolderPath[i]) - .toURI().toURL()); - } - - } catch (Exception e2) { - System.out - .println("Yikes! codefolder, prepareImports(): " - + e2); } + } else { - System.err.println("Experimental Mode: Yikes! Can't find \"" - + entry - + "\" library! Line: " - + impstat.getLineNumber() - + " in tab: " - + editor.getSketch().getCode(impstat.getTab()) - .getPrettyName()); - System.out - .println("Please make sure that the library is present in /libraries folder or in the code folder of your sketch"); + System.err + .println("Yikes! There was some problem in prepareImports(): " + + e); + System.err.println("I was processing: " + entry); + + // e.printStackTrace(); } - - } else { - System.err - .println("Yikes! There was some problem in prepareImports(): " - + e); - System.err.println("I was processing: " + entry); - - // e.printStackTrace(); } + } - } - + astGenerator.loadJars(); // update jar file for completion lookup } /** @@ -679,11 +976,6 @@ protected boolean ignorableImport(String packageName) { @SuppressWarnings("rawtypes") protected Map compilerSettings; - /** - * Enable/Disable warnings from being shown - */ - public boolean warningsEnabled = true; - /** * Sets compiler options for JDT Compiler */ @@ -712,32 +1004,35 @@ protected void prepareCompilerSetting() { /** * Updates the error table in the Error Window. */ - synchronized public void updateErrorTable() { + public void updateErrorTable() { try { String[][] errorData = new String[problemsList.size()][3]; for (int i = 0; i < problemsList.size(); i++) { - errorData[i][0] = problemsList.get(i).message; ////TODO: this is temporary - //+ " : " + errorMsgSimplifier.getIDName(problemsList.get(i).getIProblem().getID()); + errorData[i][0] = problemsList.get(i).getMessage(); ////TODO: this is temporary + //+ " : " + errorMsgSimplifier.getIDName(problemsList.get(i).getIProblem().getID()); errorData[i][1] = editor.getSketch() - .getCode(problemsList.get(i).tabIndex).getPrettyName(); - errorData[i][2] = problemsList.get(i).lineNumber + ""; - + .getCode(problemsList.get(i).getTabIndex()).getPrettyName(); + 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).message,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 @@ -752,12 +1047,12 @@ synchronized public void updateErrorTable() { errorWindow.setTitle("Problems - " + editor.getSketch().getName() + " " + info); } - } + }*/ } catch (Exception e) { log("Exception at updateErrorTable() " + e); e.printStackTrace(); - stopThread(); + pauseThread(); } } @@ -778,33 +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()); - for (ErrorMarker emarker : editor.errorBar.errorPoints) { - if (emarker.problem.lineNumber == editor.getTextArea() - .getCaretLine() + 1) { - if (emarker.type == ErrorMarker.Warning) { - editor.statusNotice(emarker.problem.message); - //+ " : " + errorMsgSimplifier.getIDName(emarker.problem.getIProblem().getID())); - //TODO: this is temporary - } - else { - editor.statusError(emarker.problem.message); - //+ " : " + errorMsgSimplifier.getIDName(emarker.problem.getIProblem().getID())); + if(ExperimentalMode.errorCheckEnabled) + synchronized (editor.errorBar.errorPoints) { + for (ErrorMarker emarker : editor.errorBar.errorPoints) { + if (emarker.getProblem().getLineNumber() == editor.getTextArea() + .getCaretLine()) { + if (emarker.getType() == ErrorMarker.Warning) { + editor.statusMessage(emarker.getProblem().getMessage(), + DebugEditor.STATUS_INFO); + //+ " : " + errorMsgSimplifier.getIDName(emarker.problem.getIProblem().getID())); + //TODO: this is temporary + } + else { + editor.statusMessage(emarker.getProblem().getMessage(), + DebugEditor.STATUS_COMPILER_ERR); + //+ " : " + errorMsgSimplifier.getIDName(emarker.problem.getIProblem().getID())); + } + return; } - 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(); +// } } /** @@ -904,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 + ", " @@ -977,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 @@ -992,7 +1312,7 @@ public int[] calculateTabIndexAndLineNumber(IProblem problem) { * code is not yet compile ready. */ - private String preprocessCode(String pdeCode) { + protected String preprocessCode(String pdeCode) { programImports = new ArrayList(); @@ -1127,9 +1447,13 @@ private 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]); @@ -1171,17 +1495,12 @@ public void scrollToErrorLine(Problem p) { if (p == null) return; try { - editor.toFront(); - editor.getSketch().setCurrentCode(p.tabIndex); - - editor - .setSelection(editor.getTextArea() - .getLineStartNonWhiteSpaceOffset(p.lineNumber - 1) - + editor.getTextArea() - .getLineText(p.lineNumber - 1).trim().length(), - editor.getTextArea() - .getLineStartNonWhiteSpaceOffset(p.lineNumber - 1)); - editor.getTextArea().scrollTo(p.lineNumber - 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 @@ -1231,10 +1550,10 @@ public static boolean scrollToErrorLine(Editor edt, int tabIndex, int lineNoInTa * Checks if import statements in the sketch have changed. If they have, * compiler classpath needs to be updated. */ - private void checkForChangedImports() { - // log("Imports: " + programImports.size() + - // " Prev Imp: " - // + previousImports.size()); + protected void checkForChangedImports() { +// log("Imports: " + programImports.size() + +// " Prev Imp: " +// + previousImports.size()); if (programImports.size() != previousImports.size()) { // log(1); loadCompClass = true; @@ -1253,7 +1572,7 @@ private void checkForChangedImports() { // log("load..? " + loadCompClass); } - private int pdeImportsCount; + protected int pdeImportsCount; public int getPdeImportsCount() { return pdeImportsCount; @@ -1269,7 +1588,7 @@ public int getPdeImportsCount() { * - index of the tab * @return String - Tab code with imports replaced with white spaces */ - private String scrapImportStatements(String tabProgram, int tabNumber) { + protected String scrapImportStatements(String tabProgram, int tabNumber) { //TODO: Commented out imports are still detected as main imports. pdeImportsCount = 0; String tabSource = new String(tabProgram); @@ -1357,25 +1676,46 @@ public static String substituteUnicode(String program) { return new String(p2, 0, index); } + public void handleErrorCheckingToggle(){ + if (!ExperimentalMode.errorCheckEnabled) { + // unticked Menu Item + // pauseThread(); + log(editor.getSketch().getName() + + " - Error Checker paused."); + editor.errorBar.errorPoints.clear(); + problemsList.clear(); + updateErrorTable(); + updateEditorStatus(); + editor.getTextArea().repaint(); + editor.errorBar.repaint(); + } else { + //resumeThread(); + log(editor.getSketch().getName() + + " - Error Checker resumed."); + runManualErrorCheck(); + } + } + /** * Stops the Error Checker Service thread */ public void stopThread() { - stopThread = true; + logE("Stopping thread: " + editor.getSketch().getName()); + stopThread.set(true); } /** * Pauses the Error Checker Service thread */ public void pauseThread() { - pauseThread = true; + pauseThread.set(true); } /** * Resumes the Error Checker Service thread */ public void resumeThread() { - pauseThread = false; + pauseThread.set(false); } public DebugEditor getEditor() { diff --git a/src/processing/mode/experimental/ErrorMarker.java b/src/processing/mode/experimental/ErrorMarker.java index bc244f7..b3c7d94 100755 --- a/src/processing/mode/experimental/ErrorMarker.java +++ b/src/processing/mode/experimental/ErrorMarker.java @@ -6,14 +6,14 @@ * */ public class ErrorMarker { - /** + /** * y co-ordinate of the marker */ - public int y; + private int y; /** * Type of marker: Error or Warning? */ - public int type = -1; + private int type = -1; /** * Error Type constant */ @@ -26,11 +26,34 @@ public class ErrorMarker { * Problem that the error marker represents * @see Problem */ - public Problem problem; + private Problem problem; public ErrorMarker(Problem problem, int y, int type) { this.problem = problem; this.y = y; this.type = type; } + + /** + * y co-ordinate of the marker + */ + public int getY() { + return y; + } + + /** + * Type of marker: ErrorMarker.Error or ErrorMarker.Warning? + */ + public int getType() { + return type; + } + + /** + * Problem that the error marker represents + * @see Problem + */ + public Problem getProblem() { + return problem; + } + } \ No newline at end of file diff --git a/src/processing/mode/experimental/ErrorWindow.java b/src/processing/mode/experimental/ErrorWindow.java index db4e2a8..cc95f2e 100755 --- a/src/processing/mode/experimental/ErrorWindow.java +++ b/src/processing/mode/experimental/ErrorWindow.java @@ -183,7 +183,7 @@ public void windowDeiconified(WindowEvent e) { return; } - thisEditor.addWindowListener(new WindowAdapter() { + /*thisEditor.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { @@ -207,7 +207,7 @@ public void windowDeiconified(WindowEvent e) { thisErrorWindow.setExtendedState(Frame.NORMAL); } - }); + });*/ thisEditor.addComponentListener(new ComponentListener() { diff --git a/src/processing/mode/experimental/ExperimentalMode.java b/src/processing/mode/experimental/ExperimentalMode.java index 61d93d4..26cb99f 100755 --- a/src/processing/mode/experimental/ExperimentalMode.java +++ b/src/processing/mode/experimental/ExperimentalMode.java @@ -28,10 +28,14 @@ import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; + +import javax.swing.ImageIcon; + import processing.app.Base; import processing.app.Editor; import processing.app.EditorState; import processing.app.Mode; +import processing.app.Preferences; import processing.mode.java.JavaMode; @@ -43,7 +47,7 @@ public class ExperimentalMode extends JavaMode { public static final boolean VERBOSE_LOGGING = true; //public static final boolean VERBOSE_LOGGING = false; public static final int LOG_SIZE = 512 * 1024; // max log file size (in bytes) - public static boolean DEBUG = true; + public static boolean DEBUG = !true; public ExperimentalMode(Base base, File folder) { super(base, folder); @@ -96,6 +100,8 @@ public ExperimentalMode(Base base, File folder) { // String titleAndVersion = p.getImplementationTitle() + " (v" + p.getImplementationVersion() + ")"; // //log(titleAndVersion); // Logger.getLogger(ExperimentalMode.class.getName()).log(Level.INFO, titleAndVersion); + loadPreferences(); + loadIcons(); } @@ -110,6 +116,80 @@ public File[] getKeywordFiles() { Base.getContentFile("modes/java/keywords.txt") }; } + + 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, + autoSaveEnabled = true, autoSavePromptEnabled = true, + defaultAutoSaveEnabled = true; // ,untitledAutoSaveEnabled; + public static int autoSaveInterval = 3; //in minutes + + public static final String prefErrorCheck = "pdex.errorCheckEnabled", + prefWarnings = "pdex.warningsEnabled", + prefCodeCompletionEnabled = "pdex.ccEnabled", + 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"); + ensurePrefsExist(); + errorCheckEnabled = Preferences.getBoolean(prefErrorCheck); + warningsEnabled = Preferences.getBoolean(prefWarnings); + 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(){ + log("Saving PDEX prefs"); + Preferences.setBoolean(prefErrorCheck, errorCheckEnabled); + Preferences.setBoolean(prefWarnings, warningsEnabled); + 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(){ + if(Preferences.get(prefErrorCheck) == null) + Preferences.setBoolean(prefErrorCheck,errorCheckEnabled); + if(Preferences.get(prefWarnings) == null) + Preferences.setBoolean(prefWarnings,warningsEnabled); + if(Preferences.get(prefCodeCompletionEnabled) == null) + Preferences.setBoolean(prefCodeCompletionEnabled,codeCompletionsEnabled); + if(Preferences.get(prefDebugOP) == null) + 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); + } /** @@ -156,6 +236,21 @@ public Color getThemeColor(String attribute, Color defaultValue) { Logger.getLogger(ExperimentalMode.class.getName()).log(Level.WARNING, "Error loading Color: {0}", attribute); return defaultValue; } + + protected ImageIcon classIcon, fieldIcon, methodIcon, localVarIcon; + protected void loadIcons(){ + String iconPath = getContentFile("data") + .getAbsolutePath() + + File.separator + "icons"; + classIcon = new ImageIcon(iconPath + File.separator + "class_obj.png"); + methodIcon = new ImageIcon(iconPath + File.separator + + "methpub_obj.png"); + fieldIcon = new ImageIcon(iconPath + File.separator + + "field_protected_obj.png"); + localVarIcon = new ImageIcon(iconPath + File.separator + + "field_default_obj.png"); + log("Icons loaded"); + } public ClassLoader getJavaModeClassLoader() { @@ -192,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 60d0c0b..4f7e9c4 100644 --- a/src/processing/mode/experimental/Problem.java +++ b/src/processing/mode/experimental/Problem.java @@ -44,21 +44,25 @@ public class Problem { /** * The tab number to which the error belongs to */ - public int tabIndex; + private int tabIndex; /** * Line number(pde code) of the error */ - public int lineNumber; + private int lineNumber; + + private int lineStartOffset; + + private int lineStopOffset; /** * Error Message. Processed form of IProblem.getMessage() */ - public String message; + private String message; /** * The type of error - WARNING or ERROR. */ - public int type; + private int type; public static final int ERROR = 1, WARNING = 2; @@ -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(){ @@ -101,6 +119,14 @@ public String getMessage(){ public IProblem getIProblem(){ return iProblem; } + + public int getTabIndex(){ + return tabIndex; + } + + public int getLineNumber(){ + return lineNumber; + } public void setType(int ProblemType){ if(ProblemType == ERROR) diff --git a/src/processing/mode/experimental/SketchOutline.java b/src/processing/mode/experimental/SketchOutline.java index f753d15..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(), @@ -199,13 +201,13 @@ public void changedUpdate(DocumentEvent e) { private void updateSelection(){ SwingWorker worker = new SwingWorker() { protected Object doInBackground() throws Exception { - return null; - } - - protected void done() { String text = searchField.getText().toLowerCase(); tempNode = new DefaultMutableTreeNode(); filterTree(text, tempNode, soNode); + return null; + } + + protected void done() { soTree.setModel(new DefaultTreeModel(tempNode)); ((DefaultTreeModel) soTree.getModel()).reload(); for (int i = 0; i < soTree.getRowCount(); i++) { @@ -249,10 +251,6 @@ protected void done() { } DefaultMutableTreeNode tnode = (DefaultMutableTreeNode) soTree .getLastSelectedPathComponent(); - if (tnode.getUserObject() == null) { - return; - } - if (tnode.getUserObject() instanceof ASTNodeWrapper) { ASTNodeWrapper awrap = (ASTNodeWrapper) tnode.getUserObject(); // log(awrap); @@ -346,19 +344,6 @@ public boolean isVisible(){ protected class CustomCellRenderer extends DefaultTreeCellRenderer { - protected final ImageIcon classIcon, fieldIcon, methodIcon; - - public CustomCellRenderer() { - String iconPath = editor.getMode().getContentFile("data") - .getAbsolutePath() - + File.separator + "icons"; - classIcon = new ImageIcon(iconPath + File.separator + "class_obj.png"); - methodIcon = new ImageIcon(iconPath + File.separator - + "methpub_obj.png"); - fieldIcon = new ImageIcon(iconPath + File.separator - + "field_protected_obj.png"); - } - public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { @@ -378,11 +363,11 @@ public javax.swing.Icon getTreeIcon(Object o) { .getUserObject(); int type = awrap.getNode().getParent().getNodeType(); if (type == ASTNode.METHOD_DECLARATION) - return methodIcon; + return editor.dmode.methodIcon; if (type == ASTNode.TYPE_DECLARATION) - return classIcon; + return editor.dmode.classIcon; if (type == ASTNode.VARIABLE_DECLARATION_FRAGMENT) - return fieldIcon; + return editor.dmode.fieldIcon; } return null; } diff --git a/src/processing/mode/experimental/TextArea.java b/src/processing/mode/experimental/TextArea.java index 00e1282..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 @@ -34,6 +34,7 @@ import javax.swing.DefaultListModel; import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; import processing.app.syntax.JEditTextArea; import processing.app.syntax.TextAreaDefaults; @@ -126,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){ @@ -172,23 +177,54 @@ public void processKeyEvent(KeyEvent evt) { case KeyEvent.VK_BACK_SPACE: log("BK Key"); break; + case KeyEvent.VK_SPACE: + if (suggestion != null) + if (suggestion.isVisible()) { + log("Space bar, hide completion list"); + suggestion.hide(); + } + break; default: break; } } - super.processKeyEvent(evt); + super.processKeyEvent(evt); if (evt.getID() == KeyEvent.KEY_TYPED) { - errorCheckerService.runManualErrorCheck(); - log(" Typing: " + fetchPhrase(evt) + " " - + (evt.getKeyChar() == KeyEvent.VK_ENTER)); - + + char keyChar = evt.getKeyChar(); + if (keyChar == KeyEvent.VK_ENTER || keyChar == KeyEvent.VK_ESCAPE) { + return; + } else if (keyChar == KeyEvent.VK_SPACE || keyChar == KeyEvent.VK_TAB + || keyChar == KeyEvent.CHAR_UNDEFINED) { + return; + } + if(evt.isAltDown() || evt.isControlDown() || evt.isMetaDown()){ + return; + } + 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) + " T: " + System.currentTimeMillis()); + return null; + } + }; + worker.execute(); } } - + /** + * 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()); @@ -220,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 @@ -244,27 +281,20 @@ else if (s.length() == 0) if (Character.isDigit(word.charAt(0))) return null; log("Mouse click, word: " + word.trim()); - errorCheckerService.astGenerator.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) { - if (evt != null) { - char keyChar = evt.getKeyChar(); - if (keyChar == KeyEvent.VK_ENTER) { - //log("Enter keypress."); - return null; - } -// if (keyChar == KeyEvent.VK_BACK_SPACE || keyChar == KeyEvent.VK_DELETE) -// ; // accepted these keys - else if (keyChar == KeyEvent.VK_ESCAPE) { - //log("ESC keypress. fetchPhrase()"); - return null; - } else if (keyChar == KeyEvent.VK_TAB || keyChar == KeyEvent.CHAR_UNDEFINED) { - return null; - } - } + int off = getCaretPosition(); log2("off " + off); if (off < 0) @@ -294,8 +324,8 @@ else if (keyChar == KeyEvent.VK_ESCAPE) { word = word.trim(); if (word.endsWith(".")) word = word.substring(0, word.length() - 1); - if(word.length() > 1) - errorCheckerService.astGenerator.preparePredictions(word, line + + errorCheckerService.getASTGenerator().preparePredictions(word, line + errorCheckerService.mainClassOffset,0); return word; } @@ -348,32 +378,32 @@ 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.astGenerator.preparePredictions(word, line + errorCheckerService.getASTGenerator().preparePredictions(word, line + errorCheckerService.mainClassOffset,lineStartNonWSOffset); //showSuggestionLater(); return word; @@ -387,6 +417,9 @@ else if (s.charAt(x1) == ']') { * @return gutter width in pixels */ protected int getGutterWidth() { + if(editor.debugToolbarEnabled == null || !editor.debugToolbarEnabled.get()){ + return 0; + } FontMetrics fm = painter.getFontMetrics(); // log("fm: " + (fm == null)); // log("editor: " + (editor == null)); @@ -404,6 +437,9 @@ protected int getGutterWidth() { * @return margins in pixels */ protected int getGutterMargins() { + if(editor.debugToolbarEnabled == null || !editor.debugToolbarEnabled.get()){ + return 0; + } return gutterPadding; } @@ -645,7 +681,7 @@ public void mouseMoved(MouseEvent me) { //JEditTextArea textarea; - // worthless + /* No longer used private void addCompletionPopupListner() { this.addKeyListener(new KeyListener() { @@ -676,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() { @@ -688,10 +724,16 @@ 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) { log("TextArea: No suggestions to show."); - hideSuggestion(); return; } int position = getCaretPosition(); @@ -700,7 +742,8 @@ protected void showSuggestion(DefaultListModel defListModel,String subWord) { location.x = offsetToX(getCaretLine(), position - getLineStartOffset(getCaretLine())); location.y = lineToY(getCaretLine()) - + getPainter().getFontMetrics().getHeight(); + + getPainter().getFontMetrics().getHeight() + getPainter().getFontMetrics().getDescent(); + log("TA position: " + location); } catch (Exception e2) { e2.printStackTrace(); return; @@ -709,24 +752,29 @@ protected void showSuggestion(DefaultListModel defListModel,String subWord) { if (subWord.length() < 2) { return; } - if (suggestion == null) - suggestion = new CompletionPanel(this, position, subWord, defListModel, - location); - else - suggestion.updateList(defListModel, subWord, position); - suggestion.setVisible(true); -// requestFocusInWindow(); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - requestFocusInWindow(); - } - }); + //if (suggestion == null) + suggestion = new CompletionPanel(this, position, subWord, defListModel, + location,editor); +// else +// suggestion.updateList(defListModel, subWord, location, position); +// +// suggestion.setVisible(true); + requestFocusInWindow(); +// SwingUtilities.invokeLater(new Runnable() { +// @Override +// public void run() { +// requestFocusInWindow(); +// } +// }); } - private void hideSuggestion() { + /** + * Hides suggestion popup + */ + protected void hideSuggestion() { if (suggestion != null) { - suggestion.hideSuggestion(); + suggestion.hide(); + log("Suggestion hidden."); suggestion = null; } } diff --git a/src/processing/mode/experimental/TextAreaPainter.java b/src/processing/mode/experimental/TextAreaPainter.java index 89170ed..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.astGenerator.scrollToDeclaration(line - + errorCheckerService.mainClassOffset, word, xLS); + errorCheckerService.getASTGenerator() + .scrollToDeclaration(line, word, xLS); } } @@ -172,20 +173,27 @@ private void loadTheme(ExperimentalMode mode) { @Override protected void paintLine(Graphics gfx, TokenMarker tokenMarker, int line, int x) { - - // paint gutter - paintGutterBg(gfx, line, x); - - paintLineBgColor(gfx, line, x + ta.getGutterWidth()); - - paintGutterLine(gfx, line, x); - - // paint gutter symbol - paintGutterText(gfx, line, x); - + try { + //TODO: This line is causing NPE's randomly ever since I added the toggle for + //Java Mode/Debugger toolbar. + super.paintLine(gfx, tokenMarker, line, x + ta.getGutterWidth()); + } catch (Exception e) { + log(e.getMessage()); + } + if(ta.editor.debugToolbarEnabled != null && ta.editor.debugToolbarEnabled.get()){ + // paint gutter + paintGutterBg(gfx, line, x); + + // disabled line background after P5 2.1, since it adds highlight by default + //paintLineBgColor(gfx, line, x + ta.getGutterWidth()); + + paintGutterLine(gfx, line, x); + + // paint gutter symbol + paintGutterText(gfx, line, x); + + } paintErrorLine(gfx, line, x); - - super.paintLine(gfx, tokenMarker, line, x + ta.getGutterWidth()); } /** @@ -296,7 +304,6 @@ protected void paintLineBgColor(Graphics gfx, int line, int x) { * @param x */ protected void paintErrorLine(Graphics gfx, int line, int x) { - if (errorCheckerService == null) { return; } @@ -307,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.problem.lineNumber == line + 1) { + if (emarker.getProblem().getLineNumber() == line) { notFound = false; - if (emarker.type == ErrorMarker.Warning) { + if (emarker.getType() == ErrorMarker.Warning) { isWarning = true; } + problem = emarker.getProblem(); + //log(problem.toString()); break; } } @@ -330,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() + " ," @@ -348,24 +362,28 @@ 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 += 20; - x2 += 20; + x1 += ta.getGutterWidth(); + x2 += ta.getGutterWidth(); // gfx.fillRect(x1, y, rw, height); // Let the painting begin! - gfx.setColor(errorMarkerColor); - if (isWarning) { - gfx.setColor(warningMarkerColor); - } - gfx.fillRect(1, y + 2, 3, height - 2); - + + // Little rect at starting of a line containing errors - disabling it for now +// gfx.setColor(errorMarkerColor); +// if (isWarning) { +// gfx.setColor(warningMarkerColor); +// } +// gfx.fillRect(1, y + 2, 3, height - 2); + + gfx.setColor(errorColor); if (isWarning) { gfx.setColor(warningColor); @@ -448,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 @@ -472,9 +491,8 @@ else if (s.length() == 0) } if (Character.isDigit(word.charAt(0))) return null; - String tooltipText = errorCheckerService.astGenerator - .getLabelForASTNode(line + errorCheckerService.mainClassOffset, word, - xLS); + String tooltipText = errorCheckerService.getASTGenerator() + .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/src/processing/mode/experimental/XQConsoleToggle.java b/src/processing/mode/experimental/XQConsoleToggle.java index 79d8516..9652e0a 100755 --- a/src/processing/mode/experimental/XQConsoleToggle.java +++ b/src/processing/mode/experimental/XQConsoleToggle.java @@ -90,10 +90,22 @@ public void paintComponent(Graphics g) { g.fillRect(0, 0, 4, this.getHeight()); g.setColor(Color.WHITE); } - + g.drawString(buttonName, getWidth() / 2 + 2 // + 2 is a offset - getFontMetrics(getFont()).stringWidth(buttonName) / 2, this.getHeight() - 6); + if(drawMarker){ + g.setColor(markerColor); + g.fillRect(4, 0, 2, this.getHeight()); + } + } + + boolean drawMarker = false; + protected Color markerColor; + public void updateMarker(boolean value, Color color){ + drawMarker = value; + markerColor = color; + repaint(); } @Override diff --git a/theme/buttons-2x.png b/theme/buttons-2x.png index a63d28a..e82e968 100644 Binary files a/theme/buttons-2x.png and b/theme/buttons-2x.png differ diff --git a/theme/buttons-debug-2x.png b/theme/buttons-debug-2x.png new file mode 100644 index 0000000..a63d28a Binary files /dev/null and b/theme/buttons-debug-2x.png differ diff --git a/theme/buttons-debug.png b/theme/buttons-debug.png new file mode 100644 index 0000000..1ca1e87 Binary files /dev/null and b/theme/buttons-debug.png differ diff --git a/theme/buttons.png b/theme/buttons.png index 1ca1e87..7b34abb 100644 Binary files a/theme/buttons.png and b/theme/buttons.png differ 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 + +