Skip to content

Commit 475d405

Browse files
authored
Merge pull request #1115 from Stefterv/lsp-fixes
Adding support for the new VSCode extension
2 parents 3f36db5 + 8b04110 commit 475d405

File tree

7 files changed

+312
-17
lines changed

7 files changed

+312
-17
lines changed

app/build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ dependencies {
124124
testImplementation(libs.junitJupiterParams)
125125

126126
implementation(libs.clikt)
127+
implementation(libs.kotlinxSerializationJson)
127128
}
128129

129130
tasks.test {
@@ -424,7 +425,6 @@ tasks.register<Copy>("renameWindres") {
424425
}
425426
tasks.register("includeProcessingResources"){
426427
dependsOn(
427-
"includeJdk",
428428
"includeCore",
429429
"includeJavaMode",
430430
"includeSharedAssets",
@@ -433,6 +433,7 @@ tasks.register("includeProcessingResources"){
433433
"includeJavaModeResources",
434434
"renameWindres"
435435
)
436+
mustRunAfter("includeJdk")
436437
finalizedBy("signResources")
437438
}
438439

@@ -539,6 +540,7 @@ afterEvaluate {
539540
dependsOn("includeProcessingResources")
540541
}
541542
tasks.named("createDistributable").configure {
543+
dependsOn("includeJdk")
542544
finalizedBy("setExecutablePermissions")
543545
}
544546
}

app/src/processing/app/Base.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -166,18 +166,6 @@ static public void main(final String[] args) {
166166
static private void createAndShowGUI(String[] args) {
167167
// these times are fairly negligible relative to Base.<init>
168168
// long t1 = System.currentTimeMillis();
169-
var preferences = java.util.prefs.Preferences.userRoot().node("org/processing/app");
170-
var installLocations = new ArrayList<>(List.of(preferences.get("installLocations", "").split(",")));
171-
var installLocation = System.getProperty("user.dir") + "^" + Base.getVersionName();
172-
173-
// Check if the installLocation is already in the list
174-
if (!installLocations.contains(installLocation)) {
175-
// Add the installLocation to the list
176-
installLocations.add(installLocation);
177-
178-
// Save the updated list back to preferences
179-
preferences.put("installLocations", String.join(",", installLocations));
180-
}
181169
// TODO: Cleanup old locations if no longer installed
182170
// TODO: Cleanup old locations if current version is installed in the same location
183171

app/src/processing/app/Processing.kt

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import com.github.ajalt.clikt.parameters.arguments.multiple
1010
import com.github.ajalt.clikt.parameters.options.flag
1111
import com.github.ajalt.clikt.parameters.options.help
1212
import com.github.ajalt.clikt.parameters.options.option
13+
import processing.app.api.Contributions
14+
import processing.app.api.Sketchbook
1315
import processing.app.ui.Start
16+
import java.io.File
17+
import java.util.prefs.Preferences
18+
import kotlin.concurrent.thread
1419

1520
class Processing: SuspendingCliktCommand("processing"){
1621
val version by option("-v","--version")
@@ -29,6 +34,11 @@ class Processing: SuspendingCliktCommand("processing"){
2934
return
3035
}
3136

37+
thread {
38+
// Update the install locations in preferences
39+
updateInstallLocations()
40+
}
41+
3242
val subcommand = currentContext.invokedSubcommand
3343
if (subcommand == null) {
3444
Start.main(sketches.toTypedArray())
@@ -40,7 +50,9 @@ suspend fun main(args: Array<String>){
4050
Processing()
4151
.subcommands(
4252
LSP(),
43-
LegacyCLI(args)
53+
LegacyCLI(args),
54+
Contributions(),
55+
Sketchbook()
4456
)
4557
.main(args)
4658
}
@@ -49,6 +61,9 @@ class LSP: SuspendingCliktCommand("lsp"){
4961
override fun help(context: Context) = "Start the Processing Language Server"
5062
override suspend fun run(){
5163
try {
64+
// run in headless mode
65+
System.setProperty("java.awt.headless", "true")
66+
5267
// Indirect invocation since app does not depend on java mode
5368
Class.forName("processing.mode.java.lsp.PdeLanguageServer")
5469
.getMethod("main", Array<String>::class.java)
@@ -68,10 +83,9 @@ class LegacyCLI(val args: Array<String>): SuspendingCliktCommand("cli") {
6883

6984
override suspend fun run() {
7085
try {
71-
if (arguments.contains("--build")) {
72-
System.setProperty("java.awt.headless", "true")
73-
}
86+
System.setProperty("java.awt.headless", "true")
7487

88+
// Indirect invocation since app does not depend on java mode
7589
Class.forName("processing.mode.java.Commander")
7690
.getMethod("main", Array<String>::class.java)
7791
.invoke(null, arguments.toTypedArray())
@@ -80,3 +94,49 @@ class LegacyCLI(val args: Array<String>): SuspendingCliktCommand("cli") {
8094
}
8195
}
8296
}
97+
98+
fun updateInstallLocations(){
99+
val preferences = Preferences.userRoot().node("org/processing/app")
100+
val installLocations = preferences.get("installLocations", "")
101+
.split(",")
102+
.dropLastWhile { it.isEmpty() }
103+
.filter { install ->
104+
try{
105+
val (path, version) = install.split("^")
106+
val file = File(path)
107+
if(!file.exists() || file.isDirectory){
108+
return@filter false
109+
}
110+
// call the path to check if it is a valid install location
111+
val process = ProcessBuilder(path, "--version")
112+
.redirectErrorStream(true)
113+
.start()
114+
val exitCode = process.waitFor()
115+
if(exitCode != 0){
116+
return@filter false
117+
}
118+
val output = process.inputStream.bufferedReader().readText()
119+
return@filter output.contains(version)
120+
} catch (e: Exception){
121+
false
122+
}
123+
}
124+
.toMutableList()
125+
val command = ProcessHandle.current().info().command()
126+
if(command.isEmpty) {
127+
return
128+
}
129+
val installLocation = "${command.get()}^${Base.getVersionName()}"
130+
131+
132+
// Check if the installLocation is already in the list
133+
if (installLocations.contains(installLocation)) {
134+
return
135+
}
136+
137+
// Add the installLocation to the list
138+
installLocations.add(installLocation)
139+
140+
// Save the updated list back to preferences
141+
preferences.put("installLocations", java.lang.String.join(",", installLocations))
142+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package processing.app.api
2+
3+
import com.github.ajalt.clikt.command.SuspendingCliktCommand
4+
import com.github.ajalt.clikt.core.Context
5+
import com.github.ajalt.clikt.core.subcommands
6+
import kotlinx.serialization.encodeToString
7+
import kotlinx.serialization.json.Json
8+
import processing.app.Platform
9+
import processing.app.api.Sketch.Companion.getSketches
10+
import java.io.File
11+
12+
class Contributions: SuspendingCliktCommand(){
13+
override fun help(context: Context) = "Manage Processing contributions"
14+
override suspend fun run() {
15+
System.setProperty("java.awt.headless", "true")
16+
}
17+
init {
18+
subcommands(Examples())
19+
}
20+
21+
class Examples: SuspendingCliktCommand("examples") {
22+
override fun help(context: Context) = "Manage Processing examples"
23+
override suspend fun run() {
24+
}
25+
init {
26+
subcommands(ExamplesList())
27+
}
28+
}
29+
30+
class ExamplesList: SuspendingCliktCommand("list") {
31+
32+
33+
val serializer = Json {
34+
prettyPrint = true
35+
}
36+
37+
override fun help(context: Context) = "List all examples"
38+
override suspend fun run() {
39+
Platform.init()
40+
// TODO: Decouple modes listing from `Base` class, defaulting to Java mode for now
41+
// TODO: Allow the user to change the sketchbook location
42+
// TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode
43+
val sketchbookFolder = Platform.getDefaultSketchbookFolder()
44+
val resourcesDir = System.getProperty("compose.application.resources.dir")
45+
46+
val javaMode = "$resourcesDir/modes/java"
47+
48+
val javaModeExamples = File("$javaMode/examples")
49+
.listFiles()
50+
?.map { getSketches(it)}
51+
?: emptyList()
52+
53+
val javaModeLibrariesExamples = File("$javaMode/libraries")
54+
.listFiles{ it.isDirectory }
55+
?.map { library ->
56+
val properties = library.resolve("library.properties")
57+
val name = findNameInProperties(properties) ?: library.name
58+
59+
val libraryExamples = getSketches(library.resolve("examples"))
60+
Sketch.Companion.Folder(
61+
type = "folder",
62+
name = name,
63+
path = library.absolutePath,
64+
mode = "java",
65+
children = libraryExamples?.children ?: emptyList(),
66+
sketches = libraryExamples?.sketches ?: emptyList()
67+
)
68+
} ?: emptyList()
69+
val javaModeLibraries = Sketch.Companion.Folder(
70+
type = "folder",
71+
name = "Libraries",
72+
path = "$javaMode/libraries",
73+
mode = "java",
74+
children = javaModeLibrariesExamples,
75+
sketches = emptyList()
76+
)
77+
78+
val contributedLibraries = sketchbookFolder.resolve("libraries")
79+
.listFiles{ it.isDirectory }
80+
?.map { library ->
81+
val properties = library.resolve("library.properties")
82+
val name = findNameInProperties(properties) ?: library.name
83+
// Get library name from library.properties if it exists
84+
val libraryExamples = getSketches(library.resolve("examples"))
85+
Sketch.Companion.Folder(
86+
type = "folder",
87+
name = name,
88+
path = library.absolutePath,
89+
mode = "java",
90+
children = libraryExamples?.children ?: emptyList(),
91+
sketches = libraryExamples?.sketches ?: emptyList()
92+
)
93+
} ?: emptyList()
94+
95+
val contributedLibrariesFolder = Sketch.Companion.Folder(
96+
type = "folder",
97+
name = "Contributed Libraries",
98+
path = sketchbookFolder.resolve("libraries").absolutePath,
99+
mode = "java",
100+
children = contributedLibraries,
101+
sketches = emptyList()
102+
)
103+
104+
val contributedExamples = sketchbookFolder.resolve("examples")
105+
.listFiles{ it.isDirectory }
106+
?.map {
107+
val properties = it.resolve("examples.properties")
108+
val name = findNameInProperties(properties) ?: it.name
109+
110+
val sketches = getSketches(it.resolve("examples"))
111+
Sketch.Companion.Folder(
112+
type = "folder",
113+
name,
114+
path = it.absolutePath,
115+
mode = "java",
116+
children = sketches?.children ?: emptyList(),
117+
sketches = sketches?.sketches ?: emptyList(),
118+
)
119+
}
120+
?: emptyList()
121+
val contributedExamplesFolder = Sketch.Companion.Folder(
122+
type = "folder",
123+
name = "Contributed Examples",
124+
path = sketchbookFolder.resolve("examples").absolutePath,
125+
mode = "java",
126+
children = contributedExamples,
127+
sketches = emptyList()
128+
)
129+
130+
val json = serializer.encodeToString(javaModeExamples + javaModeLibraries + contributedLibrariesFolder + contributedExamplesFolder)
131+
println(json)
132+
}
133+
134+
private fun findNameInProperties(properties: File): String? {
135+
if (!properties.exists()) return null
136+
137+
return properties.readLines().firstNotNullOfOrNull { line ->
138+
line.split("=", limit = 2)
139+
.takeIf { it.size == 2 && it[0].trim() == "name" }
140+
?.let { it[1].trim() }
141+
}
142+
}
143+
}
144+
}

app/src/processing/app/api/Sketch.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package processing.app.api
2+
3+
import kotlinx.serialization.Serializable
4+
import java.io.File
5+
6+
class Sketch {
7+
companion object{
8+
@Serializable
9+
data class Sketch(
10+
val type: String = "sketch",
11+
val name: String,
12+
val path: String,
13+
val mode: String = "java",
14+
)
15+
16+
@Serializable
17+
data class Folder(
18+
val type: String = "folder",
19+
val name: String,
20+
val path: String,
21+
val mode: String = "java",
22+
val children: List<Folder> = emptyList(),
23+
val sketches: List<Sketch> = emptyList()
24+
)
25+
26+
fun getSketches(file: File, filter: (File) -> Boolean = { true }): Folder? {
27+
val name = file.name
28+
val (sketchesFolders, childrenFolders) = file.listFiles()?.filter (File::isDirectory)?.partition { isSketchFolder(it) } ?: return Folder(
29+
name = name,
30+
path = file.absolutePath,
31+
sketches = emptyList(),
32+
children = emptyList()
33+
)
34+
val children = childrenFolders.filter(filter).mapNotNull { getSketches(it) }
35+
val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) }
36+
if(sketches.isEmpty() && children.isEmpty()) {
37+
return null
38+
}
39+
return Folder(
40+
name = name,
41+
path = file.absolutePath,
42+
children = children,
43+
sketches = sketches
44+
)
45+
}
46+
fun isSketchFolder(file: File): Boolean {
47+
return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") }
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)