Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions src/main/kotlin/org/pkl/lsp/VirtualFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ sealed class BaseFile : VirtualFile, CachedValueDataHolderBase() {

private var readError: Exception? = null

private val lock = Object()

// If contents have not yet been set, read contents from external source, possibly performing I/O.
override var contents: String
get() {
Expand All @@ -128,7 +130,7 @@ sealed class BaseFile : VirtualFile, CachedValueDataHolderBase() {
readError = null
project.cachedValuesManager.clearCachedValue(this, cacheKey)
}
return project.cachedValuesManager.getCachedValue(this, cacheKey) {
return project.cachedValuesManager.getCachedValue(this, cacheKey, lock) {
CachedValue(CompletableFuture.supplyAsync(::doBuildModule), this)
}!!
}
Expand Down Expand Up @@ -163,7 +165,7 @@ class FsFile(override val path: Path, override val project: Project) : BaseFile(

override val pklProjectDir: VirtualFile?
get() =
project.cachedValuesManager.getCachedValue(this, "FsFile.getProjectDir($path)") {
project.cachedValuesManager.getCachedValue(this, "FsFile.getProjectDir($path)", lock) {
val dependency = project.pklProjectManager.addedOrRemovedFilesModificationTracker
var dir = if (Files.isDirectory(path)) this else parent()
while (dir != null) {
Expand Down Expand Up @@ -200,6 +202,8 @@ class FsFile(override val path: Path, override val project: Project) : BaseFile(
override fun canModify(): Boolean = true

override fun doReadContents(): String = path.readText()

private val lock = Object()
}

class StdlibFile(moduleName: String, override val project: Project) : BaseFile() {
Expand Down Expand Up @@ -272,6 +276,8 @@ class HttpsFile(override val uri: URI, override val project: Project) : BaseFile

class JarFile(override val path: Path, override val uri: URI, override val project: Project) :
BaseFile() {
private val lock = Object()

override val name: String
get() = path.name

Expand All @@ -288,7 +294,7 @@ class JarFile(override val path: Path, override val uri: URI, override val proje

override val `package`: PackageDependency?
get() =
project.cachedValuesManager.getCachedValue(this, "JarFile.package(${uri})") {
project.cachedValuesManager.getCachedValue(this, "JarFile.package(${uri})", lock) {
val jarFile: Path = Path.of(URI(uri.toString().drop(4).substringBefore("!/")))
val jsonFile =
jarFile.parent.resolve(jarFile.nameWithoutExtension + ".json")
Expand All @@ -308,7 +314,11 @@ class JarFile(override val path: Path, override val uri: URI, override val proje
project.virtualFileManager.get(this.path.resolve(path))

override fun doReadContents(): String =
project.cachedValuesManager.getCachedValue(this, "${javaClass.simpleName}-contents-${uri}") {
project.cachedValuesManager.getCachedValue(
this,
"${javaClass.simpleName}-contents-${uri}",
lock,
) {
CachedValue(path.readText())
}!!

Expand Down
4 changes: 3 additions & 1 deletion src/main/kotlin/org/pkl/lsp/ast/Member.kt
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ class PklObjectBodyImpl(
override val parent: PklNode,
override val ctx: Node,
) : AbstractPklNode(project, parent, ctx), PklObjectBody {
private val lock = Object()

override val parameters: PklParameterList? by lazy {
children.firstInstanceOf<PklParameterList>()
}
Expand All @@ -220,7 +222,7 @@ class PklObjectBodyImpl(
}

override fun isConstScope(): Boolean {
return project.cachedValuesManager.getCachedValue(this, "isConstScope()") {
return project.cachedValuesManager.getCachedValue(this, "isConstScope()", lock) {
val parentProperty = parentOfTypes(PklProperty::class, PklMethod::class)
val isConstScope =
if (parentProperty?.isConst == true) {
Expand Down
5 changes: 4 additions & 1 deletion src/main/kotlin/org/pkl/lsp/ast/ModuleOrClass.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ class PklModuleImpl(override val ctx: Node, override val virtualFile: VirtualFil
header?.moduleExtendsAmendsClause?.moduleUri
}

private val lock = Object()

// This is cached at the VirtualFile level
override fun supermodule(context: PklProject?): PklModule? =
project.cachedValuesManager.getCachedValue(this, "supermodule") {
project.cachedValuesManager.getCachedValue(this, "supermodule", lock) {
val resolved = extendsAmendsUri?.resolve(context)
CachedValue(resolved, listOfNotNull(this.containingFile, resolved?.containingFile))
}
Expand All @@ -61,6 +63,7 @@ class PklModuleImpl(override val ctx: Node, override val virtualFile: VirtualFil
project.cachedValuesManager.getCachedValue(
this,
"PklModule.cache(${virtualFile.uri}, ${context?.projectDir}}",
lock,
) {
val cache = ModuleMemberCache.create(this, context)
CachedValue(cache, cache.dependencies + project.pklProjectManager.syncTracker)
Expand Down
19 changes: 17 additions & 2 deletions src/main/kotlin/org/pkl/lsp/ast/PklModuleUri.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@
package org.pkl.lsp.ast

import io.github.treesitter.jtreesitter.Node
import java.nio.file.Files
import java.nio.file.Path
import org.pkl.lsp.*
import org.pkl.lsp.FsFile
Expand All @@ -40,6 +41,8 @@ class PklModuleUriImpl(project: Project, override val parent: PklNode, override

companion object {

private val lock = Object()

fun resolve(
project: Project,
targetUri: String,
Expand Down Expand Up @@ -78,7 +81,8 @@ class PklModuleUriImpl(project: Project, override val parent: PklNode, override
context: PklProject?,
): List<VirtualFile>? =
element.project.cachedValuesManager.getCachedValue(
"PklModuleUri.resolveGlob($targetUriString, $moduleUriString, ${element.containingFile.path}, ${context?.projectDir})"
"PklModuleUri.resolveGlob($targetUriString, $moduleUriString, ${element.containingFile.path}, ${context?.projectDir})",
lock,
) {
val result =
doResolveGlob(targetUriString, moduleUriString, element, context)
Expand Down Expand Up @@ -122,6 +126,17 @@ class PklModuleUriImpl(project: Project, override val parent: PklNode, override
?.resolve(targetUri.fragment)
vfile?.getModule()?.get()
}
"modulepath" -> {
val path = targetUri.path.trimStart('/')
val absolutePath =
project.settingsManager.settings.pklModulepath
.map { it.resolve(path) }
.firstOrNull(Files::exists) ?: return null
return sourceFile.project.virtualFileManager
.get(absolutePath.toUri(), absolutePath)
?.getModule()
?.get()
}
// targetUri is a relative URI
null -> {
when {
Expand Down
5 changes: 4 additions & 1 deletion src/main/kotlin/org/pkl/lsp/services/DiagnosticsManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class DiagnosticsManager(project: Project) : Component(project) {
private val openFiles: MutableMap<URI, Boolean> = ConcurrentHashMap()
private val downloadPackageTracker = SimpleModificationTracker()

private val lock = Object()

override fun initialize(): CompletableFuture<*> {
project.messageBus.subscribe(textDocumentTopic, ::handleTextDocumentEvent)
project.messageBus.subscribe(projectTopic, ::handleProjectEvent)
Expand Down Expand Up @@ -96,7 +98,8 @@ class DiagnosticsManager(project: Project) : Component(project) {

fun getDiagnostics(module: PklModule): List<PklDiagnostic> {
return project.cachedValuesManager.getCachedValue(
"DiagnosticsManager.getDiagnostics(${module.uri})"
"DiagnosticsManager.getDiagnostics(${module.uri})",
lock,
) {
val holder = DiagnosticsHolder()
for (analyzer in analyzers) {
Expand Down
9 changes: 6 additions & 3 deletions src/main/kotlin/org/pkl/lsp/services/PackageManager.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -47,8 +47,10 @@ sealed interface PackageEvent {
class PackageManager(project: Project) : Component(project) {
private fun Path.toVirtualFile(): VirtualFile? = project.virtualFileManager.get(this)

val lock = Object()

fun getLibraryRoots(dependency: PackageDependency): PackageLibraryRoots? =
project.cachedValuesManager.getCachedValue("library-roots-${dependency.packageUri}") {
project.cachedValuesManager.getCachedValue("library-roots-${dependency.packageUri}", lock) {
logger.info("Getting library roots for ${dependency.packageUri}")
val metadataFile =
dependency.packageUri.relativeMetadataFiles.firstNotNullOfOrNull {
Expand Down Expand Up @@ -92,7 +94,8 @@ class PackageManager(project: Project) : Component(project) {

private fun getPackageMetadata(packageDependency: PackageDependency): PackageMetadata? =
project.cachedValuesManager.getCachedValue(
"PackageManager.getPackageMetadata(${packageDependency.packageUri})"
"PackageManager.getPackageMetadata(${packageDependency.packageUri})",
lock,
) {
val roots = getLibraryRoots(packageDependency) ?: return@getCachedValue null
CachedValue(PackageMetadata.load(roots.metadataFile))
Expand Down
64 changes: 46 additions & 18 deletions src/main/kotlin/org/pkl/lsp/services/SettingsManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package org.pkl.lsp.services

import com.google.gson.JsonElement
import com.google.gson.JsonPrimitive
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.CompletableFuture
import kotlin.io.path.isExecutable
Expand All @@ -25,7 +26,11 @@ import org.eclipse.lsp4j.*
import org.pkl.lsp.*
import org.pkl.lsp.messages.ActionableNotification

@Serializable data class WorkspaceSettings(var pklCliPath: Path? = null)
@Serializable
data class WorkspaceSettings(
var pklCliPath: Path? = null,
var pklModulepath: List<Path> = listOf(),
)

class SettingsManager(project: Project) : Component(project) {
var settings: WorkspaceSettings = WorkspaceSettings()
Expand All @@ -50,27 +55,50 @@ class SettingsManager(project: Project) : Component(project) {
ConfigurationItem().apply {
scopeUri = "Pkl"
section = "pkl.cli.path"
}
},
ConfigurationItem().apply {
scopeUri = "Pkl"
section = "pkl.modulepath"
},
)
)
return project.languageClient
.configuration(params)
.thenApply { (cliPath) ->
logger.log("Got configuration: $cliPath")
cliPath as JsonElement
if (cliPath.isJsonNull) {
settings.pklCliPath = findPklCliOnPath()
return@thenApply
}
if (!(cliPath is JsonPrimitive && cliPath.isString)) {
logger.warn("Got non-string value for configuration: $cliPath")
return@thenApply
return project.languageClient.configuration(params).thenApply { (cliPath, modulepath) ->
logger.log("Got configuration: { cli.path = $cliPath, modulepath = $modulepath }")
setCliPath(cliPath as JsonElement)
setModulepath(modulepath as JsonElement)
}
}

private fun setCliPath(cliPath: JsonElement) {
if (cliPath.isJsonNull) {
settings.pklCliPath = findPklCliOnPath()
return
}
if (!(cliPath is JsonPrimitive && cliPath.isString)) {
logger.warn("Got non-string value for configuration: $cliPath")
return
}
settings.pklCliPath =
cliPath.asString.let { if (it.isEmpty()) findPklCliOnPath() else Path.of(it) }
}

private fun setModulepath(modulepath: JsonElement) {
if (modulepath.isJsonNull) return
if (!modulepath.isJsonArray) {
logger.warn("Got non-array value for configuration: $modulepath")
return
}
settings.pklModulepath = buildList {
for (path in modulepath.asJsonArray) {
if (!(path is JsonPrimitive && path.isString)) {
logger.warn("Got non-string value in modulepath: $path")
continue
}
settings.pklCliPath =
cliPath.asString.let { if (it.isEmpty()) findPklCliOnPath() else Path.of(it) }
val entry = Path.of(path.asString)
if (Files.exists(entry)) add(entry)
else logger.warn("Entry in modulepath does not exist: $entry")
}
.exceptionally { logger.error("Failed to fetch settings: ${it.cause}") }
.whenComplete { _, _ -> logger.log("Settings changed to $settings") }
}
}

private fun findPklCliOnPath(): Path? {
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/org/pkl/lsp/type/ComputeExprType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ fun PklNode?.computeExprType(
project.cachedValuesManager.getCachedValue(
containingFile,
"computeExprType(${System.identityHashCode(this)})",
lock = this,
) {
val result = doComputeExprType(base, bindings, context)
val dependencies = buildList {
Expand Down
7 changes: 4 additions & 3 deletions src/main/kotlin/org/pkl/lsp/util/CachedValuesManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,17 @@ class CachedValuesManager(project: Project) : Component(project), CachedValueDat
fun <T> getCachedValue(
holder: CachedValueDataHolder,
key: String,
lock: Any,
provider: () -> CachedValue<T>?,
): T? {
synchronized(holder) {
synchronized(lock) {
return getValue<T>(holder, key)?.value
?: provider()?.also { storeCachedValue(holder, key, it) }?.value
}
}

fun <T> getCachedValue(key: String, provider: () -> CachedValue<T>?): T? =
getCachedValue(this, key, provider)
fun <T> getCachedValue(key: String, lock: Any, provider: () -> CachedValue<T>?): T? =
getCachedValue(this, key, lock, provider)

fun clearCachedValue(holder: CachedValueDataHolder, key: String) {
holder.cachedValues.remove(key)
Expand Down