├── .github └── workflows │ └── lock.yml ├── .gitignore ├── .idea ├── misc.xml ├── modules.xml └── vcs.xml ├── .settings └── org.eclipse.jdt.core.prefs ├── README.md ├── build.xml ├── done.txt ├── jshint.js ├── keywords.txt ├── libraries └── p5.sound │ └── library │ └── p5.sound.min.js ├── mode.properties ├── mode ├── asm-commons.jar ├── asm-tree.jar ├── asm-util.jar ├── asm.jar └── nashorn-core.jar ├── p5jsMode.iml ├── releases.md ├── src └── processing │ └── mode │ └── p5js │ ├── HtmlTokenMarker.java │ ├── Linter.java │ ├── NashornParse.java │ ├── build │ └── ImportExamples.java │ ├── p5jsBuild.java │ ├── p5jsEditor.java │ ├── p5jsLibrary.java │ ├── p5jsMode.java │ ├── p5jsToolbar.java │ └── server │ ├── CarlOrff.java │ ├── EditorHandler.java │ ├── FavIconHandler.java │ ├── GenericHandler.java │ ├── Handler.java │ ├── HttpServer.java │ └── HttpWorker.java ├── template ├── index.html ├── libraries │ └── p5.min.js └── sketch.js └── todo.txt /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | name: 'Lock Threads' 2 | 3 | on: 4 | schedule: 5 | - cron: '* 6 * * *' 6 | 7 | jobs: 8 | lock: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: dessant/lock-threads@v2.0.1 12 | with: 13 | github-token: ${{ github.token }} 14 | issue-lock-inactive-days: '30' 15 | issue-lock-comment: > 16 | This issue has been automatically locked. To avoid confusion 17 | with reports that have already been resolved, closed issues 18 | are automatically locked 30 days after the last comment. 19 | Please open a new issue for related bugs. 20 | pr-lock-comment: > 21 | This pull request has been automatically locked. 22 | Pull requests that have been closed are automatically 23 | locked 30 days after the last comment. 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /bin/ 3 | /out/ 4 | .DS_Store 5 | thumbs.db 6 | local.properties 7 | /mode/p5jsMode.jar 8 | /16* 9 | /19* 10 | /20* 11 | /21* 12 | /22* 13 | /p5.js-website 14 | /examples/ 15 | /dist 16 | 17 | # User-specific stuff 18 | .idea/**/workspace.xml 19 | .idea/**/tasks.xml 20 | .idea/**/usage.statistics.xml 21 | .idea/**/dictionaries 22 | .idea/**/shelf 23 | 24 | # AWS User-specific 25 | .idea/**/aws.xml 26 | 27 | # Generated files 28 | .idea/**/contentModel.xml 29 | 30 | # Sensitive or high-churn files 31 | .idea/**/dataSources/ 32 | .idea/**/dataSources.ids 33 | .idea/**/dataSources.local.xml 34 | .idea/**/sqlDataSources.xml 35 | .idea/**/dynamic.xml 36 | .idea/**/uiDesigner.xml 37 | .idea/**/dbnavigator.xml 38 | 39 | # Gradle 40 | .idea/**/gradle.xml 41 | .idea/**/libraries 42 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled 3 | org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore 4 | org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull 5 | org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault 6 | org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable 7 | org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled 8 | org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning 9 | org.eclipse.jdt.core.compiler.problem.autoboxing=ignore 10 | org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning 11 | org.eclipse.jdt.core.compiler.problem.deadCode=ignore 12 | org.eclipse.jdt.core.compiler.problem.deprecation=warning 13 | org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled 14 | org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled 15 | org.eclipse.jdt.core.compiler.problem.discouragedReference=warning 16 | org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore 17 | org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore 18 | org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore 19 | org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled 20 | org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore 21 | org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning 22 | org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning 23 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=error 24 | org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning 25 | org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled 26 | org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning 27 | org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning 28 | org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore 29 | org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore 30 | org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning 31 | org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore 32 | org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore 33 | org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled 34 | org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore 35 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning 36 | org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled 37 | org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore 38 | org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore 39 | org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning 40 | org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning 41 | org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore 42 | org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning 43 | org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error 44 | org.eclipse.jdt.core.compiler.problem.nullReference=warning 45 | org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error 46 | org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning 47 | org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning 48 | org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore 49 | org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore 50 | org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore 51 | org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=warning 52 | org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore 53 | org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning 54 | org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore 55 | org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore 56 | org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore 57 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore 58 | org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore 59 | org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled 60 | org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning 61 | org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled 62 | org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled 63 | org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled 64 | org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore 65 | org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning 66 | org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled 67 | org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning 68 | org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning 69 | org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore 70 | org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning 71 | org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore 72 | org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning 73 | org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore 74 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore 75 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled 76 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled 77 | org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled 78 | org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore 79 | org.eclipse.jdt.core.compiler.problem.unusedImport=warning 80 | org.eclipse.jdt.core.compiler.problem.unusedLabel=warning 81 | org.eclipse.jdt.core.compiler.problem.unusedLocal=warning 82 | org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore 83 | org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore 84 | org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled 85 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled 86 | org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled 87 | org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning 88 | org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore 89 | org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning 90 | org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## p5.js Mode for Processing 2 | 3 | A simple editor for [p5.js](https://p5js.org) code that runs inside the PDE. 4 | 5 | Starting in 2022, Processing 4.0 is required. Major changes in Java 17 make backwards compatibility difficult, and Processing 3 was last updated in January 2020. 6 | 7 | 8 | ### Goals/Criteria 9 | 10 | * A simple way to get started with p5.js, while making use of the editing facilities of the PDE and its Sketchbook for organizing projects. 11 | * A simple editor that allows offline use. 12 | * Make use of the PDE being installed in many schools and labs, and have a Mode that’s easy to install from inside the PDE (no download, unzip, install process). 13 | * A way for [us](https://fathom.info) to support [courses](https://infodesign.mit.edu) we are [teaching](https://learn.fathom.info). 14 | * Sketches should always be runnable directly from the folder: no need to Export or otherwise prepare a Sketch. 15 | * This is not an official [Processing Foundation](https://github.com/processing) project, no matter what you might know about its [primary author](https://github.com/benfry). 16 | 17 | 18 | ### Details 19 | 20 | This Mode is not designed for flexibility. It's also not a way to teach people how to do “proper” JavaScript development any more than the PDE is a way to teach people Java. Like all things in the PDE, we're removing features in an attempt to simplify the process of getting started quickly, creating projects at the simpler end of the scale, and maximizing the ability to try ideas rapidly. 21 | 22 | The tradeoffs below represent the best solution based on the goals and criteria above. Of course, these are subject to change if/when we realize mistakes. 23 | 24 | * Like the usual Java Mode in Processing, the main code is found in a file with the same name as the sketch. If you need more flexibility, this Mode isn't for you. 25 | * An `index.html` file is created in each new sketch folder. It contains a section where each `.js` file from the sketch is added automatically. Removing this block of code (it’s clearly marked in the file) will cause the sketch to no longer run inside the PDE. 26 | * If you run into trouble, remove the `index.html` file, which will reset it to the version from the template. 27 | * Add library files or additional code to the `libraries` subfolder of the sketch. References to that code will automatically be added to the HTML file, though the libraries won’t be visible as tabs in the Editor. 28 | * Like everything else in the PDE, this uses the `data` folder (unlike many p5js examples which use an `assets` folder). Because sketches must specify `assets` in the path, it's just as easy to do that as to specify `data` instead (rather than rewrite file handling in the PDE). 29 | * As with the rest of Processing, if you outgrow this setup, you should use another IDE or development solution (like a full-featured programmer's text editor and similar tools). We have no interest in creating a JavaScript IDE. Also like the rest of Processing, we *want* people to outgrow this setup. 30 | 31 | This project started from Florian Jenett's [JavaScript Mode](https://github.com/fjenett/javascript-mode-processing) that was previously included (way back!) in Processing 1.5, but I’m not sure if any of his original code remains. 32 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 17 | 18 | 19 | 20 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 89 | 96 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 170 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /done.txt: -------------------------------------------------------------------------------- 1 | 0016 (1.6) 2 | X updated from p5.js 1.4.0 to 1.4.2, and then 1.5.0 3 | X preload() should be highlighted like setup, draw, mousePressed, etc 4 | X warn users when files have a capitalization problem 5 | X since the server is running locally, should be able to detect 6 | X i.e. if there's a GET for a file and the capitalization is different 7 | X removed unused jsoup code 8 | 9 | index.html re-generation 10 | o silly to replace @@sketch@@ in p5jsMode.addTemplate() 11 | X p5jsBuild.updateHtml() will rewrite it anyway, and include the other tabs 12 | X just making this clearer in the code 13 | X make updateHtml() operate on the live text area 14 | X and remove the 'could not update' message 15 | X or if the index.html is broken, make sure the user gets a 'please reset' message 16 | o when doing new tab/rename tab/delete tab, it's not possible to rewrite index.html 17 | o although it is: just need to reach in and make the changes in the live document 18 | X index.html being overwritten even after save in p5jsMode 19 | X trying to replace "data/fonts" with "fonts" in the yule project index.html 20 | X re-create index.html option doesn't take new tabs into account 21 | X rewrote the code and should behave better 22 | X Save As not updating the sketch name in index.html 23 | 24 | 25 | 0015 (1.5) 26 | X remove json as a file type to display in editor 27 | X otherwise it's not added to the data folder, which is inconsistent 28 | 29 | linting changes 30 | X update to JSHint 2.13.4 31 | X turn off lint for using dot syntax 32 | X too confusing, and will be removed in jshint soon too 33 | X add DOCTYPE declaration to template index.html 34 | X https://github.com/processing/processing4/issues/465 35 | X allow users to skip semicolons 36 | X https://github.com/fathominfo/processing-p5js-mode/issues/25 37 | 38 | temp files 39 | X still a lot of temp files left around? 40 | X instead of doing temp files, should that be state stored by the server? 41 | X if there's a 'temp' version of a particular file, use that when requested 42 | X or just send the contents of the editor tabs when requested? 43 | X write the temp stuff to another folder? 44 | X positive part of temp files: not having to save between runs 45 | X save the temp stuff to a different folder entirely? 46 | o when using p5jsMode, just require that sketches are saved 47 | o too many problems with unsaved code not working properly 48 | o (instead of doing the weird tricks with temp files) 49 | X this is a headache; let's not require it 50 | X change to loading modified files dynamically at the server level 51 | 52 | 53 | 0014 (1.4.2) 54 | X fixes for how openURL() is handled 55 | X https://github.com/fathominfo/processing-p5js-mode/pull/29 56 | X update outdated URLs, change a few from http to https 57 | X show local server address in the console 58 | X require use of 4.0 beta 5 for PdeTextAreaDefaults constructor deprecation 59 | X cleaning up some old code, using lambdas, clear up warnings 60 | o need to sort out the deleteFile() weirdness in JNA 61 | X should be resolved now 62 | 63 | 64 | 0013 (1.4.1) 65 | X imports were totally wrong inside build 66 | X apparently was building with IntelliJ against Java 11? 67 | X get things working properly from build.xml 68 | 69 | 70 | 0012 (1.4) 71 | X change 'authorList' to 'authors' (that changed in, uh, 3.0a11) 72 | X update p5.js to 1.4.0 instead of 1.2.0 73 | X update jshint from 2.12 to 2.13.3 74 | X https://github.com/jshint/jshint/releases/tag/2.13.3 75 | 76 | update to new Nashorn scripting engine 77 | X Warning: Nashorn engine is planned to be removed from a future JDK release 78 | X https://github.com/fathominfo/processing-p5js-mode/issues/27 79 | X add nashorn-core-15.3.jar (named as nashorn-core.jar) 80 | X https://search.maven.org/artifact/org.openjdk.nashorn/nashorn-core/15.3/jar 81 | X nashorn-core uses version 7.3.1 of asm, asm-commons, asm-tree, asm-util 82 | X web page https://search.maven.org/artifact/org.ow2.asm/asm-util/7.3.1/jar 83 | X jar download https://search.maven.org/remotecontent?filepath=org/ow2/asm/asm-commons/7.3.1/asm-commons-7.3.1.jar 84 | 85 | return new org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory() .getScriptEngine(); 86 | 87 | https://stackoverflow.com/questions/65265629/how-to-use-nashorn-in-java-15-and-later 88 | 89 | https://github.com/openjdk/nashorn 90 | https://search.maven.org/artifact/org.openjdk.nashorn/nashorn-core/15.3/jar 91 | 92 | Exception in thread "Thread-2" java.lang.NullPointerException: Cannot invoke "javax.script.Compilable.compile(String)" because "engine" is null 93 | at processing.mode.p5js.Linter.lambda$0(Linter.java:55) 94 | at java.base/java.lang.Thread.run(Thread.java:833) 95 | 96 | X aborted attempt to work with GraalVM 97 | o what a f*king mess: https://medium.com/graalvm/d4da3605b6cb 98 | o scroll to "How to run GraalVM JavaScript on a stock JDK" 99 | o decompose this maven-fest https://github.com/graalvm/graal-js-jdk11-maven-demo 100 | o into the necessary collection of jars, and try to move from there 101 | X seems to be possible to follow our use case: a Java-only engine, 102 | X though everything seems to push in direction of native code or full VM 103 | o how to suppress the warning 104 | o https://bugs.openjdk.java.net/browse/JDK-8210140 105 | o pass "--no-deprecation-warning" as a Nashorn startup arg 106 | o or on JVM startup: -Dnashorn.args="--no-deprecation-warning" 107 | o https://www.graalvm.org/downloads/ 108 | o ok, less of a mess: 109 | o simpler tutorial: https://golb.hplar.ch/2020/04/java-javascript-engine.html 110 | o list of jars needed: https://www.graalvm.org/reference-manual/js/RunOnJDK/#graalvm-javascript-without-maven---jar-files-from-graalvm 111 | o using a Linux download just in case any native libs are in there 112 | o https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-21.2.0 113 | 114 | $GRAALVM/jre/languages/js/graaljs.jar - core component of GraalVM JavaScript 115 | https://repo.maven.apache.org/maven2/org/graalvm/js/js/20.3.0/js-20.3.0.jar 116 | 117 | $GRAALVM/jre/languages/js/icu4j.jar - ICU4J component for internationalization 118 | https://repo.maven.apache.org/maven2/com/ibm/icu/icu4j/67.1/icu4j-67.1.jar 119 | 120 | $GRAALVM/jre/languages/regex/tregex.jar - GraalVM’s regular expression engine 121 | https://repo.maven.apache.org/maven2/org/graalvm/regex/regex/20.3.0/regex-20.3.0.jar 122 | 123 | $GRAALVM/jre/lib/boot/graal-sdk.jar - GraalVM’s SDK to implement languages 124 | https://repo.maven.apache.org/maven2/org/graalvm/sdk/graal-sdk/20.3.0/graal-sdk-20.3.0.jar 125 | 126 | $GRAALVM/jre/lib/truffle/truffle-api.jar - GraalVM’s Language API, to implement interpreters 127 | https://repo.maven.apache.org/maven2/org/graalvm/truffle/truffle-api/20.3.0/truffle-api-20.3.0.jar 128 | 129 | $GRAALVM/jre/lib/graalvm/graaljs-launcher.jar - GraalVM JS cmd line interpreter (optional) 130 | ??? 131 | 132 | $GRAALVM/jre/lib/graalvm/launcher-common.jar - common launcher code shared by all languages 133 | ??? 134 | https://search.maven.org/artifact/org.graalvm/launcher-common (but that's old...) 135 | also https://docs.oracle.com/en/graalvm/enterprise/20/docs/reference-manual/js/RunOnJDK/ 136 | 137 | $GRAALVM/jre/lib/boot/graaljs-scriptengine.jar - ScriptEngine/JSR 223 support (optional) 138 | https://repo.maven.apache.org/maven2/org/graalvm/js/js-scriptengine/20.3.0/js-scriptengine-20.3.0.jar 139 | 140 | 141 | 0011 (1.3.1) 142 | X workaround for NoClassDefFoundError that Chris ran into 143 | 144 | 145 | 0010 (1.3) 146 | X add revision check to the build 147 | X use p5js 1.0 148 | X update examples, remove p5js.dom references 149 | X update to p5js 1.1.9 (July 22, 2020) 150 | X fix up warnings by explicitly closing a few more streams 151 | X update to p5js 1.2.0 (December 19, 2020 release) 152 | X adding workaround for JNA issues on Big Sur 153 | X https://github.com/fathominfo/processing-p5js-mode/issues/26 154 | X ability to update p5*.js in the libraries subfolder 155 | o automatically notify user if version in the template is updated? 156 | X not just about p5jsMode updates, but updating older sketches 157 | X this could be a Mode menu item, grayed out if up-to-date 158 | o how do we bring back the auto-update block if it's broken? 159 | o or if it's not present, stop updating that file? 160 | X reset index.html from menu option 161 | 162 | 163 | 0009 (1.2.2) 164 | X fix release numbering 165 | 166 | 167 | 0008 (1.2.1) 168 | X update p5js version from 0.7.3 to 0.8 169 | X update to jshint 2.11.0 170 | X update p5js to 0.10.2 171 | X remove dom as separate library 172 | X disable trailing comma warning 173 | X https://jshint.com/docs/options/#trailingcomma 174 | 175 | 176 | 0007 (1.2) 177 | X incorporating linter 178 | X use one server/port per editor window 179 | X stay fixed throughout usage 180 | X still a downside to not always being the same (for breakpoints) 181 | X but for advanced editing like that, people shouldn't be using this mode anyway 182 | X fix coloring and Find in Reference for circle(), square(), push(), pop() 183 | 184 | 185 | 0006 (1.1.1) 186 | X fix up the build process to avoid errors 187 | 188 | 189 | 0005 (1.1) 190 | X make worker objects no longer static, fixing collisions and cache issues 191 | X update p5.js to 0.5.16 192 | X https://github.com/fathominfo/processing-p5js-mode/pull/19 193 | X update to 0.7.3 194 | X update sound and dom libraries 195 | X Allow use of let and const in p5js-mode 196 | X https://github.com/fathominfo/processing-p5js-mode/issues/20 197 | X seems to be supported as of 8u40? 198 | X https://bugs.openjdk.java.net/browse/JDK-8046038 199 | X https://developer.oracle.com/databases/nashorn-javascript-part1 200 | X p5.js sketch breaks when I save it (looks like issue with 'let') 201 | X https://github.com/fathominfo/processing-p5js-mode/issues/18 202 | X update examples 203 | X use minified versions of code? 204 | X issues when editing css files 205 | X https://github.com/fathominfo/processing-p5js-mode/issues/21 206 | X showing a css tab was causing a lot of error messages 207 | X Add support for editing .json files in the Editor 208 | X https://github.com/fathominfo/processing-p5js-mode/issues/16 209 | X modify p5jsMode.getExtensions() (what to do about data folder?) 210 | o and how should syntax highlighting work? (uses js highlighter) 211 | X .json files must be in the root of the sketch folder 212 | X (not a subfolder like data or assets) to be visible/editable 213 | X use minified version of libs for the examples 214 | X check whether Desktop is available for URL opening 215 | X https://github.com/fathominfo/processing-p5js-mode/issues/17 216 | X Opening a sketch without PDE folder structure not working properly 217 | X https://github.com/fathominfo/processing-p5js-mode/issues/7 218 | X Find in Reference not implemented 219 | X https://github.com/fathominfo/processing-p5js-mode/issues/22 220 | X "Could not open the URL... p5jsMode/reference/ellipse_.html" 221 | X http://p5js.org/reference/#/p5/ellipse 222 | o what does loadKeywords map to? 223 | X implemented a hokey version 224 | 225 | cleaning 226 | o NPE when doing Find in Reference w/ nothing selected 227 | o Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException 228 | o at processing.app.ui.Editor.handleFindReference(Editor.java:2309) 229 | o at processing.mode.p5js.p5jsEditor.access$0(p5jsEditor.java:1) 230 | o at processing.mode.p5js.p5jsEditor$5.actionPerformed(p5jsEditor.java:337) 231 | X doesn't seem to be possible anymore 232 | o properly implement the minified versions of libs 233 | o git checkout e560d0576c2440081787e7b09eefdc760152f022^ -- 234 | o (that's the commit that started deleting, ^ will get us to previous) 235 | o when rewriting the index.html file, 'sketch modified externally' messages 236 | o https://github.com/fathominfo/processing-p5js-mode/issues/15 237 | X seems like the Change Detector has ironed this out in the meantime. 238 | 239 | 240 | 0004 (1.0.4) 241 | X prevent reformatting of index.html 242 | X https://github.com/fathominfo/processing-p5js-mode/issues/13 243 | X update to 0.5.7 release of p5js and supporting libraries 244 | X remove the sketch.js renaming code 245 | X switch to minified version of libraries 246 | X and switch back 247 | X when adding library, add it to the "do not touch" section as well 248 | X don't tell people "do not touch", then talk about un-commenting libraries 249 | X disable opening of sketch.js files so things don't get destroyed 250 | 251 | verify 252 | X (hopefully) fix the release itself so that it installs properly 253 | X https://github.com/fathominfo/processing-p5js-mode/issues/11 254 | X re-using the same server port for each run 255 | X https://github.com/fathominfo/processing-p5js-mode/issues/8 256 | X Windows temp files still in use and cannot be removed 257 | X https://github.com/fathominfo/processing-p5js-mode/issues/6 258 | 259 | 260 | 0003 (1.0.3) 261 | X update to p5js 0.5.4 262 | X investigate problem with browser returning -600 error 263 | X https://github.com/fathominfo/processing-p5js-mode/issues/4 264 | X use jsoup to parse/modify the html instead of find/replace 265 | X (hopefully) fix the release itself so that it installs properly 266 | _ https://github.com/fathominfo/processing-p5js-mode/issues/11 267 | X re-using the same server port for each run 268 | _ https://github.com/fathominfo/processing-p5js-mode/issues/8 269 | X Windows temp files still in use and cannot be removed 270 | _ https://github.com/fathominfo/processing-p5js-mode/issues/6 271 | 272 | 273 | 0002 (1.0.2) 274 | X Save required before running 275 | X https://github.com/fathominfo/processing-p5js-mode/issues/2 276 | X Running a sketch may simply re-launch a window with the old server 277 | X https://github.com/fathominfo/processing-p5js-mode/issues/3 278 | X probably there before, but exacerbated by fixing #2 279 | 280 | 281 | 0001 (1.0) 282 | X add 'dist' target to build.xml 283 | X add 'clean' target to build.xml 284 | X how do we edit html, text, css? 285 | X set an auto-update block in the html to include the js files 286 | X insert files into html method: 287 | X first add anything from /libraries 288 | X then anything from the main directory 289 | X then the main sketch.js file 290 | X don't include dom.js and sound.js by default 291 | X html not reloading in editor after rebuild 292 | X need to write template correctly on first load 293 | o need to call updateHtml() on init 294 | X can't do that b/c the Sketch and Editor objects don't exist yet 295 | X use temp files to run without saving 296 | X how to clear these out when rewriting the html 297 | X need to use some sort of weird temp name format 298 | X contradiction between wanting valid files locally, always 299 | o should it be writing some .tmp.html files instead? 300 | o writeHtml() could be called to save those out in the interim 301 | X force save of HTML file when updating 302 | X implement handling for favicon.ico 303 | X should be able to send the image from PApplet 304 | X import all the examples 305 | X also generate the ordering for the category folders 306 | X get assets to work properly 307 | X hide 'libraries' folder from Java Mode 308 | X hide contrib libraries from Java Mode 309 | X hide contrib examples from Java Mode (since/if not compatible) 310 | X import library 311 | X allow some sort of single .js file library setup? 312 | X get p5.dom.js and p5.sound.js into the menu 313 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # additional keywords for JavaScript mode 2 | # https://developer.mozilla.org/en/JavaScript/Reference/Reserved_Words 3 | 4 | preload FUNCTION4 5 | 6 | debugger KEYWORD1 7 | var KEYWORD1 8 | function KEYWORD1 9 | typeof KEYWORD1 10 | delete KEYWORD1 11 | throw KEYWORD1 12 | with KEYWORD1 13 | in KEYWORD1 14 | 15 | prototype KEYWORD1 16 | arguments KEYWORD1 17 | #callee KEYWORD1 18 | 19 | enum KEYWORD1 20 | export KEYWORD1 21 | let KEYWORD1 22 | yield KEYWORD1 23 | undefined KEYWORD1 24 | const KEYWORD1 25 | 26 | # some specials 27 | 28 | console KEYWORD1 29 | window KEYWORD1 30 | document KEYWORD1 31 | 32 | alert KEYWORD3 33 | 34 | circle FUNCTION1 circle_ 35 | square FUNCTION1 square_ 36 | push FUNCTION1 push_ 37 | pop FUNCTION1 pop_ 38 | -------------------------------------------------------------------------------- /mode.properties: -------------------------------------------------------------------------------- 1 | name=p5.js Mode 2 | category=Unknown 3 | authors=[Fathom Information Design](http://fathom.info/) 4 | url=https://github.com/fathominfo/processing-p5js-mode 5 | sentence=Adds a simple editor for p5.js code 6 | paragraph=p5jsMode is a simple editor for p5.js 7 | version=17 8 | prettyVersion=1.6.1 9 | minRevision=1280 10 | maxRevision=0 11 | imports=processing.mode.java.JavaMode 12 | -------------------------------------------------------------------------------- /mode/asm-commons.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fathominfo/processing-p5js-mode/79eaa2f236183851dc549a563b922da9c6617259/mode/asm-commons.jar -------------------------------------------------------------------------------- /mode/asm-tree.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fathominfo/processing-p5js-mode/79eaa2f236183851dc549a563b922da9c6617259/mode/asm-tree.jar -------------------------------------------------------------------------------- /mode/asm-util.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fathominfo/processing-p5js-mode/79eaa2f236183851dc549a563b922da9c6617259/mode/asm-util.jar -------------------------------------------------------------------------------- /mode/asm.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fathominfo/processing-p5js-mode/79eaa2f236183851dc549a563b922da9c6617259/mode/asm.jar -------------------------------------------------------------------------------- /mode/nashorn-core.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fathominfo/processing-p5js-mode/79eaa2f236183851dc549a563b922da9c6617259/mode/nashorn-core.jar -------------------------------------------------------------------------------- /p5jsMode.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /releases.md: -------------------------------------------------------------------------------- 1 | # p5jsMode 1.6 2 | 3 | * Updated from p5.js 1.4.0 to 1.4.2, and then 1.5.0. 4 | 5 | * `preload()` now highlighted like `setup()` and `draw()` 6 | 7 | * Major rewrite for how `index.html` is generated. In some cases, the `index.html` file was not updated properly, or in some cases, could even override changes made by users. These problems should now be ironed out. (Because this involved major changes, the release was bumped to 1.6 instead of 1.5.1). 8 | 9 | * Warn users when files have a capitalization problem. When using `loadImage("amazing.png")` you should now see a warning if the file is actually called `Amazing.png` on disk. In most cases, the case change is not a problem on macOS or Windows, but if moved to a server with a case sensitive file system (i.e. Linux), this can cause confusing errors. 10 | 11 | * Removed unused jsoup library and code. 12 | 13 | * More cleanup of the temporary file handling that was removed in 1.5. 14 | 15 | 16 | # p5jsMode 1.5 17 | 18 | Goodbye temporary files! 19 | 20 | * This includes a major change that gets rid of the “temporary” files (prefixed `p5js-temp`) that were used to incorporate unsaved changes into a sketch. These were problematic, especially on Windows. 21 | 22 | Linting changes: 23 | 24 | * Updated to JSHint 2.13.4 25 | 26 | * Turned off warnings about “better to use dot syntax” 27 | 28 | * Also turned off the warning about skipping semicolons. [#25](https://github.com/fathominfo/processing-p5js-mode/issues/25) 29 | 30 | Other changes: 31 | 32 | * No longer showing `.json` files in the PDE, because 1) it's inconsistent with how all other data files are handled, and 2) it's confusing when dragging and dropping a `.json` file to the PDE window immediately shows it as part of the sketch. 33 | 34 | * Added `DOCTYPE` declaration to the `index.html` template. [#465](https://github.com/processing/processing4/issues/465) 35 | 36 | 37 | # p5jsMode 1.4.2 38 | 39 | A few bug fixes and improvements. 40 | 41 | * After hitting Run, the local server address is shown in the console, which helps with cross-device testing. 42 | 43 | * Fixes for how openURL() is handled. [#29](https://github.com/fathominfo/processing-p5js-mode/pull/29) 44 | 45 | * Fix up outdated URLs, change a few from http to https. 46 | 47 | * Require use of 4.0 beta 5 for `PdeTextAreaDefaults` constructor deprecation. 48 | 49 | * Cleaning up some old code, using lambdas, clearing up compile warnings. 50 | 51 | 52 | # p5jsMode 1.4.1 53 | 54 | All the things from the previous release (1.4) actually work in this one. 55 | 56 | 57 | # p5jsMode 1.4 58 | 59 | Latest p5.js (1.4) and support for Processing 4.0 beta 3 (which uses Java 17). 60 | 61 | * Update p5.js from 1.2.0 to 1.4.0. 62 | 63 | * Switch to Nashorn scripting engine [15.3](https://search.maven.org/artifact/org.openjdk.nashorn/nashorn-core/15.3/jar) from OpenJDK built by Attila Szegedi. The original Nashorn engine was removed prior to the Java 17 release, so this was necessary to keep things compatible with Processing 4.0 beta 3. 64 | 65 | * Update jshint from 2.12 to [2.13.3](https://github.com/jshint/jshint/releases/tag/2.13.3) 66 | 67 | * Change `authorList` to `authors` in `mode.properties` (responding to a change from, uh, Processing 3.0 alpha 11, which brings us in line with the spec as of 2015). 68 | 69 | 70 | # p5jsMode 1.3.1 71 | 72 | * Another workaround for the JNA issues seen in the previous releases 73 | 74 | 75 | # p5jsMode 1.3 76 | 77 | Bug fixes and other updates 78 | 79 | * Added workaround for JNA issues on Big Sur [#26](https://github.com/fathominfo/processing-p5js-mode/issues/26) 80 | * Update to p5.js 1.2.0 (the December 19, 2020 release) 81 | * Update the examples 82 | * Added p5.js menu 83 | * Added menu option to replace p5.js with the version included with p5jsMode 84 | * Implemented menu option to replace `index.html` 85 | 86 | Internal changes 87 | 88 | * Add revision check to the build 89 | * Fix up a few warnings by explicitly closing a few more streams 90 | * Remove p5js.dom references 91 | 92 | 93 | # p5jsMode 1.2.2 94 | 95 | A few bug fixes and updates: 96 | 97 | * Updated p5.js to version 0.10.2 98 | * Update jshint to 2.11.0 99 | * Remove warning about trailing commas in code 100 | 101 | 102 | # p5jsMode 1.2 103 | 104 | Significant improvements to syntax and error checking! 105 | 106 | * Incorporates an entirely new linter, *much* better error-checking and now includes warnings. 107 | * Supports newer JS syntax (more ES6+ features supported). 108 | * Using just one server/port per editor window (rather than restarting a fresh server on each run). 109 | * Fix coloring and Find in Reference for circle(), square(), push(), pop(). 110 | 111 | 112 | # p5jsMode 1.1.1 113 | 114 | Same as 1.1 but with fixes to the build process. 115 | 116 | ## Features and Fixes 117 | * Add a basic version of Find in Reference [22](https://github.com/fathominfo/processing-p5js-mode/issues/22) 118 | * Updated to p5.js 0.7.3 119 | * Also using the newest Sound and DOM libraries 120 | * Update examples to the latest version available 121 | * Allow use of let and const in p5js-mode [20](https://github.com/fathominfo/processing-p5js-mode/issues/20), [18](https://github.com/fathominfo/processing-p5js-mode/issues/18) 122 | * Switch to using the minified version of p5.js (and the libraries) 123 | * Allow JSON files to be viewed in the editor (though not from subfolders like `assets` or `data`) [16](https://github.com/fathominfo/processing-p5js-mode/issues/16) 124 | * Better handling of URL opening on Linux systems [17](https://github.com/fathominfo/processing-p5js-mode/issues/17) 125 | * Fix problems when opening a bare `.js` file as a sketch [7](https://github.com/fathominfo/processing-p5js-mode/issues/7) 126 | 127 | ## Fixes in Processing 3.5.3 128 | * Prevent issues when editing CSS files [21](https://github.com/fathominfo/processing-p5js-mode/issues/21) 129 | -------------------------------------------------------------------------------- /src/processing/mode/p5js/HtmlTokenMarker.java: -------------------------------------------------------------------------------- 1 | /* 2 | * HTMLTokenMarker.java - HTML token marker 3 | * Copyright (C) 1998, 1999 Slava Pestov 4 | * 5 | * You may use and modify this package for any purpose. Redistribution is 6 | * permitted, in both source and binary form, provided that this notice 7 | * remains intact in all source distributions of this package. 8 | */ 9 | 10 | package processing.mode.p5js; 11 | 12 | import javax.swing.text.Segment; 13 | 14 | import processing.app.syntax.KeywordMap; 15 | import processing.app.syntax.Token; 16 | import processing.app.syntax.TokenMarker; 17 | 18 | 19 | /** 20 | * HTML token marker by Slava Pestove from the original jEditSyntax package. 21 | * Updated slightly for use with p5jsMode and without JavaScriptTokenMarker. 22 | */ 23 | public class HtmlTokenMarker extends TokenMarker { 24 | public static final byte JAVASCRIPT = Token.INTERNAL_FIRST; 25 | 26 | 27 | public HtmlTokenMarker() { 28 | this(true); 29 | } 30 | 31 | 32 | public HtmlTokenMarker(boolean js) { 33 | this.js = js; 34 | keywords = new KeywordMap(false); 35 | keywords.add("function", Token.KEYWORD3, false); 36 | keywords.add("var", Token.KEYWORD3, false); 37 | keywords.add("let", Token.KEYWORD3, false); 38 | keywords.add("const", Token.KEYWORD3, false); 39 | keywords.add("else", Token.KEYWORD1, false); 40 | keywords.add("for", Token.KEYWORD1, false); 41 | keywords.add("if", Token.KEYWORD1, false); 42 | keywords.add("in", Token.KEYWORD1, false); 43 | keywords.add("new", Token.KEYWORD1, false); 44 | keywords.add("return", Token.KEYWORD1, false); 45 | keywords.add("while", Token.KEYWORD1, false); 46 | keywords.add("with", Token.KEYWORD1, false); 47 | keywords.add("break", Token.KEYWORD1, false); 48 | keywords.add("case", Token.KEYWORD1, false); 49 | keywords.add("continue", Token.KEYWORD1, false); 50 | keywords.add("default", Token.KEYWORD1, false); 51 | keywords.add("false", Token.LABEL, false); 52 | keywords.add("this", Token.LABEL, false); 53 | keywords.add("true", Token.LABEL, false); 54 | } 55 | 56 | 57 | @Override 58 | public byte markTokensImpl(byte token, Segment line, int lineIndex) { 59 | char[] array = line.array; 60 | int offset = line.offset; 61 | lastOffset = offset; 62 | lastKeyword = offset; 63 | int length = line.count + offset; 64 | boolean backslash = false; 65 | 66 | loop: 67 | for (int i = offset; i < length; i++) { 68 | int i1 = (i+1); 69 | 70 | char c = array[i]; 71 | if (c == '\\') { 72 | backslash = !backslash; 73 | continue; 74 | } 75 | 76 | switch (token) { 77 | case Token.NULL: // HTML text 78 | backslash = false; 79 | switch (c) { 80 | case '<': 81 | addToken(i - lastOffset, token); 82 | lastOffset = lastKeyword = i; 83 | if (regionMatches(false, line, i1, "!--")) { 84 | i += 3; 85 | token = Token.COMMENT1; 86 | } else if (js && regionMatches(true, line, i1, "script>")) { 87 | addToken(8, Token.KEYWORD1); 88 | lastOffset = lastKeyword = (i += 8); 89 | token = JAVASCRIPT; 90 | } else { 91 | token = Token.KEYWORD1; 92 | } 93 | break; 94 | case '&': 95 | addToken(i - lastOffset, token); 96 | lastOffset = lastKeyword = i; 97 | token = Token.KEYWORD2; 98 | break; 99 | } 100 | break; 101 | case Token.KEYWORD1: // Inside a tag 102 | backslash = false; 103 | if (c == '>') { 104 | addToken(i1 - lastOffset, token); 105 | lastOffset = lastKeyword = i1; 106 | token = Token.NULL; 107 | } 108 | break; 109 | case Token.KEYWORD2: // Inside an entity 110 | backslash = false; 111 | if (c == ';') { 112 | addToken(i1 - lastOffset, token); 113 | lastOffset = lastKeyword = i1; 114 | token = Token.NULL; 115 | break; 116 | } 117 | break; 118 | case Token.COMMENT1: // Inside a comment 119 | backslash = false; 120 | if (regionMatches(false, line, i, "-->")) { 121 | addToken((i + 3) - lastOffset, token); 122 | lastOffset = lastKeyword = i + 3; 123 | token = Token.NULL; 124 | } 125 | break; 126 | case JAVASCRIPT: // Inside a JavaScript 127 | switch (c) { 128 | case '<': 129 | backslash = false; 130 | doKeyword(line, i, c); 131 | if (regionMatches(true, line, i1, "/script>")) { 132 | addToken(i - lastOffset, Token.NULL); 133 | addToken(9, Token.KEYWORD1); 134 | lastOffset = lastKeyword = (i += 9); 135 | token = Token.NULL; 136 | } 137 | break; 138 | case '"': 139 | if (backslash) { 140 | backslash = false; 141 | } else { 142 | doKeyword(line, i, c); 143 | addToken(i - lastOffset, Token.NULL); 144 | lastOffset = lastKeyword = i; 145 | token = Token.LITERAL1; 146 | } 147 | break; 148 | case '\'': 149 | if (backslash) { 150 | backslash = false; 151 | } else { 152 | doKeyword(line, i, c); 153 | addToken(i - lastOffset, Token.NULL); 154 | lastOffset = lastKeyword = i; 155 | token = Token.LITERAL2; 156 | } 157 | break; 158 | case '/': 159 | backslash = false; 160 | doKeyword(line, i, c); 161 | if (length - i > 1) { 162 | addToken(i - lastOffset, Token.NULL); 163 | lastOffset = lastKeyword = i; 164 | if (array[i1] == '/') { 165 | addToken(length - i, Token.COMMENT2); 166 | lastOffset = lastKeyword = length; 167 | break loop; 168 | } else if (array[i1] == '*') { 169 | token = Token.COMMENT2; 170 | } 171 | } 172 | break; 173 | default: 174 | backslash = false; 175 | if (!Character.isLetterOrDigit(c) && c != '_') { 176 | doKeyword(line, i, c); 177 | } 178 | break; 179 | } 180 | break; 181 | case Token.LITERAL1: // JavaScript "..." 182 | if (backslash) { 183 | backslash = false; 184 | } else if (c == '"') { 185 | addToken(i1 - lastOffset, Token.LITERAL1); 186 | lastOffset = lastKeyword = i1; 187 | token = JAVASCRIPT; 188 | } 189 | break; 190 | case Token.LITERAL2: // JavaScript '...' 191 | if (backslash) { 192 | backslash = false; 193 | } else if (c == '\'') { 194 | addToken(i1 - lastOffset, Token.LITERAL1); 195 | lastOffset = lastKeyword = i1; 196 | token = JAVASCRIPT; 197 | } 198 | break; 199 | case Token.COMMENT2: // Inside a JavaScript comment 200 | backslash = false; 201 | if (c == '*' && length - i > 1 && array[i1] == '/') { 202 | addToken((i+=2) - lastOffset, Token.COMMENT2); 203 | lastOffset = lastKeyword = i; 204 | token = JAVASCRIPT; 205 | } 206 | break; 207 | default: 208 | throw new InternalError("Invalid state: " + token); 209 | } 210 | } 211 | 212 | switch (token) { 213 | case Token.LITERAL1: 214 | case Token.LITERAL2: 215 | addToken(length - lastOffset, Token.INVALID); 216 | token = JAVASCRIPT; 217 | break; 218 | case Token.KEYWORD2: 219 | addToken(length - lastOffset, Token.INVALID); 220 | token = Token.NULL; 221 | break; 222 | case JAVASCRIPT: 223 | doKeyword(line, length, '\0'); 224 | addToken(length - lastOffset, Token.NULL); 225 | break; 226 | default: 227 | addToken(length - lastOffset, token); 228 | break; 229 | } 230 | return token; 231 | } 232 | 233 | 234 | // private members 235 | private KeywordMap keywords; 236 | private boolean js; 237 | private int lastOffset; 238 | private int lastKeyword; 239 | 240 | private boolean doKeyword(Segment line, int i, char c) { 241 | int i1 = i+1; 242 | int len = i - lastKeyword; 243 | byte id = keywords.lookup(line, lastKeyword, len, false); 244 | if (id != Token.NULL) { 245 | if (lastKeyword != lastOffset) { 246 | addToken(lastKeyword - lastOffset, Token.NULL); 247 | } 248 | addToken(len, id); 249 | lastOffset = i; 250 | } 251 | lastKeyword = i1; 252 | return false; 253 | } 254 | 255 | 256 | // moved from SyntaxUtilities 257 | static boolean regionMatches(boolean ignoreCase, Segment text, 258 | int offset, String match) { 259 | return KeywordMap.regionMatches(ignoreCase, text, offset, 260 | match.toCharArray()); 261 | } 262 | 263 | 264 | @Override 265 | public void addColoring(String keyword, String coloring) { 266 | System.out.format("addColoring(%s, %s) called in HtmlTokenMarker%n"); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/processing/mode/p5js/Linter.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js; 2 | 3 | import java.io.File; 4 | import java.util.Collections; 5 | import java.util.HashMap; 6 | 7 | import javax.script.Compilable; 8 | import javax.script.CompiledScript; 9 | import javax.script.ScriptEngine; 10 | import javax.script.ScriptException; 11 | import javax.script.SimpleBindings; 12 | 13 | import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory; 14 | 15 | import processing.app.ui.Editor; 16 | import processing.core.PApplet; 17 | import processing.data.JSONArray; 18 | 19 | 20 | // Nashorn invocation based on ParallelNashornJSHint.java from this repo: 21 | // https://github.com/wfhartford/parallel-nashorn-jshint/ 22 | 23 | // other useful polyfills in section 4.1.2: https://www.n-k.de/riding-the-nashorn/ 24 | 25 | public class Linter { 26 | static private final String OPTIONS = "{" + 27 | "esversion: 6," + 28 | "browser: true," + // enable browser globals (document, navigator, FileReader) 29 | "curly: true," + // require curly braces around blocks 30 | "eqnull: true," + // ok to check if something == null 31 | "evil: true," + // allows eval() 32 | "jquery: true," + // turn on jquery globals 33 | "loopfunc: true," + // warn about functions defined inside loops 34 | "noarg: true," + // prohibits the use of arguments.caller and arguments.callee 35 | 36 | // https://jshint.com/docs/options/#sub 37 | "sub: true," + // don't complain about person['name'] vs. person.name 38 | 39 | // https://jshint.com/docs/options/#trailingcomma 40 | "trailingcomma: false," + // too confusing for users 41 | 42 | // https://jshint.com/docs/options/#asi 43 | // https://github.com/fathominfo/processing-p5js-mode/issues/25 44 | "asi: true" + // allow no semicolons 45 | "}"; 46 | 47 | 48 | // Call jshint on array named 'lines' which we'll replace with the sketch. 49 | static private final String FUNCTION_CALL = 50 | "JSHINT(lines, " + OPTIONS + "); JSON.stringify(JSHINT.errors);"; 51 | 52 | CompiledScript compiled; 53 | 54 | 55 | public Linter(Editor editor) { 56 | new Thread(() -> { 57 | final ScriptEngine engine = 58 | new NashornScriptEngineFactory().getScriptEngine(); 59 | 60 | File jshintFile = editor.getMode().getContentFile("jshint.js"); 61 | String[] lines = PApplet.loadStrings(jshintFile); 62 | String preamble = PApplet.join(lines, '\n') + "\n" + FUNCTION_CALL; 63 | try { 64 | compiled = ((Compilable) engine).compile(preamble); 65 | lint(""); 66 | } catch (ScriptException e) { 67 | e.printStackTrace(); // need better error handling, but... 68 | } 69 | }).start(); 70 | } 71 | 72 | 73 | JSONArray lint(String code) throws ScriptException { 74 | if (compiled != null) { 75 | Object result = compiled.eval(new SimpleBindings(new HashMap<>(Collections.singletonMap("lines", code)))); 76 | if (result instanceof String) { 77 | return JSONArray.parse((String) result); 78 | } else { 79 | System.err.println("result not a string: " + result.getClass().getName()); 80 | System.err.println("result toString() is " + result); 81 | } 82 | } 83 | return null; 84 | } 85 | 86 | 87 | /* 88 | static Object test(final String scriptLines) throws ScriptException { 89 | long t1 = System.currentTimeMillis(); 90 | final CompiledScript script = getCompiledScript(getHintLines()); 91 | long t2 = System.currentTimeMillis(); 92 | final String dumbLines = ""; 93 | Object result = script.eval(new SimpleBindings(new HashMap<>(Collections.singletonMap("lines", dumbLines)))); 94 | long t3 = System.currentTimeMillis(); 95 | result = script.eval(new SimpleBindings(new HashMap<>(Collections.singletonMap("lines", dumbLines)))); 96 | long t4 = System.currentTimeMillis(); 97 | result = script.eval(new SimpleBindings(new HashMap<>(Collections.singletonMap("lines", scriptLines)))); 98 | long t5 = System.currentTimeMillis(); 99 | // 554 1078 70 484 100 | // half second to get set up, one second for first run 101 | // for p5 sketch, maybe half a second to run 102 | System.out.println((t2 - t1) + " " + (t3-t2) + " " + (t4-t3) + " " + (t5-t4)); 103 | 104 | if (result instanceof String) { 105 | //JSONObject obj = JSONObject.parse((String) result); 106 | JSONArray obj = JSONArray.parse((String) result); 107 | System.out.println(obj.format(2)); 108 | } 109 | 110 | return result; 111 | } 112 | 113 | 114 | private static List getHintLines() { //throws IOException { 115 | File file = new File("/Users/fry/coconut/processing-other/p5js-mode/jshint.js"); 116 | String[] lines = PApplet.loadStrings(file); 117 | return Arrays.asList(lines); 118 | } 119 | 120 | 121 | private static CompiledScript getCompiledScript(final List lines) throws ScriptException { 122 | final ScriptEngineManager manager = new ScriptEngineManager(); 123 | final ScriptEngine engine = manager.getEngineByMimeType("text/javascript"); 124 | 125 | final String fullScript = Stream.concat(lines.stream(), Stream.of(FUNCTION_CALL)) 126 | .collect(Collectors.joining("\n")); 127 | return ((Compilable) engine).compile(fullScript); 128 | } 129 | */ 130 | } -------------------------------------------------------------------------------- /src/processing/mode/p5js/NashornParse.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js; 2 | 3 | import org.openjdk.nashorn.api.scripting.ScriptUtils; 4 | import org.openjdk.nashorn.internal.runtime.Context; 5 | import org.openjdk.nashorn.internal.runtime.ECMAException; 6 | import org.openjdk.nashorn.internal.runtime.ErrorManager; 7 | import org.openjdk.nashorn.internal.runtime.options.Options; 8 | 9 | import processing.app.Base; 10 | import processing.app.Sketch; 11 | import processing.app.SketchCode; 12 | import processing.app.SketchException; 13 | import processing.core.PApplet; 14 | 15 | 16 | public class NashornParse { 17 | 18 | static void handle(Sketch sketch) throws SketchException { 19 | for (int i = 0; i < sketch.getCodeCount(); i++) { 20 | handleFile(sketch, i); 21 | } 22 | } 23 | 24 | static void handleFile(Sketch sketch, int codeIndex) throws SketchException { 25 | SketchCode sketchCode = sketch.getCode(codeIndex); 26 | if (sketchCode.isExtension("js") || sketchCode.isExtension("json")) { 27 | Options options = new Options("nashorn"); 28 | options.set("anon.functions", true); 29 | options.set("parse.only", true); 30 | options.set("scripting", true); 31 | options.set("language", "es6"); 32 | 33 | ErrorManager errors = new ErrorManager(); 34 | Context context = new Context(options, errors, Base.class.getClassLoader()); 35 | Context.setGlobal(context.createGlobal()); 36 | //String code = PApplet.join(PApplet.loadStrings(file), "\n"); 37 | //String code = sketchCode.getProgram(); 38 | 39 | try { 40 | String code = sketchCode.getDocumentText(); 41 | 42 | //String json = ScriptUtils.parse(code, sketch.getName(), true); 43 | ScriptUtils.parse(code, sketch.getName(), true); 44 | 45 | } catch (ECMAException ecma) { 46 | // [0] "SyntaxError: test2:6:14 Expected ; but found !" 47 | // [1] "SyntaxError" 48 | // [2] " test2" 49 | // [3] "6" 50 | // [4] "14" 51 | // [5] "Expected ; but found !" 52 | String[] m = PApplet.match(ecma.getMessage(), 53 | "(.*:\\s*)(.*):(\\d+):(\\d+)\\s+([^\n]*)"); 54 | //PApplet.printArray(m); 55 | 56 | if (m == null) { 57 | // not sure how to parse this one, just posted it as-is 58 | ecma.printStackTrace(); 59 | throw new SketchException(ecma.getMessage(), false); 60 | } else { 61 | // Subtract 1 from the result because the lines are 1-indexed. 62 | // If the parseInt fails, won't set the line or column number, 63 | // because it'll return 0 and then subtract 1, and -1 passed to 64 | // SketchException for line or column is "unknown". 65 | throw new SketchException(m[1] + m[5], codeIndex, 66 | PApplet.parseInt(m[3]) - 1, 67 | PApplet.parseInt(m[4]) - 1, 68 | false); 69 | } 70 | } catch (Exception e) { 71 | e.printStackTrace(); 72 | throw new SketchException("Internal error: check console for details"); 73 | //System.out.println(e.getClass().getName()); 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/processing/mode/p5js/build/ImportExamples.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js.build; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import processing.app.Platform; 10 | import processing.app.Util; 11 | import processing.core.PApplet; 12 | import processing.data.StringList; 13 | import processing.mode.p5js.p5jsMode; 14 | 15 | 16 | // initial setup 17 | 18 | // git clone git@github.com:processing/p5.js-website.git 19 | // cd p5.js-website.git 20 | // npm install 21 | // npm run watch 22 | 23 | // subsequent updates 24 | // cd p5.js-website.git 25 | // git pull 26 | // additional modules may be in use 27 | // npm install 28 | // npm run watch 29 | 30 | // the /examples subdirectory is ignored in git, 31 | // so seeing changes with 'git status' won't be possible 32 | 33 | public class ImportExamples extends PApplet { 34 | static final String EXAMPLE_PREFIX = 35 | "dist/assets/examples/en/"; 36 | static final String ASSETS_PREFIX = 37 | "dist/assets/examples/assets/"; 38 | 39 | 40 | @Override 41 | public void setup() { 42 | File siteFolder = sketchFile("p5.js-website"); 43 | if (!siteFolder.exists()) { 44 | System.err.println("You must first clone p5.js-website before running"); 45 | System.exit(1); 46 | } 47 | if (!new File(siteFolder, EXAMPLE_PREFIX).exists()) { 48 | System.err.println("First run 'npm run watch' inside 'p5.js-website'"); 49 | System.exit(1); 50 | } 51 | 52 | Map assetMap = new HashMap<>(); 53 | Map exampleMap = new HashMap<>(); 54 | 55 | try { 56 | String[] paths = 57 | listPaths(siteFolder.getAbsolutePath(), "recursive", "relative"); 58 | for (String path : paths) { 59 | if (path.startsWith(EXAMPLE_PREFIX) && path.endsWith(".js")) { 60 | String name = path.substring(EXAMPLE_PREFIX.length()); 61 | exampleMap.put(name, new File(siteFolder, path)); 62 | } else if (path.startsWith(ASSETS_PREFIX)) { 63 | String name = path.substring(ASSETS_PREFIX.length()); 64 | assetMap.put(name, new File(siteFolder, path)); 65 | } 66 | } 67 | 68 | File examplesFolder = sketchFile("examples"); 69 | if (examplesFolder.exists()) { 70 | Platform.deleteFile(examplesFolder); // move to trash 71 | } 72 | 73 | StringList exampleList = new StringList(exampleMap.keySet()); 74 | exampleList.sort(); 75 | StringList categories = new StringList(); 76 | 77 | File templateFolder = sketchFile("template"); 78 | 79 | for (String item : exampleList) { 80 | System.out.println(item); 81 | String[] pieces = PApplet.split(item, '/'); 82 | // keep the numbers on here so that we can get the order 83 | categories.appendUnique(pieces[0]); 84 | // remove the number prefix when writing to the folder 85 | File categoryFolder = new File(examplesFolder, fixCategory(pieces[0])); 86 | // remove the .js from the end of the name 87 | String name = "ex" + pieces[1].substring(0, pieces[1].length() - 3); 88 | File exampleFolder = new File(categoryFolder, name); 89 | if (exampleFolder.mkdirs()) { 90 | File exampleFile = new File(exampleFolder, name + ".js"); 91 | String[] lines = loadStrings(exampleMap.get(item)); 92 | //for (String line : lines) { 93 | StringList libraries = new StringList(); 94 | for (int i = 0; i < lines.length; i++) { 95 | String line = lines[i]; 96 | 97 | // sketches that need these libraries mention 'em in the comments 98 | if (line.contains("p5.dom")) { 99 | libraries.appendUnique("p5.dom"); 100 | } 101 | if (line.contains("p5.sound")) { 102 | libraries.appendUnique("p5.sound"); 103 | } 104 | 105 | //String[] m = PApplet.match(line, "[\"']assets/(.+)[\"']"); 106 | String[] m = PApplet.match(line, "[\"']assets/([^\"']+)"); 107 | if (m != null) { 108 | // Switch to using the data folder so that it 109 | // plays nicely with the PDE's "Add File" command. 110 | lines[i] = line.replace("assets/" + m[1], "data/" + m[1]); 111 | 112 | //ZipEntry entry = assetMap.get(m[1]); 113 | File entry = assetMap.get(m[1]); 114 | if (entry != null) { 115 | // saveStream() will create intermediate folders as necessary 116 | InputStream in = createInput(entry); 117 | PApplet.saveStream(new File(exampleFolder, "data/" + m[1]), in); 118 | in.close(); 119 | } else { 120 | boolean found = false; 121 | for (String ext : new String[] { ".mp3", ".ogg" }) { 122 | entry = assetMap.get(m[1] + ext); 123 | if (entry != null) { 124 | InputStream in = createInput(entry); 125 | PApplet.saveStream(new File(exampleFolder, "data/" + m[1] + ext), in); 126 | in.close(); 127 | found = true; 128 | } 129 | } 130 | if (!found) { 131 | System.err.println(item + " could not find " + m[1]); 132 | System.err.println(" " + line); 133 | } 134 | } 135 | } 136 | PApplet.saveStrings(exampleFile, lines); 137 | 138 | // Add index.html and the p5js library itself 139 | Util.copyDir(templateFolder, exampleFolder); 140 | // now for some libraries 141 | File librariesFolder = sketchFile("libraries"); 142 | for (String library : libraries) { 143 | // libraries/p5.dom/library/p5.dom.js 144 | if (!library.equals("p5.dom")) { 145 | Util.copyFile(new File(librariesFolder, library + 146 | "/library/" + library + ".min.js"), 147 | new File(exampleFolder, 148 | "libraries/" + library + ".min.js")); 149 | } 150 | } 151 | // won't be needing this one 152 | new File(exampleFolder, "sketch.js").delete(); 153 | p5jsMode.insertSketchName(exampleFolder, name); 154 | } 155 | } else { 156 | throw new RuntimeException("Couldn't make dir " + exampleFolder); 157 | } 158 | } 159 | 160 | System.out.println(); 161 | System.out.println("Example categories for p5jsMode.java:"); 162 | categories.sort(); 163 | for (String category : categories) { 164 | System.out.print("\"" + fixCategory(category) + "\", "); 165 | } 166 | System.out.println(); 167 | exit(); 168 | 169 | } catch (IOException e) { 170 | e.printStackTrace(); 171 | } 172 | } 173 | 174 | 175 | static private String fixCategory(String what) { 176 | what = what.substring(3); 177 | if ("Dom".equals(what)) { 178 | // consistent with example title and other folder naming 179 | return "DOM"; 180 | } 181 | return what.replace('_', ' '); 182 | } 183 | 184 | 185 | static public void main(String[] args) { 186 | PApplet.main(ImportExamples.class); 187 | } 188 | } -------------------------------------------------------------------------------- /src/processing/mode/p5js/p5jsBuild.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import processing.app.Sketch; 7 | import processing.app.SketchCode; 8 | import processing.app.SketchException; 9 | import processing.app.Util; 10 | 11 | import processing.core.PApplet; 12 | import processing.data.StringList; 13 | 14 | import javax.swing.text.BadLocationException; 15 | import javax.swing.text.Document; 16 | 17 | 18 | public class p5jsBuild { 19 | static final String P5JS_UNMINIFIED = "p5.js"; 20 | static final String P5JS_MINIFIED = "p5.min.js"; 21 | 22 | static final String HTML_PREFIX = 23 | ""; 24 | static final String HTML_SUFFIX = 25 | ""; 26 | 27 | 28 | static void updateHtml(Sketch sketch) throws SketchException { 29 | File sketchFolder = sketch.getFolder(); 30 | SketchCode indexHtmlCode = p5jsMode.findIndexHtml(sketch); 31 | if (indexHtmlCode == null) { 32 | System.err.println("Re-creating the missing index.html file."); 33 | // If the index.html file has been removed, 34 | // replace with a fresh copy from the template 35 | File templateFolder = sketch.getMode().getTemplateFolder(); 36 | File htmlTemplate = new File(templateFolder, "index.html"); 37 | 38 | /* 39 | File htmlFile = new File(sketchFolder, "index.html"); 40 | try { 41 | Util.copyFile(htmlTemplate, htmlFile); 42 | } catch (IOException e) { 43 | throw new SketchException(e.getMessage()); 44 | } 45 | */ 46 | /* 47 | // Reload the sketch so that the index.html file shows up. 48 | // But *do not* reload if there are edits to other tabs, 49 | // otherwise the changes will be lost. 50 | if (!sketch.isModified()) { 51 | sketch.reload(); 52 | } 53 | */ 54 | // This will copy index.html from the template folder 55 | // and take care of creating a new tab for it. 56 | // This will include @@sketch@@, but that's ok, 57 | // because it will be rewritten immediately below. 58 | sketch.addFile(htmlTemplate); 59 | 60 | // Get the (formerly missing, now new) tab so we can work with it 61 | indexHtmlCode = p5jsMode.findIndexHtml(sketch); 62 | } 63 | 64 | // if (indexHtmlCode != null && indexHtmlCode.isModified()) { 65 | // // TODO can we throw an exception here? how often is this happening? 66 | // System.err.println("Could not update index.html because it has unsaved changes."); 67 | // return; 68 | // } 69 | 70 | StringList insert; 71 | insert = new StringList(); 72 | 73 | // load p5.js first 74 | insert.append(scriptPath("libraries/" + P5JS_MINIFIED)); 75 | //insert.append(scriptPath("libraries/" + P5JS_UNMINIFIED)); 76 | 77 | // then other entries from /libraries 78 | File librariesFolder = new File(sketchFolder, "libraries"); 79 | File[] libraryList = librariesFolder.listFiles(file -> { 80 | if (!file.isDirectory()) { // not doing subdirectories 81 | String name = file.getName(); 82 | //noinspection RedundantIfStatement 83 | if (!name.equals(P5JS_MINIFIED) && // already loaded first 84 | !name.equals(P5JS_UNMINIFIED) && // don't double-add 85 | name.toLowerCase().endsWith(".js")) { 86 | return true; 87 | } 88 | } 89 | return false; 90 | }); 91 | 92 | // if folder is unreadable, show a warning and recover 93 | if (libraryList == null) { 94 | System.err.println("Could not read " + librariesFolder); 95 | libraryList = new File[0]; 96 | } 97 | 98 | for (File file : libraryList) { 99 | insert.append(scriptPath("libraries/" + file.getName())); 100 | } 101 | 102 | // now the sketch code 103 | for (int ii = 0; ii < sketch.getCodeCount(); ii++) { 104 | // start at [1], and write [0] (the main code) to the file last 105 | int i = (ii + 1) % sketch.getCodeCount(); 106 | SketchCode code = sketch.getCode(i); 107 | String filename = code.getFileName(); 108 | //if (filename.endsWith(".js")) { 109 | if (code.isExtension("js")) { 110 | insert.append(scriptPath(filename)); 111 | } 112 | } 113 | 114 | /* 115 | // write the HTML based on the template 116 | String[] lines = PApplet.loadStrings(htmlFile); 117 | if (lines == null) { 118 | System.err.println("Could not read " + htmlFile); 119 | lines = new String[0]; // recover; shows more useful error msg below 120 | } 121 | String html = PApplet.join(lines, "\n"); 122 | */ 123 | 124 | String html = null; 125 | if (indexHtmlCode.getDocument() != null) { 126 | try { 127 | // If actively editing, use the text from the Document object. 128 | html = indexHtmlCode.getDocumentText(); 129 | } catch (BadLocationException ignored) { } 130 | } 131 | if (html == null) { 132 | // If there was an error, or the Document object does not yet exist 133 | // (because the tab has not been visited), fall back to getProgram(). 134 | // It will have the last version loaded from disk, or from setProgram() 135 | // being called by prepareRun() or when switching away from a tab. 136 | html = indexHtmlCode.getProgram(); 137 | } 138 | 139 | int start = html.indexOf(HTML_PREFIX); 140 | int stop = html.indexOf(HTML_SUFFIX); 141 | 142 | if (start == -1 || stop == -1) { 143 | System.out.println("p5jsMode uses a specially crafted index.html to work properly."); 144 | System.out.println("Use Sketch → Show Sketch Folder and rename index.html to something else."); 145 | System.out.println("The index.html file will be automatically re-created."); 146 | System.out.println("If there are edits you need from the renamed file,"); 147 | System.out.println("copy and paste those parts into the new index.html."); 148 | throw new SketchException("The index.html file is damaged, " + 149 | "please remove or rename it and try again.", false); 150 | } 151 | 152 | // stop point is after the suffix, but need it to be -1 up top there 153 | stop += HTML_SUFFIX.length(); 154 | 155 | final String newInsert = 156 | HTML_PREFIX + "\n" + insert.join("\n") + "\n " + HTML_SUFFIX; 157 | 158 | if (indexHtmlCode.getDocument() != null) { 159 | // If the tab has been opened at least once, work on the Document object 160 | try { 161 | Document doc = indexHtmlCode.getDocument(); 162 | String oldInsert = doc.getText(start, stop - start); 163 | if (!oldInsert.equals(newInsert)) { 164 | doc.remove(start, stop - start); 165 | doc.insertString(start, newInsert, null); 166 | indexHtmlCode.setModified(true); 167 | } 168 | } catch (BadLocationException ble) { 169 | ble.printStackTrace(); 170 | throw new SketchException("Error while updating index.html"); 171 | } 172 | } 173 | // XX Tab has not been visited, so no Document object available yet. 174 | // XX Use setProgram() to set the text without doing a save. 175 | // Need to call setProgram() regardless, so that Save As will work. 176 | String newHtml = 177 | html.substring(0, start) + newInsert + html.substring(stop); 178 | if (!newHtml.equals(html)) { 179 | indexHtmlCode.setProgram(newHtml); 180 | indexHtmlCode.setModified(true); 181 | } 182 | 183 | /* 184 | html = html.substring(0, start) + 185 | HTML_PREFIX + "\n" + insert.join("\n") + "\n " + HTML_SUFFIX + 186 | html.substring(stop + HTML_SUFFIX.length()); 187 | 188 | PApplet.saveStrings(htmlFile, PApplet.split(html, '\n')); 189 | 190 | // reload in the Editor after modifications 191 | if (indexHtmlCode != null) { 192 | indexHtmlCode.load(); 193 | // unfortunate hack that seems necessary at the moment? 194 | indexHtmlCode.setDocument(null); 195 | 196 | //} else { 197 | // TODO when the index.html file has been removed (and now, rewritten), 198 | // add its tab to the PDE, and mark it as opened so that we don't 199 | // get the message about the file being modified externally. 200 | } 201 | */ 202 | } 203 | 204 | 205 | static String scriptPath(String path) { 206 | return " "; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/processing/mode/p5js/p5jsEditor.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js; 2 | 3 | import java.awt.EventQueue; 4 | import java.awt.event.WindowAdapter; 5 | import java.awt.event.WindowEvent; 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.net.BindException; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import javax.script.ScriptException; 13 | import javax.swing.JMenu; 14 | import javax.swing.JMenuItem; 15 | import javax.swing.event.DocumentEvent; 16 | import javax.swing.event.DocumentListener; 17 | import javax.swing.text.AbstractDocument; 18 | import javax.swing.text.BadLocationException; 19 | import javax.swing.text.Document; 20 | 21 | import processing.app.Base; 22 | import processing.app.Formatter; 23 | import processing.app.Library; 24 | import processing.app.Messages; 25 | import processing.app.Mode; 26 | import processing.app.Platform; 27 | import processing.app.Problem; 28 | import processing.app.SketchCode; 29 | import processing.app.SketchException; 30 | import processing.app.Util; 31 | import processing.app.syntax.JEditTextArea; 32 | import processing.app.syntax.PdeTextArea; 33 | import processing.app.syntax.PdeTextAreaDefaults; 34 | import processing.app.ui.Editor; 35 | import processing.app.ui.EditorException; 36 | import processing.app.ui.EditorFooter; 37 | import processing.app.ui.EditorState; 38 | import processing.app.ui.EditorToolbar; 39 | import processing.app.ui.Toolkit; 40 | 41 | import processing.data.StringList; 42 | import processing.mode.java.AutoFormat; 43 | import processing.mode.java.JavaInputHandler; 44 | import processing.mode.p5js.server.HttpServer; 45 | 46 | import processing.data.JSONArray; 47 | import processing.data.JSONObject; 48 | 49 | 50 | public class p5jsEditor extends Editor { 51 | // One server per Editor, same port used for the same Editor 52 | HttpServer server; 53 | // Try to maintain the same port for this Editor window 54 | int port; 55 | 56 | static final boolean USE_LINTER = true; 57 | // object to handle linting, invoke once b/c heavyweight 58 | static Linter linter; 59 | 60 | ErrorWatcher watcher; 61 | 62 | 63 | protected p5jsEditor(Base base, String path, 64 | EditorState state, Mode mode) throws EditorException { 65 | super(base, path, state, mode); 66 | 67 | if (linter == null) { 68 | linter = new Linter(this); 69 | } 70 | 71 | // if getting started with the template, get things cleaned up 72 | if (sketch.isUntitled()) { 73 | rebuildHtml(); 74 | } 75 | 76 | // if the "libraries" folder doesn't exist, re-create it 77 | File sketchLibs = new File(sketch.getFolder(), "libraries"); 78 | if (!sketchLibs.exists()) { 79 | File templateLibs = new File(mode.getTemplateFolder(), "libraries"); 80 | try { 81 | Util.copyDir(templateLibs, sketchLibs); 82 | } catch (IOException e) { 83 | throw new EditorException("Could not re-create the libraries folder for this sketch.", e); 84 | } 85 | } 86 | // if the index.html file has gone missing, re-create it 87 | if (p5jsMode.findIndexHtml(sketch) == null) { 88 | rebuildHtml(); 89 | } 90 | 91 | enableDisableModeMenu(); 92 | 93 | initWatcher(); 94 | startServer(); 95 | } 96 | 97 | 98 | @Override 99 | protected void handleOpenInternal(String path) throws EditorException { 100 | if (path.endsWith("sketch.js")) { 101 | // Because of poor Exception handling by me, there isn't a good way to 102 | // recover from this situation without throwing this ridiculous blob of 103 | // text into the Exception message itself. Ben 1, Software Engineering 0. 104 | throw new EditorException(""" 105 | Cannot open this type of sketch. 106 | This version of p5jsMode does not play nicely with sketches created by other editors. 107 | To use this code, please use File > New to create a new sketch and copy your code into it. 108 | See https://github.com/fathominfo/processing-p5js-mode/issues/14 for updates or details."""); 109 | } 110 | super.handleOpenInternal(path); 111 | } 112 | 113 | 114 | @Override 115 | protected JEditTextArea createTextArea() { 116 | return new PdeTextArea(new PdeTextAreaDefaults(), 117 | new JavaInputHandler(this), this); 118 | } 119 | 120 | 121 | /** 122 | * Create and return the toolbar (tools above text area), 123 | * implements abstract Editor.createToolbar(), 124 | * called in Editor constructor to add the toolbar to the window. 125 | * 126 | * @return an EditorToolbar, in our case a JavaScriptToolbar 127 | * @see processing.mode.p5js.p5jsToolbar 128 | */ 129 | @Override 130 | public EditorToolbar createToolbar() { 131 | return new p5jsToolbar(this); 132 | } 133 | 134 | 135 | @Override 136 | public EditorFooter createFooter() { 137 | EditorFooter footer = super.createFooter(); 138 | addErrorTable(footer); 139 | return footer; 140 | } 141 | 142 | 143 | /** 144 | * Create a formatter to prettify code, 145 | * implements abstract Editor.createFormatter(), 146 | * called by Editor.handleAutoFormat() to handle menu item or shortcut 147 | * 148 | * @return the formatter to handle formatting of code. 149 | */ 150 | @Override 151 | public Formatter createFormatter() { 152 | return new AutoFormat(); 153 | } 154 | 155 | 156 | /** 157 | * Build the "File" menu, 158 | * implements abstract Editor.buildFileMenu(), 159 | * called by Editor.buildMenuBar() to generate the app menu for the editor window 160 | * 161 | * @return JMenu containing the menu items for "File" menu 162 | */ 163 | @Override 164 | public JMenu buildFileMenu() { 165 | return buildFileMenu(null); 166 | } 167 | 168 | 169 | /** 170 | * Build the "Sketch" menu, implements abstract Editor.buildSketchMenu(), 171 | * called by Editor.buildMenuBar(). 172 | * @return JMenu containing the menu items for "Sketch" menu 173 | */ 174 | @Override 175 | public JMenu buildSketchMenu() { 176 | JMenuItem runItem = Toolkit.newJMenuItem("Run", 'R'); 177 | runItem.addActionListener(e -> handleRun()); 178 | 179 | JMenuItem stopItem = new JMenuItem("Stop"); 180 | stopItem.addActionListener(e -> handleStop()); 181 | 182 | return buildSketchMenu(new JMenuItem[] { runItem, stopItem }); 183 | } 184 | 185 | 186 | JMenuItem resetPeaFiveItem; 187 | JMenuItem resetIndexItem; 188 | 189 | @Override 190 | public JMenu buildModeMenu() { 191 | JMenu menu = new JMenu("p5.js"); 192 | // JMenuItem item; 193 | 194 | resetPeaFiveItem = new JMenuItem("Replace p5.js library"); 195 | resetPeaFiveItem.addActionListener(e -> { 196 | // copy p5.min.js to the libraries folder 197 | File sourceFile = 198 | new File(mode.getTemplateFolder(), "libraries/p5.min.js"); 199 | File targetFile = 200 | new File(sketch.getFolder(), "libraries/p5.min.js"); 201 | try { 202 | Util.copyFile(sourceFile, targetFile); 203 | enableDisableModeMenu(); // disable the menu item 204 | 205 | } catch (IOException ioe) { 206 | Messages.showTrace("Could not update the p5.js library", 207 | "An error occurred while trying to replace\n" + 208 | targetFile.getAbsolutePath(), ioe, false); 209 | } 210 | }); 211 | menu.add(resetPeaFiveItem); 212 | 213 | resetIndexItem = new JMenuItem("Re-create index.html"); 214 | resetIndexItem.addActionListener(e -> { 215 | try { 216 | // get a fresh index.html from the template 217 | File sourceFile = 218 | new File(mode.getTemplateFolder(), "index.html"); 219 | 220 | // Should not be possible for this to return null, because the watcher 221 | // will have detected removal and rewritten the file. Uh, right? 222 | SketchCode indexHtmlCode = p5jsMode.findIndexHtml(sketch); 223 | if (indexHtmlCode != null) { 224 | /* 225 | Document doc = indexHtmlCode.getDocument(); 226 | if (doc != null) { 227 | doc.remove(0, doc.getLength()); 228 | doc.insertString(0, template, null); 229 | } else { 230 | indexHtmlCode.setProgram(template); 231 | } 232 | */ 233 | // Grab the template. It will have @@sketch@@, but 234 | String template = Util.loadFile(sourceFile); 235 | 236 | // Instead of juggling program and doc, force it 237 | // via setProgram() and reset the Document object. 238 | indexHtmlCode.setProgram(template); 239 | indexHtmlCode.setDocument(null); 240 | //setCode(indexHtmlCode); 241 | 242 | // Now insert all the tabs and libraries for this sketch. 243 | p5jsBuild.updateHtml(sketch); 244 | // ...and save the new tab to disk (otherwise it will look correct 245 | // in the PDE, but the @@sketch@@ version is still on disk). 246 | indexHtmlCode.save(); 247 | 248 | // Now update the code in the editor window. 249 | // This will set index.html as the current tab. 250 | // If already current, it will reset the Document object. 251 | setCode(indexHtmlCode); 252 | } 253 | 254 | /* 255 | File targetFile = 256 | new File(sketch.getFolder(), "index.html"); 257 | Util.copyFile(sourceFile, targetFile); 258 | 259 | // swap @@ entries from the template with the sketch name 260 | p5jsMode.insertSketchName(sketch.getFolder(), sketch.getName()); 261 | 262 | // load the new one back into the editor 263 | SketchCode indexHtmlCode = p5jsMode.findIndexHtml(sketch); 264 | 265 | if (indexHtmlCode != null) { 266 | indexHtmlCode.load(); 267 | // unfortunate hack that seems necessary at the moment? 268 | indexHtmlCode.setDocument(null); 269 | // this gets the editor text area to update 270 | setCode(indexHtmlCode); 271 | } 272 | */ 273 | 274 | } catch (Exception ex) { 275 | Messages.showTrace("Error", "Could not write index.html.", ex, false); 276 | } 277 | }); 278 | menu.add(resetIndexItem); 279 | 280 | enableDisableModeMenu(); 281 | return menu; 282 | } 283 | 284 | 285 | private void enableDisableModeMenu() { 286 | if (sketch != null) { 287 | File sourceFile = 288 | new File(mode.getTemplateFolder(), "libraries/p5.min.js"); 289 | File targetFile = 290 | new File(sketch.getFolder(), "libraries/p5.min.js"); 291 | 292 | // not a perfect check, but good enough for this usage 293 | boolean p5jsDifferent = 294 | (sourceFile.lastModified() != targetFile.lastModified()) || 295 | (sourceFile.length() != targetFile.length()); 296 | resetPeaFiveItem.setEnabled(p5jsDifferent); 297 | } 298 | } 299 | 300 | 301 | /** 302 | * Build the "Help" menu, implements abstract Editor.buildHelpMenu() 303 | * @return JMenu containing the menu items for "Help" menu 304 | */ 305 | @Override 306 | public JMenu buildHelpMenu() { 307 | JMenu menu = new JMenu("Help"); 308 | JMenuItem item; 309 | 310 | item = new JMenuItem("Getting Started"); 311 | item.addActionListener(e -> Platform.openURL("https://p5js.org/get-started/")); 312 | menu.add(item); 313 | 314 | item = new JMenuItem("Reference"); 315 | item.addActionListener(e -> Platform.openURL("https://p5js.org/reference/")); 316 | menu.add(item); 317 | 318 | item = Toolkit.newJMenuItemShift("Find in Reference", 'F'); 319 | item.addActionListener(e -> handleFindReference()); 320 | menu.add(item); 321 | 322 | menu.addSeparator(); 323 | 324 | item = new JMenuItem("Visit p5js.org"); 325 | item.addActionListener(e -> Platform.openURL("https://p5js.org/")); 326 | menu.add(item); 327 | 328 | item = new JMenuItem("Visit the Forum"); 329 | item.addActionListener(e -> Platform.openURL("https://forum.processing.org/")); 330 | menu.add(item); 331 | 332 | item = new JMenuItem("View p5js on Github"); 333 | item.addActionListener(e -> Platform.openURL("https://github.com/processing/p5.js")); 334 | menu.add(item); 335 | 336 | return menu; 337 | } 338 | 339 | 340 | /** 341 | * Returns the default commenting prefix for comment/uncomment command, 342 | * called from Editor.handleCommentUncomment() 343 | */ 344 | @Override 345 | public String getCommentPrefix() { 346 | return "//"; 347 | } 348 | 349 | 350 | /** 351 | * The EditorHeader is rebuilt when tabs are renamed, added, or removed. 352 | * Use this as a callback to rewrite the HTML file from the template. 353 | */ 354 | @Override 355 | public void rebuildHeader() { 356 | super.rebuildHeader(); 357 | rebuildHtml(); 358 | } 359 | 360 | 361 | /** 362 | * Stop the runner, in our case this is the server, 363 | * implements abstract Editor.internalCloseRunner(), 364 | * called from Editor.prepareRun() 365 | * 366 | * Called when the window is going to be reused for another sketch. 367 | */ 368 | @Override 369 | public void internalCloseRunner() { 370 | handleStop(); 371 | } 372 | 373 | 374 | /** 375 | * Implements abstract Editor.deactivateRun() 376 | */ 377 | @Override 378 | public void deactivateRun() { 379 | toolbar.deactivateRun(); 380 | } 381 | 382 | 383 | public void handleRun() { 384 | toolbar.activateRun(); 385 | 386 | try { 387 | // Make sure the sketch folder still exists, and the SketchCode objects 388 | // are updated to include any text changes from the Editor. 389 | prepareRun(); 390 | // write the HTML here in case we need temp files 391 | //p5jsBuild.updateHtml(sketch); // disabling [fry 230208] 392 | 393 | if (checkErrors()) { 394 | toolbar.deactivateRun(); 395 | 396 | } else { 397 | if (server == null || server.isDead()) { 398 | startServer(); 399 | } 400 | statusNotice("Server running at " + server.getAddress()); 401 | StringList local = server.getLocalAddresses(); 402 | if (local.size() > 0) { 403 | System.out.println("To connect from another device on the local network, try:"); 404 | for (String item : local) { 405 | System.out.println(item); 406 | } 407 | } 408 | // in 4.0 beta 5, some fixes to how openURL() works 409 | Platform.openURL(server.getAddress()); 410 | } 411 | } catch (Exception e) { 412 | statusError(e); 413 | } 414 | } 415 | 416 | 417 | public void handleStop() { 418 | /* 419 | try { 420 | p5jsBuild.cleanTempFiles(sketch); 421 | } catch (IOException e) { 422 | e.printStackTrace(); // TODO ignore? 423 | } 424 | */ 425 | toolbar.deactivateRun(); 426 | } 427 | 428 | 429 | /** Find the first entry in the list of Problem objects that's an error. */ 430 | private Problem findError() { 431 | for (Problem p : problems) { 432 | if (p.isError()) return p; 433 | } 434 | return null; 435 | } 436 | 437 | 438 | /** 439 | * Check for errors before launching 440 | * @return true if fatal errors found 441 | */ 442 | private boolean checkErrors() { 443 | // if using the linter and there's an error, fail with that 444 | if (USE_LINTER) { 445 | Problem p = findError(); 446 | if (p != null) { 447 | int line = p.getLineNumber(); 448 | int column = p.getStartOffset() - getLineStartOffset(line); 449 | statusError(new SketchException(p.getMessage(), 450 | p.getTabIndex(), 451 | p.getLineNumber(), 452 | column, 453 | false)); 454 | return true; 455 | } 456 | } else { // otherwise, just use the basic parser as in 1.1 and earlier 457 | try { 458 | NashornParse.handle(sketch); 459 | 460 | } catch (SketchException se) { 461 | statusError(se); 462 | return true; 463 | } 464 | } 465 | return false; 466 | } 467 | 468 | 469 | @Override 470 | public void showReference(String filename) { 471 | // this will give us "blah_.html" or "blah.html" 472 | // first remove the .html from the end 473 | String term = filename.substring(0, filename.length() - 5); 474 | // p5js doesn't do the underscore thing (functions and vars 475 | // can't have the same name in JS anyway, those lucky ducks) 476 | if (term.endsWith("_")) { 477 | term = term.substring(0, term.length() - 1); 478 | } 479 | Platform.openURL("https://p5js.org/reference/#/p5/" + term); 480 | } 481 | 482 | 483 | // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 484 | 485 | 486 | private void initWatcher() { 487 | // window will fire an activated event, so don't call startWatcher() 488 | addWindowListener(new WindowAdapter() { 489 | 490 | @Override 491 | public void windowActivated(WindowEvent e) { 492 | startWatcher(); 493 | } 494 | 495 | @Override 496 | public void windowDeactivated(WindowEvent e) { 497 | stopWatcher(); 498 | } 499 | }); 500 | 501 | // install the ErrorListener on all js and json tabs 502 | for (SketchCode sc : sketch.getCode()) { 503 | checkDocumentListener(sc); 504 | } 505 | } 506 | 507 | 508 | private void startWatcher() { 509 | watcher = new ErrorWatcher(); 510 | watcher.start(); 511 | } 512 | 513 | 514 | public void stopWatcher() { 515 | watcher = null; 516 | } 517 | 518 | 519 | class ErrorWatcher extends Thread { 520 | 521 | @Override 522 | public void run() { 523 | while (watcher == this) { 524 | if (System.currentTimeMillis() > nextUpdate) { 525 | checkLint(); 526 | } 527 | try { 528 | Thread.sleep(1000); 529 | } catch (InterruptedException e) { 530 | e.printStackTrace(); 531 | } 532 | } 533 | } 534 | } 535 | 536 | 537 | private void checkLint() { 538 | try { 539 | if (linter != null) { 540 | // Grab the code from the current tab. This should be the one that 541 | // changed, but just in case, we'll double-check that it's a JS tab. 542 | // (Removing/auto-replacing index.html can cause this.) 543 | SketchCode code = sketch.getCurrentCode(); 544 | if (code.isExtension("js")) { 545 | try { 546 | JSONArray result = linter.lint(code.getDocumentText()); 547 | if (result != null) { 548 | parseErrors(result); 549 | // no more updates until this is reset by a document change 550 | nextUpdate = Long.MAX_VALUE; 551 | } 552 | } catch (BadLocationException ignored) { 553 | } // ignore for now 554 | } 555 | } 556 | } catch (ScriptException e1) { 557 | e1.printStackTrace(); 558 | } 559 | } 560 | 561 | 562 | private void parseErrors(JSONArray result) { 563 | // System.out.println(result.format(2)); 564 | final int tabIndex = sketch.getCurrentCodeIndex(); 565 | 566 | List problems = new ArrayList<>(); 567 | 568 | //for (JSONObject obj : result.) 569 | for (int i = 0; i < result.size(); i++) { 570 | JSONObject obj = result.getJSONObject(i); 571 | 572 | String errorCode = obj.getString("code", null); 573 | final boolean error = errorCode != null && errorCode.startsWith("E"); 574 | 575 | final String message = obj.getString("reason"); 576 | 577 | final int line = obj.getInt("line", 1) - 1; 578 | final int start = getLineStartOffset(line); 579 | // use the evidence or the entire line 580 | String evidence = obj.getString("evidence", getLineText(line)); 581 | final int stop = start + evidence.length(); 582 | 583 | problems.add(new Problem() { 584 | 585 | @Override 586 | public boolean isWarning() { 587 | return !error; 588 | } 589 | 590 | @Override 591 | public boolean isError() { 592 | return error; 593 | } 594 | 595 | @Override 596 | public int getTabIndex() { 597 | return tabIndex; 598 | } 599 | 600 | @Override 601 | public int getStartOffset() { 602 | return start; 603 | } 604 | 605 | @Override 606 | public int getStopOffset() { 607 | return stop; 608 | } 609 | 610 | @Override 611 | public String getMessage() { 612 | return message; 613 | } 614 | 615 | @Override 616 | public int getLineNumber() { 617 | return line; 618 | } 619 | }); 620 | } 621 | setProblemList(problems); 622 | } 623 | 624 | 625 | static final int DELAY_BEFORE_UPDATE = 650; 626 | long nextUpdate; 627 | 628 | 629 | @Override 630 | public void sketchChanged() { 631 | nextUpdate = System.currentTimeMillis() + DELAY_BEFORE_UPDATE; 632 | } 633 | 634 | 635 | final DocumentListener sketchChangedListener = new DocumentListener() { 636 | @Override 637 | public void insertUpdate(DocumentEvent e) { 638 | sketchChanged(); 639 | } 640 | 641 | @Override 642 | public void removeUpdate(DocumentEvent e) { 643 | sketchChanged(); 644 | } 645 | 646 | @Override 647 | public void changedUpdate(DocumentEvent e) { 648 | sketchChanged(); 649 | } 650 | }; 651 | 652 | 653 | private boolean hasListener(Document doc) { 654 | for (DocumentListener dl : ((AbstractDocument) doc).getDocumentListeners()) { 655 | if (dl == sketchChangedListener) { 656 | return true; 657 | } 658 | } 659 | return false; 660 | } 661 | 662 | 663 | private void checkDocumentListener(SketchCode sketchCode) { 664 | //if (sketchCode.isExtension("js") || sketchCode.isExtension("json")) { 665 | if (sketchCode.isExtension("js")) { 666 | Document doc = sketchCode.getDocument(); 667 | if (doc != null) { 668 | if (!hasListener(doc)) { 669 | doc.addDocumentListener(sketchChangedListener); 670 | } 671 | } 672 | } 673 | } 674 | 675 | 676 | /** 677 | * Event handler called when switching between tabs. 678 | * @param code tab to switch to 679 | */ 680 | @Override 681 | public void setCode(SketchCode code) { 682 | super.setCode(code); 683 | checkDocumentListener(code); 684 | } 685 | 686 | 687 | @Override 688 | public void dispose() { 689 | // set the error thread to null when closing 690 | stopWatcher(); 691 | } 692 | 693 | 694 | // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 695 | 696 | 697 | protected boolean rebuildHtml() { 698 | try { 699 | p5jsBuild.updateHtml(sketch); 700 | return true; 701 | } catch (Exception e) { 702 | statusError(e); 703 | } 704 | return false; 705 | } 706 | 707 | 708 | @Override 709 | public boolean handleSaveAs() { 710 | if (super.handleSaveAs()) { 711 | EventQueue.invokeLater(() -> { 712 | while (sketch.isSaving()) { // wait until Save As completes 713 | try { 714 | Thread.sleep(5); 715 | } catch (InterruptedException ignored) { } 716 | } 717 | // This will rebuild the index.html code in the Editor 718 | rebuildHtml(); 719 | // …but we still need to manually save index.html with the change. 720 | SketchCode indexHtmlCode = p5jsMode.findIndexHtml(sketch); 721 | if (indexHtmlCode != null) { 722 | try { 723 | indexHtmlCode.save(); 724 | } catch (IOException e) { 725 | e.printStackTrace(); 726 | } 727 | } 728 | }); 729 | return true; // kind of a farce 730 | } 731 | return false; 732 | } 733 | 734 | 735 | /** 736 | * Start the internal server for this sketch. 737 | */ 738 | protected void startServer() { 739 | // System.out.println("restarting server? " + server + " " + (server != null && server.isDead())); 740 | if (server != null && server.isDead()) { 741 | // if server hung or something else went wrong... stop it. 742 | server.stop(); 743 | server = null; 744 | } 745 | 746 | if (port == 0) { 747 | resetPort(); 748 | } 749 | try { 750 | server = new HttpServer(this, port); 751 | 752 | } catch (BindException be) { 753 | // If the port is in use, try another. Only do this once, 754 | // because it may be due to a firewall or other circumstances. 755 | resetPort(); 756 | try { 757 | server = new HttpServer(this, port); 758 | } catch (IOException ioe) { 759 | statusError(ioe); // error out here if still trouble 760 | } 761 | } catch (IOException e) { // other unknown type of exception 762 | statusError(e); 763 | } 764 | 765 | if (server != null) { // actually kick off the listening threads 766 | server.start(); 767 | } 768 | } 769 | 770 | 771 | void resetPort() { 772 | port = (int) (8000 + Math.random() * 1000); 773 | } 774 | 775 | 776 | // method is still here, though we're never gonna stop the server 777 | protected void stopServer() { 778 | if (server != null) { 779 | server.stop(); 780 | } 781 | } 782 | 783 | 784 | /** 785 | * Create or get the sketch's properties file 786 | * @return the sketch properties file or null 787 | */ 788 | protected File getSketchPropertiesFile() { 789 | File sketchPropsFile = 790 | new File(getSketch().getFolder(), "sketch.properties"); 791 | if (!sketchPropsFile.exists()) { 792 | try { 793 | sketchPropsFile.createNewFile(); 794 | } catch (IOException ioe) { 795 | ioe.printStackTrace(); 796 | statusError("Unable to create sketch properties file!"); 797 | return null; 798 | } 799 | } 800 | return sketchPropsFile; 801 | } 802 | 803 | 804 | @Override 805 | public void handleImportLibrary(String name) { 806 | // unlike the other Modes, this is actually adding the library code 807 | Library library = mode.findLibraryByName(name); 808 | File folder = new File(library.getFolder(), "library"); 809 | try { 810 | Util.copyDir(folder, new File(sketch.getFolder(), "libraries")); 811 | statusNotice("Copied " + name + " to the libraries folder of this sketch."); 812 | } catch (IOException e) { 813 | statusError(e); 814 | } 815 | } 816 | } 817 | -------------------------------------------------------------------------------- /src/processing/mode/p5js/p5jsLibrary.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js; 2 | 3 | import java.io.File; 4 | 5 | import processing.app.Library; 6 | 7 | 8 | public class p5jsLibrary extends Library { 9 | 10 | public p5jsLibrary(File folder) { 11 | super(folder); 12 | } 13 | 14 | 15 | @Override 16 | protected void handle() { 17 | // no platform-specific stuff to do here; clear out the superclass 18 | // parsing of the .jar file and whatnot 19 | } 20 | } -------------------------------------------------------------------------------- /src/processing/mode/p5js/p5jsMode.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.ArrayList; 6 | 7 | import processing.app.*; 8 | import processing.app.syntax.TokenMarker; 9 | import processing.app.ui.*; 10 | import processing.core.PApplet; 11 | 12 | 13 | public class p5jsMode extends Mode { 14 | private p5jsEditor jsEditor; 15 | 16 | 17 | public p5jsMode (Base base, File folder) { 18 | super(base, folder); 19 | } 20 | 21 | 22 | /** 23 | * Called to create the actual editor when needed (once per Sketch) 24 | */ 25 | @Override 26 | public Editor createEditor(Base base, String path, 27 | EditorState state) throws EditorException { 28 | jsEditor = new p5jsEditor(base, path, state, this); 29 | return jsEditor; 30 | } 31 | 32 | 33 | /** 34 | * Called from Base to get the Editor for this mode. 35 | */ 36 | public Editor getEditor() { 37 | return jsEditor; 38 | } 39 | 40 | 41 | @Override 42 | public File[] getKeywordFiles() { 43 | return new File[] { 44 | Platform.getContentFile("modes/java/keywords.txt"), 45 | new File(folder, "keywords.txt") 46 | }; 47 | } 48 | 49 | 50 | @Override 51 | public TokenMarker getTokenMarker(SketchCode code) { 52 | String ext = code.getExtension(); 53 | 54 | if (ext.equals("js") || ext.equals("json")) { 55 | return tokenMarker; 56 | 57 | } else if (code.isExtension("html")) { 58 | return new HtmlTokenMarker(); 59 | 60 | // } else if (code.isExtension("css")) { 61 | // System.out.println("no highlight for " + code.getFile()); 62 | // return null; 63 | } 64 | return null; // no styling 65 | } 66 | 67 | 68 | /** 69 | * Return pretty title of this mode for menu listing and such 70 | */ 71 | @Override 72 | public String getTitle() { 73 | return "p5.js"; 74 | } 75 | 76 | 77 | // public EditorToolbar createToolbar(Editor editor) { } 78 | 79 | 80 | // public Formatter createFormatter() { } 81 | 82 | 83 | // public Editor createEditor(Base base, String path, int[] location) { } 84 | 85 | 86 | /** 87 | * Get a list of folders that contain examples, ordered by the way they 88 | * should show up in the window or menu. 89 | */ 90 | @Override 91 | public File[] getExampleCategoryFolders() { 92 | final String[] titles = { 93 | "Structure", "Form", "Data", "Arrays", "Control", "Image", "Color", 94 | "Math", "Simulate", "Interaction", "Objects", "Lights", "Motion", 95 | "Instance Mode", "DOM", "Drawing", "Transform", "Typography", 96 | "3D", "Input", "Advanced Data", "Sound", "Mobile", "Hello P5" 97 | }; 98 | 99 | File[] outgoing = new File[titles.length]; 100 | for (int i = 0; i < titles.length; i++) { 101 | outgoing[i] = new File(examplesFolder, titles[i]); 102 | } 103 | return outgoing; 104 | } 105 | 106 | 107 | @Override 108 | public void rebuildLibraryList() { 109 | coreLibraries = new ArrayList<>(); 110 | Library soundLibrary = 111 | new p5jsLibrary(new File(getLibrariesFolder(), "p5.sound")); 112 | coreLibraries.add(soundLibrary); 113 | 114 | // no contribs for now, figure this out later 115 | contribLibraries = new ArrayList<>(); 116 | } 117 | 118 | 119 | @Override 120 | public boolean requireExampleCompatibility() { 121 | return true; 122 | } 123 | 124 | 125 | /** 126 | * Return the default extension for this mode. 127 | */ 128 | @Override 129 | public String getDefaultExtension() { 130 | return "js"; 131 | } 132 | 133 | 134 | /** 135 | * The list of extensions that should show up as tabs. 136 | */ 137 | @Override 138 | public String[] getExtensions () { 139 | return new String[] { "js", "html", "css" }; 140 | } 141 | 142 | 143 | /** 144 | * Return list of file and folder names that should be ignored on Save As. 145 | * Starting in Processing 3.2, this can return null (otherwise it should be 146 | * a zero length String array if there's nothing to ignore). 147 | */ 148 | @Override 149 | public String[] getIgnorable() { 150 | return null; 151 | } 152 | 153 | 154 | /** 155 | * Calls the superclass implementation, then calls buildIndex() 156 | * to rewrite index.html with the sketch name. 157 | */ 158 | @Override 159 | public File addTemplateFiles(File sketchFolder, 160 | String sketchName) throws IOException { 161 | File mainFile = super.addTemplateFiles(sketchFolder, sketchName); 162 | insertSketchName(sketchFolder, sketchName); 163 | return mainFile; 164 | } 165 | 166 | 167 | /** 168 | * Write the index.html file. Broken out for ImportExamples. 169 | * Only handles cases where the sketch has no libraries and only 170 | * a single tab: 1) ImportExamples and 2) an Untitled sketch. 171 | */ 172 | static public void insertSketchName(File sketchFolder, 173 | String sketchName) throws IOException { 174 | File indexFile = new File(sketchFolder, "index.html"); 175 | String[] lines = PApplet.loadStrings(indexFile); 176 | if (lines == null) { 177 | throw new IOException("Could not read " + indexFile); 178 | } 179 | String program = PApplet.join(lines, "\n"); 180 | program = program.replaceAll("@@sketch@@", sketchName + ".js"); 181 | PApplet.saveStrings(indexFile, PApplet.split(program, '\n')); 182 | } 183 | 184 | 185 | static SketchCode findIndexHtml(Sketch sketch) { 186 | for (SketchCode code : sketch.getCode()) { 187 | if (code.getFileName().equals("index.html")) { 188 | return code; 189 | } 190 | } 191 | return null; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/processing/mode/p5js/p5jsToolbar.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js; 2 | 3 | import processing.app.ui.Editor; 4 | import processing.app.ui.EditorToolbar; 5 | 6 | 7 | public class p5jsToolbar extends EditorToolbar { 8 | 9 | public p5jsToolbar(Editor editor) { 10 | super(editor); 11 | } 12 | 13 | 14 | @Override 15 | public void handleRun(int modifiers) { 16 | p5jsEditor jsEditor = (p5jsEditor) editor; 17 | jsEditor.handleRun(); 18 | } 19 | 20 | 21 | @Override 22 | public void handleStop() { 23 | p5jsEditor jsEditor = (p5jsEditor) editor; 24 | jsEditor.handleStop(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/processing/mode/p5js/server/CarlOrff.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js.server; 2 | 3 | import java.io.*; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.zip.GZIPOutputStream; 6 | 7 | 8 | /** 9 | * A wrapped PrintStream that uses CRLF to indicate newlines. 10 | */ 11 | public class CarlOrff { 12 | PrintStream ps; 13 | 14 | 15 | public CarlOrff(final OutputStream out) { 16 | ps = new PrintStream(out); 17 | } 18 | 19 | 20 | public void print(String s) { 21 | ps.print(s); 22 | } 23 | 24 | 25 | public void println(String s) { 26 | print(s); 27 | println(); 28 | } 29 | 30 | 31 | public void println() { 32 | ps.print("\r\n"); 33 | } 34 | 35 | 36 | public void flush() { 37 | ps.flush(); 38 | } 39 | 40 | 41 | public void close() { 42 | ps.flush(); 43 | ps.close(); 44 | } 45 | 46 | 47 | public void write(byte[] b) { 48 | ps.write(b, 0, b.length); 49 | } 50 | 51 | 52 | public void write(byte[] b, int off, int length) { 53 | ps.write(b, off, length); 54 | } 55 | 56 | 57 | public void writeGZ(String what) throws IOException { 58 | byte[] b = what.getBytes(StandardCharsets.UTF_8); 59 | writeGZ(b); 60 | } 61 | 62 | 63 | public void writeGZ(byte[] b) throws IOException { 64 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 65 | GZIPOutputStream gzos = new GZIPOutputStream(baos); 66 | gzos.write(b); 67 | gzos.flush(); 68 | gzos.close(); 69 | write(baos.toByteArray()); 70 | println(); 71 | } 72 | 73 | 74 | public void write(Throwable t) { 75 | t.printStackTrace(ps); 76 | } 77 | 78 | 79 | public void status(int code) { 80 | ps.println("HTTP/1.1 " + code + " " + HttpServer.getStatusMessage(code)); 81 | } 82 | } -------------------------------------------------------------------------------- /src/processing/mode/p5js/server/EditorHandler.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js.server; 2 | 3 | import processing.app.SketchCode; 4 | 5 | import javax.swing.text.BadLocationException; 6 | import java.io.*; 7 | import java.nio.charset.StandardCharsets; 8 | 9 | 10 | public class EditorHandler extends GenericHandler { 11 | 12 | public EditorHandler(HttpServer server) { 13 | super(server); 14 | } 15 | 16 | 17 | byte[] loadBytes(String path, File target) throws IOException { 18 | // if this is one of the editor files, send the version on-screen 19 | // (with any edits) rather than the version on disk. 20 | for (SketchCode code : server.editor.getSketch().getCode()) { 21 | if (code.getFileName().equals(path)) { 22 | if (code.isModified()) { 23 | String program = null; 24 | if (code.getDocument() != null) { 25 | try { 26 | // if actively editing, use the text from the Document object 27 | program = code.getDocumentText(); 28 | } catch (BadLocationException ignored) { } 29 | } 30 | if (program == null) { 31 | // fall back to just getting the last code (setProgram() is 32 | // called in prepareRun() or whenever switching away from a tab) 33 | program = code.getProgram(); 34 | } 35 | //code.setProgram(program); 36 | return program.getBytes(StandardCharsets.UTF_8); 37 | } 38 | } 39 | } 40 | 41 | // no changes, or an error, send the file from disk 42 | return super.loadBytes(path, target); 43 | } 44 | } -------------------------------------------------------------------------------- /src/processing/mode/p5js/server/FavIconHandler.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js.server; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | import processing.core.PApplet; 7 | 8 | 9 | public class FavIconHandler extends Handler { 10 | 11 | /** 12 | * Send the Processing icon as a default favicon, avoiding incessant 13 | * 404s in the web browser console (to reduce unnecessary confusion). 14 | */ 15 | public FavIconHandler(HttpServer server) { 16 | super(server); 17 | } 18 | 19 | 20 | @Override 21 | public void handle(String params, CarlOrff ps) { 22 | // Nah, don't bother: why deal with guessing the mime type? 23 | //File f = new File(server.getRoot(), "favicon.ico"); 24 | 25 | final String ICON_PATH = "/icon/icon-32.png"; 26 | try (InputStream input = PApplet.class.getResourceAsStream(ICON_PATH)) { 27 | if (input == null) { 28 | ps.status(HttpServer.HTTP_NOT_FOUND); 29 | ps.println("Content-Type: text/html"); 30 | ps.println(); 31 | ps.println("Not Found"); 32 | ps.println("
" + ICON_PATH + " not found.
"); 33 | 34 | } else { 35 | try { 36 | byte[] b = PApplet.loadBytes(input); 37 | if (b == null) { 38 | throw new IOException("Could not read " + ICON_PATH); 39 | } 40 | ps.status(HttpServer.HTTP_OK); 41 | ps.println("Content-Type: image/png"); 42 | ps.println("Content-Length: " + b.length); 43 | ps.println(); 44 | ps.write(b); 45 | 46 | } catch (Exception e) { 47 | ps.status(HttpServer.HTTP_SERVER_ERROR); 48 | ps.println("Content-Type: text/html"); 49 | ps.println(); 50 | ps.println("Server Error"); 51 | ps.println("
Error while reading " + ICON_PATH);
52 |           ps.write(e);
53 |         }
54 |       }
55 |     } catch (IOException ignored) {  }
56 |   }
57 | }


--------------------------------------------------------------------------------
/src/processing/mode/p5js/server/GenericHandler.java:
--------------------------------------------------------------------------------
  1 | package processing.mode.p5js.server;
  2 | 
  3 | import java.io.*;
  4 | 
  5 | import processing.core.PApplet;
  6 | 
  7 | 
  8 | public class GenericHandler extends Handler {
  9 | 
 10 |   public GenericHandler(HttpServer server) {
 11 |     super(server);
 12 |   }
 13 | 
 14 | 
 15 |   @Override
 16 |   public void handle(String path, CarlOrff ps) {
 17 |     File target = new File(server.getRoot(), path);  // leading slash already removed
 18 |     handleFile(path, ps, target);
 19 |   }
 20 | 
 21 | 
 22 |   void handleFile(String path, CarlOrff ps, File target) {
 23 |     if (target.exists()) {
 24 |       if (target.isDirectory()) {
 25 |         File indexFile = new File(target, "index.html");
 26 |         if (indexFile.exists()) {
 27 |           if (path.length() > 0 && !path.endsWith("/")) {
 28 |             // if it's a folder with a slash on the end, and there's an index
 29 |             // file in this folder, redirect to include the slash at the end,
 30 |             // so that relative URLs in the index.html work properly.
 31 |             ps.status(HttpServer.HTTP_FOUND);
 32 |             ps.println("Location: " + path + "/");
 33 |             ps.println();
 34 | 
 35 |           } else {
 36 |             // if there's already a slash, send back the index.html file
 37 |             path += "index.html";
 38 |             handle(path, ps);
 39 |           }
 40 |         } else {
 41 |           // if no index.html exists in this folder, then deny with a 401.
 42 |           ps.status(HttpServer.HTTP_UNAUTHORIZED);
 43 |           ps.println("Content-type: text/html");
 44 |           ps.println();
 45 |           ps.println("");
 46 |           ps.println("

Directory Listing Denied

"); 47 | ps.println("
" + path + "
"); 48 | ps.println("
" + target.getAbsolutePath() + "
"); 49 | ps.println(""); 50 | } 51 | } else { 52 | try { 53 | String canonicalPath = target.getCanonicalPath(); 54 | if (!canonicalPath.endsWith(path)) { 55 | if (canonicalPath.length() > path.length()) { // just to make sure 56 | String canonicalPiece = 57 | canonicalPath.substring(canonicalPath.length() - path.length()); 58 | if (path.equalsIgnoreCase(canonicalPiece)) { 59 | System.err.println("Mismatched text found: " + 60 | path + " was requested, but the file is " + canonicalPiece); 61 | System.err.println("Fix the capitalization to avoid " + 62 | "problems when running this code on a server."); 63 | } 64 | } 65 | } 66 | byte[] b = loadBytes(path, target); 67 | 68 | ps.status(HttpServer.HTTP_OK); 69 | String contentType = "content/unknown"; 70 | String localPath = target.getAbsolutePath(); 71 | int dot = localPath.lastIndexOf('.'); 72 | if (dot != -1) { 73 | String extension = localPath.substring(dot); 74 | String found = HttpServer.getMimeType(extension); 75 | if (found != null) { 76 | contentType = found; 77 | } else { 78 | System.err.println("no content type found for " + extension); 79 | } 80 | } 81 | 82 | ps.println("Content-Type: " + contentType); 83 | ps.println("Content-Length: " + b.length); 84 | ps.println(); 85 | ps.write(b); 86 | 87 | } catch (Exception e) { 88 | ps.status(HttpServer.HTTP_SERVER_ERROR); 89 | ps.println("Content-type: text/html"); 90 | ps.println(); 91 | ps.println(""); 92 | ps.println("

" + HttpServer.HTTP_SERVER_ERROR + " Exception

"); 93 | ps.println("
");
 94 |           e.printStackTrace(ps.ps);
 95 |           ps.println("
"); 96 | ps.println(""); 97 | } 98 | } 99 | } else { 100 | ps.status(HttpServer.HTTP_NOT_FOUND); 101 | ps.println("Content-type: text/html"); 102 | ps.println(); 103 | ps.println(""); 104 | ps.println("

" + HttpServer.HTTP_NOT_FOUND + " Not Found

"); 105 | ps.println("
" + path + "
"); 106 | ps.println("
" + target.getAbsolutePath() + "
"); 107 | ps.println(""); 108 | } 109 | } 110 | 111 | 112 | byte[] loadBytes(String path, File target) throws IOException { 113 | byte[] b = PApplet.loadBytes(target); 114 | if (b == null) { 115 | throw new IOException("Could not read " + target); 116 | } 117 | return b; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/processing/mode/p5js/server/Handler.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js.server; 2 | 3 | 4 | abstract public class Handler { 5 | HttpServer server; 6 | 7 | 8 | public Handler(HttpServer server) { 9 | this.server = server; 10 | } 11 | 12 | 13 | abstract public void handle(String params, CarlOrff ps); 14 | } -------------------------------------------------------------------------------- /src/processing/mode/p5js/server/HttpServer.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js.server; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.InetAddress; 6 | import java.net.Inet4Address; 7 | import java.net.NetworkInterface; 8 | import java.net.ServerSocket; 9 | import java.net.Socket; 10 | import java.util.ArrayList; 11 | import java.util.Enumeration; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | 17 | import processing.data.StringList; 18 | import processing.mode.p5js.p5jsEditor; 19 | 20 | 21 | /** 22 | * This is a very simple, multi-threaded HTTP server, originally based on 23 | * this article on java.sun.com. 24 | */ 25 | public class HttpServer { 26 | // Where worker threads stand idle 27 | final List threads = new ArrayList<>(); 28 | 29 | // timeout on client connections 30 | static final int TIMEOUT = 10000; 31 | 32 | // max # worker threads 33 | static final int WORKERS = 5; 34 | 35 | Map handlerMap = new ConcurrentHashMap<>(); 36 | Handler genericHandler; 37 | 38 | ServerSocket socket; 39 | Thread thread; 40 | int port; 41 | boolean running; 42 | 43 | p5jsEditor editor; 44 | //File root; 45 | 46 | 47 | /* 48 | public HttpServer(p5jsEditor editor) { 49 | this(editor, (int) (8000 + Math.random() * 1000)); 50 | } 51 | */ 52 | 53 | 54 | public HttpServer(p5jsEditor editor, int port) throws IOException { 55 | this.editor = editor; 56 | this.port = port; 57 | 58 | /* 59 | handlerMap.put("details", new DetailsHandler(this)); 60 | handlerMap.put("header", new HeaderHandler(this)); 61 | handlerMap.put("page", new PageHandler(this)); 62 | handlerMap.put("sample", new SampleHandler(this)); 63 | handlerMap.put("skip", new SkipHandler(this)); 64 | handlerMap.put("status", new StatusHandler(this)); 65 | handlerMap.put("types", new TypesHandler(this)); 66 | */ 67 | // handlerMap.put("libraries", new LibrariesHandler(this)); 68 | //handlerMap.put("libraries", new GenericHandler(this, getLibrariesFolder())); 69 | handlerMap.put("favicon.ico", new FavIconHandler(this)); 70 | //genericHandler = new GenericHandler(this); 71 | genericHandler = new EditorHandler(this); 72 | 73 | for (int i = 0; i < WORKERS; i++) { 74 | HttpWorker w = new HttpWorker(this); 75 | new Thread(w, "HttpServer Worker " + (i+1)).start(); 76 | threads.add(w); 77 | } 78 | 79 | // try { 80 | socket = new ServerSocket(port); 81 | // } catch (BindException be) { 82 | // // socket already in use; try another 83 | // } 84 | 85 | /* 86 | String url = "http://localhost:" + port + "/index.html"; 87 | System.out.println(url); 88 | PApplet.launch(url); 89 | */ 90 | } 91 | 92 | 93 | /* 94 | public void start() { 95 | if (thread == null) { 96 | thread = new Thread(new Runnable() { 97 | @Override 98 | public void run() { 99 | running = true; 100 | ServerSocket socket = null; 101 | try { 102 | // try { 103 | socket = new ServerSocket(port); 104 | // } catch (BindException be) { 105 | // // socket already in use; try another 106 | // } 107 | while (Thread.currentThread() == thread) { 108 | @SuppressWarnings("resource") 109 | Socket s = socket.accept(); 110 | // Worker w = null; 111 | synchronized (threads) { 112 | if (threads.isEmpty()) { 113 | HttpWorker worker = new HttpWorker(HttpServer.this); 114 | worker.setSocket(s); 115 | (new Thread(worker, "additional worker")).start(); 116 | } else { 117 | // w = threads.get(0); 118 | // threads.remove(0); 119 | HttpWorker w = threads.remove(0); 120 | w.setSocket(s); 121 | } 122 | } 123 | } 124 | 125 | } catch (IOException e) { 126 | e.printStackTrace(); 127 | 128 | } finally { 129 | if (socket != null) { 130 | try { 131 | socket.close(); 132 | } catch (IOException e) { 133 | e.printStackTrace(); 134 | } 135 | } 136 | } 137 | running = false; 138 | } 139 | }); 140 | } 141 | thread.start(); 142 | } 143 | */ 144 | 145 | 146 | public void start() { 147 | if (thread == null) { 148 | thread = new Thread(() -> { 149 | running = true; 150 | try { 151 | while (Thread.currentThread() == thread) { 152 | Socket s = socket.accept(); 153 | synchronized (threads) { 154 | if (threads.isEmpty()) { 155 | HttpWorker worker = new HttpWorker(HttpServer.this); 156 | worker.setSocket(s); 157 | (new Thread(worker, "additional worker")).start(); 158 | } else { 159 | HttpWorker w = threads.remove(0); 160 | w.setSocket(s); 161 | } 162 | } 163 | } 164 | 165 | } catch (IOException e) { 166 | e.printStackTrace(); 167 | 168 | } finally { 169 | if (socket != null) { 170 | try { 171 | socket.close(); 172 | } catch (IOException e) { 173 | e.printStackTrace(); 174 | } 175 | } 176 | } 177 | running = false; 178 | }); 179 | } 180 | thread.start(); 181 | } 182 | 183 | 184 | public void stop() { 185 | // new Exception("server.stop() called").printStackTrace(System.out); 186 | thread = null; 187 | } 188 | 189 | 190 | public boolean addWorker(HttpWorker worker) { 191 | synchronized (threads) { 192 | if (threads.size() >= WORKERS) { 193 | // too many threads, exit this one 194 | return false; 195 | } 196 | threads.add(worker); 197 | return true; 198 | } 199 | } 200 | 201 | 202 | /* 203 | public boolean isRunning () { 204 | return running; 205 | } 206 | */ 207 | 208 | 209 | public boolean isDead() { 210 | return !running || thread == null; 211 | } 212 | 213 | 214 | public String getAddress() { 215 | return "http://127.0.0.1:" + port + "/"; 216 | } 217 | 218 | 219 | /** 220 | * Return the first local IPv4 address that is not 127.0.0.1, 221 | * or null if there's nothing that matches those criteria. 222 | */ 223 | public StringList getLocalAddresses() { 224 | StringList outgoing = new StringList(); 225 | try { 226 | Enumeration e = NetworkInterface.getNetworkInterfaces(); 227 | while (e.hasMoreElements()) { 228 | NetworkInterface n = e.nextElement(); 229 | Enumeration ee = n.getInetAddresses(); 230 | while (ee.hasMoreElements()) { 231 | InetAddress i = ee.nextElement(); 232 | if (i instanceof Inet4Address) { 233 | String addr = i.getHostAddress(); 234 | if (!addr.equals("127.0.0.1")) { // not useful 235 | outgoing.append("http://" + addr + ":" + port + "/"); 236 | } 237 | } 238 | } 239 | } 240 | } catch (IOException e) { 241 | e.printStackTrace(); 242 | } 243 | return outgoing; 244 | } 245 | 246 | 247 | File getRoot() { 248 | // Makes it persist properly even as the sketch is saved to new locations. 249 | // A sketch from a different editor will be running its own server. 250 | return editor.getSketch().getFolder(); 251 | //return root; 252 | } 253 | 254 | 255 | /* 256 | File getLibrariesFolder() { 257 | return new File(editor.getTemplateFolder(), "libraries"); 258 | } 259 | */ 260 | 261 | 262 | /** 263 | * For use when the first entry of a path is a REST API command or something 264 | * like that, i.e. /list/blah should go to a 'list' handler. 265 | */ 266 | Handler getHandler(String prefix) { 267 | return handlerMap.get(prefix); 268 | } 269 | 270 | 271 | Handler getGenericHandler() { 272 | return genericHandler; 273 | } 274 | 275 | 276 | /* 277 | static void log(String s) { 278 | if (false) { 279 | System.out.println(s); 280 | } 281 | } 282 | */ 283 | 284 | 285 | // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 | 287 | 288 | /** mapping of file extensions to content-types */ 289 | static Map mimeTypes = new HashMap<>(); 290 | 291 | 292 | static String getMimeType(String extension) { 293 | return mimeTypes.get(extension); 294 | } 295 | 296 | 297 | static { 298 | mimeTypes.put("", "content/unknown"); 299 | 300 | mimeTypes.put(".uu", "application/octet-stream"); 301 | mimeTypes.put(".exe", "application/octet-stream"); 302 | mimeTypes.put(".ps", "application/postscript"); 303 | mimeTypes.put(".zip", "application/zip"); 304 | mimeTypes.put(".sh", "application/x-shar"); 305 | mimeTypes.put(".tar", "application/x-tar"); 306 | mimeTypes.put(".snd", "audio/basic"); 307 | mimeTypes.put(".au", "audio/basic"); 308 | mimeTypes.put(".wav", "audio/x-wav"); 309 | 310 | mimeTypes.put(".gif", "image/gif"); 311 | mimeTypes.put(".jpg", "image/jpeg"); 312 | mimeTypes.put(".jpeg", "image/jpeg"); 313 | mimeTypes.put(".png", "image/png"); 314 | mimeTypes.put(".svg", "image/svg+xml"); 315 | 316 | mimeTypes.put(".htm", "text/html"); 317 | mimeTypes.put(".html", "text/html"); 318 | mimeTypes.put(".css", "text/css"); 319 | mimeTypes.put(".js", "text/javascript"); 320 | // 'application/xml' is an alternative for .xml's mime type 321 | mimeTypes.put(".xml", "text/xml"); 322 | 323 | mimeTypes.put(".json", "application/json"); 324 | mimeTypes.put(".jsonp", "application/javascript"); 325 | 326 | mimeTypes.put(".csv", "text/csv"); 327 | mimeTypes.put(".tsv", "text/tab-separated-values"); 328 | 329 | // 'application/x-font-opentype' is another .otf alternative 330 | mimeTypes.put(".otf", "font/opentype"); 331 | // 'application/x-font-truetype' is an alternative for .ttf 332 | mimeTypes.put(".ttf", "application/x-font-ttf"); 333 | mimeTypes.put(".woff", "application/font-woff"); 334 | mimeTypes.put(".woff2", "application/font-woff2"); 335 | // 'font/opentype' is an alternate for .eot's mime type 336 | mimeTypes.put(".eot", "application/vnd.ms-fontobject"); 337 | 338 | mimeTypes.put(".cur", "image/vnd.microsoft.icon"); 339 | 340 | mimeTypes.put(".txt", "text/plain"); 341 | mimeTypes.put(".java", "text/plain"); // x-java-source -> plain is better 342 | } 343 | 344 | 345 | // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 | 347 | 348 | /** 2XX: generally "OK" */ 349 | static public final int HTTP_OK = 200; 350 | // static private final int HTTP_CREATED = 201; 351 | // static private final int HTTP_ACCEPTED = 202; 352 | // static private final int HTTP_NOT_AUTHORITATIVE = 203; 353 | // static private final int HTTP_NO_CONTENT = 204; 354 | // static private final int HTTP_RESET = 205; 355 | // static private final int HTTP_PARTIAL = 206; 356 | 357 | /** 3XX: relocation/redirect */ 358 | // static private final int HTTP_MULT_CHOICE = 300; 359 | // static private final int HTTP_MOVED_PERM = 301; 360 | static public final int HTTP_FOUND = 302; 361 | // static private final int HTTP_SEE_OTHER = 303; 362 | // static private final int HTTP_NOT_MODIFIED = 304; 363 | // static private final int HTTP_USE_PROXY = 305; 364 | 365 | /** 4XX: client error */ 366 | // static private final int HTTP_BAD_REQUEST = 400; 367 | static public final int HTTP_UNAUTHORIZED = 401; 368 | // static private final int HTTP_PAYMENT_REQUIRED = 402; 369 | // static private final int HTTP_FORBIDDEN = 403; 370 | static public final int HTTP_NOT_FOUND = 404; 371 | static public final int HTTP_BAD_METHOD = 405; 372 | // static private final int HTTP_NOT_ACCEPTABLE = 406; 373 | // static private final int HTTP_PROXY_AUTH = 407; 374 | // static private final int HTTP_CLIENT_TIMEOUT = 408; 375 | // static private final int HTTP_CONFLICT = 409; 376 | // static private final int HTTP_GONE = 410; 377 | // static private final int HTTP_LENGTH_REQUIRED = 411; 378 | // static private final int HTTP_PRECON_FAILED = 412; 379 | // static private final int HTTP_ENTITY_TOO_LARGE = 413; 380 | // static private final int HTTP_REQ_TOO_LONG = 414; 381 | // static private final int HTTP_UNSUPPORTED_TYPE = 415; 382 | 383 | /** 5XX: server error */ 384 | static public final int HTTP_SERVER_ERROR = 500; 385 | // static private final int HTTP_INTERNAL_ERROR = 501; 386 | // static private final int HTTP_BAD_GATEWAY = 502; 387 | // static private final int HTTP_UNAVAILABLE = 503; 388 | // static private final int HTTP_GATEWAY_TIMEOUT = 504; 389 | // static private final int HTTP_VERSION = 505; 390 | 391 | 392 | // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393 | 394 | 395 | static Map statusMessages = new HashMap<>(); 396 | 397 | 398 | static String getStatusMessage(int code) { 399 | return statusMessages.get(code); 400 | } 401 | 402 | 403 | static { 404 | statusMessages.put(HTTP_OK, "OK"); 405 | statusMessages.put(HTTP_FOUND, "Found"); 406 | statusMessages.put(HTTP_UNAUTHORIZED, "Found"); 407 | statusMessages.put(HTTP_NOT_FOUND, "Not Found"); 408 | statusMessages.put(HTTP_BAD_METHOD, "Bad Method"); 409 | statusMessages.put(HTTP_SERVER_ERROR, "Server Error"); 410 | } 411 | } -------------------------------------------------------------------------------- /src/processing/mode/p5js/server/HttpWorker.java: -------------------------------------------------------------------------------- 1 | package processing.mode.p5js.server; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.OutputStream; 7 | import java.net.Socket; 8 | import java.net.SocketTimeoutException; 9 | 10 | import processing.core.PApplet; 11 | 12 | 13 | public class HttpWorker implements Runnable { 14 | private HttpServer server; 15 | private Socket socket; 16 | 17 | 18 | HttpWorker(HttpServer server) { 19 | this.server = server; 20 | socket = null; 21 | } 22 | 23 | 24 | /** 25 | * Set the Socket for this worker. The Socket will be closed by this class 26 | * once finished. 27 | */ 28 | synchronized void setSocket(Socket s) { 29 | this.socket = s; 30 | notify(); 31 | } 32 | 33 | 34 | @Override 35 | public synchronized void run() { 36 | while (true) { 37 | if (socket == null) { 38 | // nothing to do 39 | try { 40 | wait(); 41 | } catch (InterruptedException e) { 42 | // should not happen 43 | continue; 44 | } 45 | } 46 | try { 47 | handleClient(); 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | } 51 | // go back in wait queue if there's fewer than numHandler connections. 52 | socket = null; 53 | if (!server.addWorker(this)) { 54 | return; 55 | } 56 | } 57 | } 58 | 59 | 60 | void handleClient() throws IOException { 61 | InputStream input = socket.getInputStream(); 62 | BufferedReader reader = PApplet.createReader(input); 63 | OutputStream output = socket.getOutputStream(); 64 | CarlOrff ps = new CarlOrff(output); 65 | 66 | // we will only block in read for this many milliseconds 67 | // before we fail with java.io.InterruptedIOException, 68 | // at which point we will abandon the connection. 69 | socket.setSoTimeout(HttpServer.TIMEOUT); 70 | socket.setTcpNoDelay(true); 71 | 72 | try { 73 | String line = reader.readLine(); 74 | if (line != null) { 75 | String[] pieces = line.split(" "); 76 | if ("GET".equals(pieces[0])) { 77 | String path = pieces[1]; 78 | if (path.length() < 1 || path.charAt(0) != '/') { 79 | ps.status(HttpServer.HTTP_BAD_METHOD); 80 | ps.println("Content-type: text/plain"); 81 | ps.println(); 82 | ps.println("500 So Misunderstood"); 83 | ps.println(); 84 | 85 | } else { 86 | path = path.substring(1); 87 | int slash = path.indexOf('/'); 88 | String params = null; 89 | if (slash != -1) { 90 | params = path.substring(slash + 1); 91 | } else { 92 | slash = path.length(); 93 | } 94 | String command = path.substring(0, slash); 95 | Handler handler = server.getHandler(command); 96 | if (handler != null) { 97 | handler.handle(params, ps); 98 | } else { 99 | server.getGenericHandler().handle(path, ps); 100 | } 101 | } 102 | 103 | } else if ("HEAD".equals(pieces[0])) { 104 | System.err.println("HEAD request ignored"); 105 | 106 | } else { 107 | ps.status(HttpServer.HTTP_BAD_METHOD); 108 | ps.println("Content-type: text/plain"); 109 | ps.println(); 110 | ps.println("405 Unsupported Method: " + pieces[0]); 111 | ps.println(); 112 | } 113 | ps.flush(); 114 | } 115 | } catch (SocketTimeoutException ste) { 116 | // Ignoring these for now... Seems to be extra sockets opened 117 | // by the browser, but not quite clear yet. 118 | // https://github.com/fathominfo/processing-p5js-mode/issues/5 119 | System.out.println("Socket timed out."); 120 | 121 | } catch (IOException e) { 122 | e.printStackTrace(); 123 | } 124 | 125 | // clean up, but ignore any errors along the way 126 | try { 127 | reader.close(); 128 | } catch (Exception ignored) { } 129 | try { 130 | input.close(); 131 | } catch (Exception ignored) { } 132 | try { 133 | output.close(); 134 | } catch (Exception ignored) { } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /template/sketch.js: -------------------------------------------------------------------------------- 1 | function setup() { 2 | 3 | } 4 | 5 | 6 | function draw() { 7 | 8 | } -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | 0017 (1.6.1) 2 | 3 | 4 | _ warn if files are especially large 5 | 6 | _ lots of keywords (foreach, async, await) not in keywords.txt 7 | 8 | _ creates own sketch.properties, check on possible conflicts w/ Base.java 9 | 10 | _ add menu option to hold the port fixed (necessary?) 11 | 12 | _ make it possible for the main class to be something besides the default name 13 | _ want to use sketch.js (this is a fine restriction) 14 | _ how difficult to have modes determine whether a sketch is a sketch? 15 | _ so that the main sketch class doesn't have to follow a naming convention 16 | _ Mode.rebuildSketchbookTree() handles creation of the sketchbook menu 17 | _ which calls Base.addSketches() to do the actual adding 18 | _ would need some means of specifying what the 'main' file is 19 | _ at least with p5js we could provisionally just use sketch.js? 20 | 21 | _ or need better solution when opening sketch.js files 22 | _ just rename the sketch.js file itself 23 | _ https://github.com/fathominfo/processing-p5js-mode/issues/14 24 | _ This needs to be handled by an “Import” tool that pulls 25 | _ from a local folder or from the online editor. 26 | _ in particular, because the index.html might have just about anything 27 | 28 | _ ability to run sketches that require SSL 29 | o using SSL from inside p5jsMode? 30 | o https://stackoverflow.com/questions/2308479/simple-java-https-server 31 | X just not a good option: ip addresses don't work well 32 | _ better option would be sftp or github pages integration 33 | 34 | _ if starting a new sketch, must reload the browser first? 35 | _ what's happening here? concerned about code mods not updating 36 | _ set no cache on returned objects? 37 | 38 | _ when sketch is moved while the editor is open 39 | _ it can't re-create any of the supporting files 40 | _ leaving you with a broken sketch, which is messy b/c it looks ok 41 | 42 | _ how to enable the cdn version of p5.min.js 43 | _ would be nice to use by default, but want to avoid net connection as requirement 44 | _ (i.e. if you're using a network connection, why not use an online editor) 45 | _ or is there a way to handle it inside the p5js server? 46 | _ just have a preference/checkbox in the menu 47 | _ enabling the cdn will remove p5.min.js from the sketch 48 | _ new sketches inherit the last setting 49 | 50 | _ other import library changes 51 | _ it's actually 'add library' not 'import' in this case 52 | _ probably need a way to *remove* the libraries too 53 | _ get contributed libraries working? 54 | 55 | _ refactor library imports in p5 56 | _ properly refactor rebuildLibraryList() and the isCompatible() code 57 | _ which is currently only found in ExampleContribution, but should be 58 | _ implemented in a more general way for the other contribs 59 | _ clean up the static stuff since it's not just Examples 60 | _ Library.discover(File) is static and expects .jar files 61 | _ even once that's fixed, need to make sure js libs don't show up w/ Java 62 | _ meaning that there needs to be other ironing in there 63 | 64 | _ better means of understanding actual use of p5jsMode? 65 | _ shows as installed on 30% of machines using the manager 66 | 67 | _ mode option for showing html and css or not 68 | _ might be nice to hide these for beginners who will never modify them 69 | _ is it time to add Mode preferences? 70 | _ other supported types from old/offline p5js editor 71 | _ txt, html, css, js, json, scss, xml, csv, less 72 | _ include: html, css, js, scss, less 73 | _ exclude: xml, txt, csv, json (like to be in data folder) 74 | _ add basic syntax highlighting for css 75 | 76 | 77 | . . . 78 | 79 | roll the version/revision numbers in mode.properties 80 | 81 | # update the release in mode.properties (it'll match the rev number here) 82 | git tag -a release-1.0 -m '1.0 final' 83 | git tag -a v1.0.1 -m 'version 1.0.1' 84 | git tag -a v1.0.2 -m 'version 1.0.2' 85 | git tag -a v1.0.3 -m 'version 1.0.3' 86 | git tag -a v1.0.4 -m 'version 1.0.4' 87 | git tag -a v1.1 -m 'version 1.1' 88 | git tag -a v1.1.1 -m 'version 1.1.1' 89 | git tag -a v1.2 -m 'version 1.2' 90 | git tag -a v1.2.1 -m 'version 1.2.1' 91 | git tag -a v1.2.2 -m 'version 1.2.2' 92 | git tag -a v1.3 -m 'version 1.3' 93 | git tag -a v1.3.1 -m 'version 1.3.1' 94 | git tag -a v1.4 -m 'version 1.4' 95 | git tag -a v1.4.1 -m 'version 1.4.1' 96 | git tag -a v1.4.2 -m 'version 1.4.2' 97 | git tag -a v1.5 -m 'version 1.5' 98 | git tag -a v1.6 -m 'version 1.6' 99 | git push origin --tags 100 | 101 | # delete the previous 'latest' 102 | git tag -d latest 103 | git push origin :refs/tags/latest 104 | 105 | # create new 'latest' tag with the current state of the repo 106 | git tag -f -a latest -m 'version 1.6' 107 | # actually update things 108 | git push -f --tags 109 | 110 | ant dist 111 | then upload dist/p5jsMode.zip and dist/p5jsMode.txt to the 'latest' tag 112 | the *old version* will already be there; need to manually delete and then upload the new one 113 | (can also upload them to the most recent tag for anyone installing manually) 114 | and name/put some info about the release into the tag 115 | https://github.com/fathominfo/processing-p5js-mode/releases 116 | --------------------------------------------------------------------------------