From bd9e475f380b653a42163ec9062044e15157a0b5 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 16 May 2025 20:49:06 +0200 Subject: [PATCH 1/4] Fixes for LSP running & Improving the install locations --- app/build.gradle.kts | 3 +- app/src/processing/app/Base.java | 12 ------ app/src/processing/app/Processing.kt | 64 ++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c40365758..ef951c879 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -419,7 +419,6 @@ tasks.register("renameWindres") { } tasks.register("includeProcessingResources"){ dependsOn( - "includeJdk", "includeCore", "includeJavaMode", "includeSharedAssets", @@ -428,6 +427,7 @@ tasks.register("includeProcessingResources"){ "includeJavaModeResources", "renameWindres" ) + mustRunAfter("includeJdk") finalizedBy("signResources") } @@ -534,6 +534,7 @@ afterEvaluate { dependsOn("includeProcessingResources") } tasks.named("createDistributable").configure { + dependsOn("includeJdk") finalizedBy("setExecutablePermissions") } } diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index b5aa599b9..4d57135c9 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -166,18 +166,6 @@ static public void main(final String[] args) { static private void createAndShowGUI(String[] args) { // these times are fairly negligible relative to Base. // long t1 = System.currentTimeMillis(); - var preferences = java.util.prefs.Preferences.userRoot().node("org/processing/app"); - var installLocations = new ArrayList<>(List.of(preferences.get("installLocations", "").split(","))); - var installLocation = System.getProperty("user.dir") + "^" + Base.getVersionName(); - - // Check if the installLocation is already in the list - if (!installLocations.contains(installLocation)) { - // Add the installLocation to the list - installLocations.add(installLocation); - - // Save the updated list back to preferences - preferences.put("installLocations", String.join(",", installLocations)); - } // TODO: Cleanup old locations if no longer installed // TODO: Cleanup old locations if current version is installed in the same location diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt index 11555edf5..751dd361f 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -11,6 +11,9 @@ import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.help import com.github.ajalt.clikt.parameters.options.option import processing.app.ui.Start +import java.io.File +import java.util.prefs.Preferences +import kotlin.concurrent.thread class Processing: SuspendingCliktCommand("processing"){ val version by option("-v","--version") @@ -29,6 +32,11 @@ class Processing: SuspendingCliktCommand("processing"){ return } + thread { + // Update the install locations in preferences + updateInstallLocations() + } + val subcommand = currentContext.invokedSubcommand if (subcommand == null) { Start.main(sketches.toTypedArray()) @@ -49,10 +57,13 @@ class LSP: SuspendingCliktCommand("lsp"){ override fun help(context: Context) = "Start the Processing Language Server" override suspend fun run(){ try { + // run in headless mode + System.setProperty("java.awt.headless", "true") + // Indirect invocation since app does not depend on java mode Class.forName("processing.mode.java.lsp.PdeLanguageServer") .getMethod("main", Array::class.java) - .invoke(null, *arrayOf(emptyList())) + .invoke(null, arrayOf()) } catch (e: Exception) { throw InternalError("Failed to invoke main method", e) } @@ -76,9 +87,8 @@ class LegacyCLI(val args: Array): SuspendingCliktCommand( "cli"){ override suspend fun run(){ val cliArgs = args.filter { it != "cli" } try { - if(build){ - System.setProperty("java.awt.headless", "true") - } + System.setProperty("java.awt.headless", "true") + // Indirect invocation since app does not depend on java mode Class.forName("processing.mode.java.Commander") .getMethod("main", Array::class.java) @@ -87,4 +97,50 @@ class LegacyCLI(val args: Array): SuspendingCliktCommand( "cli"){ throw InternalError("Failed to invoke main method", e) } } +} + +fun updateInstallLocations(){ + val preferences = Preferences.userRoot().node("org/processing/app") + val installLocations = preferences.get("installLocations", "") + .split(",") + .dropLastWhile { it.isEmpty() } + .filter { install -> + try{ + val (path, version) = install.split("^") + val file = File(path) + if(!file.exists() || file.isDirectory){ + return@filter false + } + // call the path to check if it is a valid install location + val process = ProcessBuilder(path, "--version") + .redirectErrorStream(true) + .start() + val exitCode = process.waitFor() + if(exitCode != 0){ + return@filter false + } + val output = process.inputStream.bufferedReader().readText() + return@filter output.contains(version) + } catch (e: Exception){ + false + } + } + .toMutableList() + val command = ProcessHandle.current().info().command() + if(command.isEmpty) { + return + } + val installLocation = "${command.get()}^${Base.getVersionName()}" + + + // Check if the installLocation is already in the list + if (installLocations.contains(installLocation)) { + return + } + + // Add the installLocation to the list + installLocations.add(installLocation) + + // Save the updated list back to preferences + preferences.put("installLocations", java.lang.String.join(",", installLocations)) } \ No newline at end of file From 5b27fc650a57ed195593ee6e3c70f6bf1a6ccf53 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 20 May 2025 20:43:50 +0200 Subject: [PATCH 2/4] Create Examples JSON --- app/build.gradle.kts | 1 + app/src/processing/app/Processing.kt | 4 +- .../processing/app/contrib/Contributions.kt | 95 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 app/src/processing/app/contrib/Contributions.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ef951c879..79cb4ad15 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -124,6 +124,7 @@ dependencies { testImplementation(libs.junitJupiterParams) implementation(libs.clikt) + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") } tasks.test { diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt index 751dd361f..798ef4b34 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -10,6 +10,7 @@ import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.help import com.github.ajalt.clikt.parameters.options.option +import processing.app.contrib.Contributions import processing.app.ui.Start import java.io.File import java.util.prefs.Preferences @@ -48,7 +49,8 @@ suspend fun main(args: Array){ Processing() .subcommands( LSP(), - LegacyCLI(args) + LegacyCLI(args), + Contributions() ) .main(args) } diff --git a/app/src/processing/app/contrib/Contributions.kt b/app/src/processing/app/contrib/Contributions.kt new file mode 100644 index 000000000..0c1948d84 --- /dev/null +++ b/app/src/processing/app/contrib/Contributions.kt @@ -0,0 +1,95 @@ +package processing.app.contrib + +import com.github.ajalt.clikt.command.SuspendingCliktCommand +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.core.subcommands +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import processing.app.Base +import java.io.File +import kotlinx.serialization.json.* + + +class Contributions: SuspendingCliktCommand(){ + override fun help(context: Context) = "Manage Processing contributions" + override suspend fun run() { + System.setProperty("java.awt.headless", "true") + } + init { + subcommands(Examples()) + } + + class Examples: SuspendingCliktCommand("examples") { + override fun help(context: Context) = "Manage Processing examples" + override suspend fun run() { + } + init { + subcommands(ExamplesList()) + } + } + + class ExamplesList: SuspendingCliktCommand("list") { + @Serializable + data class Sketch( + val type: String = "sketch", + val name: String, + val path: String, + val mode: String = "java", + ) + + @Serializable + data class Folder( + val type: String = "folder", + val name: String, + val path: String, + val mode: String = "java", + val children: List = emptyList(), + val sketches: List = emptyList() + ) + + val serializer = Json{ + prettyPrint = true + } + + override fun help(context: Context) = "List all examples" + override suspend fun run() { + + val examplesFolder = Base.getSketchbookExamplesFolder() + + // TODO: Decouple modes listing from `Base` class, defaulting to Java mode for now + val resourcesDir = System.getProperty("compose.application.resources.dir") + val javaMode = "$resourcesDir/modes/java" + val javaModeExamples = "$javaMode/examples" + + val javaExamples = getExamples(File(javaModeExamples)) + + val json = serializer.encodeToString(listOf(javaExamples)) + println(json) + + // Build-in examples for each mode + // Get examples for core libraries + + // Examples downloaded in the sketchbook + // Library contributions + // Mode examples + } + + suspend fun getExamples(file: File): Folder { + val name = file.name + val (sketchesFolders, childrenFolders) = file.listFiles().partition { isExampleFolder(it) } + + val children = childrenFolders.map { getExamples(it) } + val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) } + return Folder( + name = name, + path = file.absolutePath, + children = children, + sketches = sketches + ) + } + fun isExampleFolder(file: File): Boolean { + return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") } + } + } +} + From b3fb5582065cfdab2530b35b4a8d80dea23a325e Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 22 May 2025 08:36:50 +0200 Subject: [PATCH 3/4] Moved into api folder + listing Sketchbook --- app/src/processing/app/Processing.kt | 2 +- .../app/{contrib => api}/Contributions.kt | 28 ++++--------------- app/src/processing/app/api/Sketch.kt | 24 ++++++++++++++++ app/src/processing/app/api/Sketchbook.kt | 25 +++++++++++++++++ 4 files changed, 56 insertions(+), 23 deletions(-) rename app/src/processing/app/{contrib => api}/Contributions.kt (71%) create mode 100644 app/src/processing/app/api/Sketch.kt create mode 100644 app/src/processing/app/api/Sketchbook.kt diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt index 798ef4b34..b72b59af6 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -10,7 +10,7 @@ import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.help import com.github.ajalt.clikt.parameters.options.option -import processing.app.contrib.Contributions +import processing.app.api.Contributions import processing.app.ui.Start import java.io.File import java.util.prefs.Preferences diff --git a/app/src/processing/app/contrib/Contributions.kt b/app/src/processing/app/api/Contributions.kt similarity index 71% rename from app/src/processing/app/contrib/Contributions.kt rename to app/src/processing/app/api/Contributions.kt index 0c1948d84..070874c13 100644 --- a/app/src/processing/app/contrib/Contributions.kt +++ b/app/src/processing/app/api/Contributions.kt @@ -1,14 +1,14 @@ -package processing.app.contrib +package processing.app.api import com.github.ajalt.clikt.command.SuspendingCliktCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.subcommands import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import processing.app.Base +import processing.app.api.Sketch.Companion.getSketches import java.io.File -import kotlinx.serialization.json.* - class Contributions: SuspendingCliktCommand(){ override fun help(context: Context) = "Manage Processing contributions" @@ -47,7 +47,7 @@ class Contributions: SuspendingCliktCommand(){ val sketches: List = emptyList() ) - val serializer = Json{ + val serializer = Json { prettyPrint = true } @@ -61,7 +61,7 @@ class Contributions: SuspendingCliktCommand(){ val javaMode = "$resourcesDir/modes/java" val javaModeExamples = "$javaMode/examples" - val javaExamples = getExamples(File(javaModeExamples)) + val javaExamples = getSketches(File(javaModeExamples)) val json = serializer.encodeToString(listOf(javaExamples)) println(json) @@ -74,22 +74,6 @@ class Contributions: SuspendingCliktCommand(){ // Mode examples } - suspend fun getExamples(file: File): Folder { - val name = file.name - val (sketchesFolders, childrenFolders) = file.listFiles().partition { isExampleFolder(it) } - val children = childrenFolders.map { getExamples(it) } - val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) } - return Folder( - name = name, - path = file.absolutePath, - children = children, - sketches = sketches - ) - } - fun isExampleFolder(file: File): Boolean { - return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") } - } } -} - +} \ No newline at end of file diff --git a/app/src/processing/app/api/Sketch.kt b/app/src/processing/app/api/Sketch.kt new file mode 100644 index 000000000..6943a7f35 --- /dev/null +++ b/app/src/processing/app/api/Sketch.kt @@ -0,0 +1,24 @@ +package processing.app.api + +import java.io.File + +class Sketch { + companion object{ + fun getSketches(file: File): Contributions.ExamplesList.Folder { + val name = file.name + val (sketchesFolders, childrenFolders) = file.listFiles().partition { isSketchFolder(it) } + + val children = childrenFolders.map { getSketches(it) } + val sketches = sketchesFolders.map { Contributions.ExamplesList.Sketch(name = it.name, path = it.absolutePath) } + return Contributions.ExamplesList.Folder( + name = name, + path = file.absolutePath, + children = children, + sketches = sketches + ) + } + fun isSketchFolder(file: File): Boolean { + return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") } + } + } +} diff --git a/app/src/processing/app/api/Sketchbook.kt b/app/src/processing/app/api/Sketchbook.kt new file mode 100644 index 000000000..c7bdd7623 --- /dev/null +++ b/app/src/processing/app/api/Sketchbook.kt @@ -0,0 +1,25 @@ +package processing.app.api + +import com.github.ajalt.clikt.command.SuspendingCliktCommand +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.core.subcommands +import processing.app.Base +import processing.app.api.Sketch.Companion.getSketches + +class Sketchbook: SuspendingCliktCommand() { + override fun help(context: Context) = "Manage the sketchbook" + override suspend fun run() { + System.setProperty("java.awt.headless", "true") + } + init { + subcommands(SketchbookList()) + } + class SketchbookList: SuspendingCliktCommand("list") { + override fun help(context: Context) = "List all sketches" + override suspend fun run() { + val sketchbookFolder = Base.getSketchbookFolder() + + val sketches = getSketches(sketchbookFolder) + } + } +} \ No newline at end of file From a863cff3ca8650088fc09d9ffe3c5096b96ca163 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 22 May 2025 09:14:42 +0200 Subject: [PATCH 4/4] support for listing Sketchbook --- app/src/processing/app/Processing.kt | 4 ++- app/src/processing/app/api/Contributions.kt | 16 ---------- app/src/processing/app/api/Sketch.kt | 34 ++++++++++++++++++--- app/src/processing/app/api/Sketchbook.kt | 23 ++++++++++++-- 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt index b72b59af6..bb05a72f3 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -11,6 +11,7 @@ import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.help import com.github.ajalt.clikt.parameters.options.option import processing.app.api.Contributions +import processing.app.api.Sketchbook import processing.app.ui.Start import java.io.File import java.util.prefs.Preferences @@ -50,7 +51,8 @@ suspend fun main(args: Array){ .subcommands( LSP(), LegacyCLI(args), - Contributions() + Contributions(), + Sketchbook() ) .main(args) } diff --git a/app/src/processing/app/api/Contributions.kt b/app/src/processing/app/api/Contributions.kt index 070874c13..80bfa502f 100644 --- a/app/src/processing/app/api/Contributions.kt +++ b/app/src/processing/app/api/Contributions.kt @@ -29,23 +29,7 @@ class Contributions: SuspendingCliktCommand(){ } class ExamplesList: SuspendingCliktCommand("list") { - @Serializable - data class Sketch( - val type: String = "sketch", - val name: String, - val path: String, - val mode: String = "java", - ) - @Serializable - data class Folder( - val type: String = "folder", - val name: String, - val path: String, - val mode: String = "java", - val children: List = emptyList(), - val sketches: List = emptyList() - ) val serializer = Json { prettyPrint = true diff --git a/app/src/processing/app/api/Sketch.kt b/app/src/processing/app/api/Sketch.kt index 6943a7f35..a0e740014 100644 --- a/app/src/processing/app/api/Sketch.kt +++ b/app/src/processing/app/api/Sketch.kt @@ -1,16 +1,40 @@ package processing.app.api +import kotlinx.serialization.Serializable import java.io.File class Sketch { companion object{ - fun getSketches(file: File): Contributions.ExamplesList.Folder { + @Serializable + data class Sketch( + val type: String = "sketch", + val name: String, + val path: String, + val mode: String = "java", + ) + + @Serializable + data class Folder( + val type: String = "folder", + val name: String, + val path: String, + val mode: String = "java", + val children: List = emptyList(), + val sketches: List = emptyList() + ) + + fun getSketches(file: File, filter: (File) -> Boolean = { true }): Folder { val name = file.name - val (sketchesFolders, childrenFolders) = file.listFiles().partition { isSketchFolder(it) } + val (sketchesFolders, childrenFolders) = file.listFiles()?.partition { isSketchFolder(it) } ?: return Folder( + name = name, + path = file.absolutePath, + sketches = emptyList(), + children = emptyList() + ) - val children = childrenFolders.map { getSketches(it) } - val sketches = sketchesFolders.map { Contributions.ExamplesList.Sketch(name = it.name, path = it.absolutePath) } - return Contributions.ExamplesList.Folder( + val children = childrenFolders.filter(filter) .map { getSketches(it) } + val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) } + return Folder( name = name, path = file.absolutePath, children = children, diff --git a/app/src/processing/app/api/Sketchbook.kt b/app/src/processing/app/api/Sketchbook.kt index c7bdd7623..43d1cc6ce 100644 --- a/app/src/processing/app/api/Sketchbook.kt +++ b/app/src/processing/app/api/Sketchbook.kt @@ -3,10 +3,16 @@ package processing.app.api import com.github.ajalt.clikt.command.SuspendingCliktCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.subcommands -import processing.app.Base +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import processing.app.Platform +import processing.app.Preferences import processing.app.api.Sketch.Companion.getSketches +import java.io.File class Sketchbook: SuspendingCliktCommand() { + + override fun help(context: Context) = "Manage the sketchbook" override suspend fun run() { System.setProperty("java.awt.headless", "true") @@ -14,12 +20,23 @@ class Sketchbook: SuspendingCliktCommand() { init { subcommands(SketchbookList()) } + + class SketchbookList: SuspendingCliktCommand("list") { + val serializer = Json { + prettyPrint = true + } + override fun help(context: Context) = "List all sketches" override suspend fun run() { - val sketchbookFolder = Base.getSketchbookFolder() + Platform.init() + // TODO: Allow the user to change the sketchbook location + // TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode + val sketchbookFolder = Platform.getDefaultSketchbookFolder() - val sketches = getSketches(sketchbookFolder) + val sketches = getSketches(sketchbookFolder.resolve("sketchbook")) + val json = serializer.encodeToString(listOf(sketches)) + println(json) } } } \ No newline at end of file