Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.ui.components.JBLoadingPanel
Expand All @@ -19,11 +20,12 @@
import com.intellij.ui.jcef.JBCefApp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import software.aws.toolkits.jetbrains.isDeveloperMode
import software.aws.toolkits.jetbrains.services.amazonq.apps.AmazonQAppInitContext
import software.aws.toolkits.jetbrains.services.amazonq.apps.AppConnection
import software.aws.toolkits.jetbrains.services.amazonq.commands.MessageTypeRegistry
import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactHelper
import software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts.ArtifactManager
import software.aws.toolkits.jetbrains.services.amazonq.lsp.flareChat.AsyncChatUiListener
import software.aws.toolkits.jetbrains.services.amazonq.messages.AmazonQMessage
import software.aws.toolkits.jetbrains.services.amazonq.messages.MessageConnector
Expand Down Expand Up @@ -105,7 +107,7 @@
wrapper.setContent(loadingPanel)

ApplicationManager.getApplication().executeOnPooledThread {
val webUri = ArtifactHelper().getLatestLocalLspArtifact().resolve("amazonq-ui.js").toUri()
val webUri = runBlocking { service<ArtifactManager>().fetchArtifact(project).resolve("amazonq-ui.js").toUri() }

Check warning on line 110 in plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQPanel.kt#L110

Added line #L110 was not covered by tests
loadingPanel.stopLoading()
runInEdt {
browser.complete(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)
): String {
val quickActionConfig = generateQuickActionConfig()
val postMessageToJavaJsCode = receiveMessageQuery.inject("JSON.stringify(message)")
// language=HTML
val jsScripts = """
<script type="text/javascript" src="$webUri" defer onload="init()"></script>
<script type="text/javascript">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@

init {
// will cause slow service init, but maybe fine for now. will not block UI since fetch/extract will be under background progress
val artifact = runBlocking { ArtifactManager(project, manifestRange = null).fetchArtifact() }.toAbsolutePath()
val artifact = runBlocking { service<ArtifactManager>().fetchArtifact(project) }.toAbsolutePath()

Check warning on line 269 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt#L269

Added line #L269 was not covered by tests
val node = if (SystemInfo.isWindows) "node.exe" else "node"
val cmd = GeneralCommandLine(
artifact.resolve(node).toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts

import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.util.io.createDirectories
Expand All @@ -18,16 +19,14 @@
import software.aws.toolkits.jetbrains.core.saveFileFromUrl
import software.aws.toolkits.jetbrains.services.amazonq.project.manifest.ManifestManager
import software.aws.toolkits.resources.AwsCoreBundle
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicInteger

class ArtifactHelper(private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH, private val maxDownloadAttempts: Int = MAX_DOWNLOAD_ATTEMPTS) {

companion object {
private val DEFAULT_ARTIFACT_PATH = getToolkitsCommonCacheRoot().resolve("aws").resolve("toolkits").resolve("language-servers")
private val logger = getLogger<ArtifactHelper>()
private const val MAX_DOWNLOAD_ATTEMPTS = 3
}
class ArtifactHelper internal constructor(
private val lspArtifactsPath: Path = DEFAULT_ARTIFACT_PATH,
private val maxDownloadAttempts: Int = MAX_DOWNLOAD_ATTEMPTS,
) {

Check warning on line 29 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt#L26-L29

Added lines #L26 - L29 were not covered by tests
private val currentAttempt = AtomicInteger(0)

fun removeDelistedVersions(delistedVersions: List<ManifestManager.Version>) {
Expand Down Expand Up @@ -79,16 +78,6 @@
.sortedByDescending { (_, semVer) -> semVer }
}

fun getLatestLocalLspArtifact(): Path {
val localFolders = getSubFolders(lspArtifactsPath)
return localFolders.map { localFolder ->
localFolder to SemVer.parseFromText(localFolder.fileName.toString())
}
.sortedByDescending { (_, semVer) -> semVer }
.first()
.first
}

fun getExistingLspArtifacts(versions: List<ManifestManager.Version>, target: ManifestManager.VersionTarget?): Boolean {
if (versions.isEmpty() || target?.contents == null) return false

Expand All @@ -114,7 +103,7 @@
}

suspend fun tryDownloadLspArtifacts(project: Project, versions: List<ManifestManager.Version>, target: ManifestManager.VersionTarget?): Path? {
val temporaryDownloadPath = lspArtifactsPath.resolve("temp")
val temporaryDownloadPath = Files.createTempDirectory("lsp-dl")

Check warning on line 106 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Possibly blocking call in non-blocking context

Possibly blocking call in non-blocking context could lead to thread starvation

Check warning on line 106 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt#L106

Added line #L106 was not covered by tests

Check warning

Code scanning / QDJVMC

Possibly blocking call in non-blocking context Warning

Possibly blocking call in non-blocking context could lead to thread starvation
val downloadPath = lspArtifactsPath.resolve(versions.first().serverVersion.toString())

while (currentAttempt.get() < maxDownloadAttempts) {
Expand Down Expand Up @@ -188,7 +177,7 @@
try {
if (!filePath.exists()) {
logger.info { "Downloading file: ${filePath.fileName}" }
saveFileFromUrl(url, filePath)
saveFileFromUrl(url, filePath, ProgressManager.getInstance().progressIndicator)

Check warning on line 180 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt#L180

Added line #L180 was not covered by tests
}
if (!validateFileHash(filePath, expectedHash)) {
logger.warn { "Hash mismatch for ${filePath.fileName}, re-downloading" }
Expand Down Expand Up @@ -222,4 +211,10 @@
throw LspException(errorMessage, LspException.ErrorCode.DOWNLOAD_FAILED)
}
}

companion object {
private val DEFAULT_ARTIFACT_PATH = getToolkitsCommonCacheRoot().resolve("aws").resolve("toolkits").resolve("language-servers")
private val logger = getLogger<ArtifactHelper>()

Check warning on line 217 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactHelper.kt#L216-L217

Added lines #L216 - L217 were not covered by tests
private const val MAX_DOWNLOAD_ATTEMPTS = 3
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,32 @@

package software.aws.toolkits.jetbrains.services.amazonq.lsp.artifacts

import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import com.intellij.serviceContainer.NonInjectable
import com.intellij.util.text.SemVer
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.jetbrains.annotations.VisibleForTesting
import software.aws.toolkits.core.utils.error
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.info
import software.aws.toolkits.jetbrains.services.amazonq.project.manifest.ManifestManager
import java.nio.file.Path

class ArtifactManager(
private val project: Project,
private val manifestFetcher: ManifestFetcher = ManifestFetcher(),
private val artifactHelper: ArtifactHelper = ArtifactHelper(),
manifestRange: SupportedManifestVersionRange?,
) {
@Service
class ArtifactManager @NonInjectable internal constructor(private val manifestFetcher: ManifestFetcher, private val artifactHelper: ArtifactHelper) {
constructor() : this(

Check warning on line 24 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Unused symbol

Constructor is never used

Check warning

Code scanning / QDJVMC

Unused symbol Warning

Constructor is never used
ManifestFetcher(),
ArtifactHelper()
)

Check warning on line 27 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L22-L27

Added lines #L22 - L27 were not covered by tests

// we currently cannot handle the versions swithing in the middle of a user's session
private val mutex = Mutex()

Check warning on line 30 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L30

Added line #L30 was not covered by tests
private var artifactDeferred: Deferred<Path>? = null

data class SupportedManifestVersionRange(
val startVersion: SemVer,
Expand All @@ -28,8 +39,6 @@
val inRangeVersions: List<ManifestManager.Version>,
)

private val manifestVersionRanges: SupportedManifestVersionRange = manifestRange ?: DEFAULT_VERSION_RANGE

companion object {
private val DEFAULT_VERSION_RANGE = SupportedManifestVersionRange(
startVersion = SemVer("0.0.0", 0, 0, 0),
Expand All @@ -38,35 +47,47 @@
private val logger = getLogger<ArtifactManager>()
}

suspend fun fetchArtifact(): Path {
val manifest = manifestFetcher.fetch() ?: throw LspException(
"Language Support is not available, as manifest is missing.",
LspException.ErrorCode.MANIFEST_FETCH_FAILED
)
val lspVersions = getLSPVersionsFromManifestWithSpecifiedRange(manifest)
suspend fun fetchArtifact(project: Project): Path {

Check warning on line 50 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L50

Added line #L50 was not covered by tests
mutex.withLock { artifactDeferred }?.let {
return it.await()

Check warning on line 52 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L52

Added line #L52 was not covered by tests
}

this.artifactHelper.removeDelistedVersions(lspVersions.deListedVersions)
return mutex.withLock {

Check warning on line 55 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L55

Added line #L55 was not covered by tests
coroutineScope {
async {

Check warning on line 57 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L57

Added line #L57 was not covered by tests
val manifest = manifestFetcher.fetch() ?: throw LspException(
"Language Support is not available, as manifest is missing.",
LspException.ErrorCode.MANIFEST_FETCH_FAILED

Check warning on line 60 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L59-L60

Added lines #L59 - L60 were not covered by tests
)
val lspVersions = getLSPVersionsFromManifestWithSpecifiedRange(manifest)

Check warning on line 62 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L62

Added line #L62 was not covered by tests

if (lspVersions.inRangeVersions.isEmpty()) {
// No versions are found which are in the given range. Fallback to local lsp artifacts.
val localLspArtifacts = this.artifactHelper.getAllLocalLspArtifactsWithinManifestRange(manifestVersionRanges)
if (localLspArtifacts.isNotEmpty()) {
return localLspArtifacts.first().first
}
throw LspException("Language server versions not found in manifest.", LspException.ErrorCode.NO_COMPATIBLE_LSP_VERSION)
}
artifactHelper.removeDelistedVersions(lspVersions.deListedVersions)

Check warning on line 64 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L64

Added line #L64 was not covered by tests

// If there is an LSP Manifest with the same version
val target = getTargetFromLspManifest(lspVersions.inRangeVersions)
// Get Local LSP files and check if we can re-use existing LSP Artifacts
val artifactPath: Path = if (this.artifactHelper.getExistingLspArtifacts(lspVersions.inRangeVersions, target)) {
this.artifactHelper.getAllLocalLspArtifactsWithinManifestRange(manifestVersionRanges).first().first
} else {
this.artifactHelper.tryDownloadLspArtifacts(project, lspVersions.inRangeVersions, target)
?: throw LspException("Failed to download LSP artifacts", LspException.ErrorCode.DOWNLOAD_FAILED)
}
this.artifactHelper.deleteOlderLspArtifacts(manifestVersionRanges)
return artifactPath
if (lspVersions.inRangeVersions.isEmpty()) {
// No versions are found which are in the given range. Fallback to local lsp artifacts.
val localLspArtifacts = artifactHelper.getAllLocalLspArtifactsWithinManifestRange(DEFAULT_VERSION_RANGE)

Check warning on line 68 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L68

Added line #L68 was not covered by tests
if (localLspArtifacts.isNotEmpty()) {
return@async localLspArtifacts.first().first

Check warning on line 70 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L70

Added line #L70 was not covered by tests
}
throw LspException("Language server versions not found in manifest.", LspException.ErrorCode.NO_COMPATIBLE_LSP_VERSION)

Check warning on line 72 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L72

Added line #L72 was not covered by tests
}

// If there is an LSP Manifest with the same version
val target = getTargetFromLspManifest(lspVersions.inRangeVersions)

Check warning on line 76 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L76

Added line #L76 was not covered by tests
// Get Local LSP files and check if we can re-use existing LSP Artifacts
val artifactPath: Path = if (artifactHelper.getExistingLspArtifacts(lspVersions.inRangeVersions, target)) {
artifactHelper.getAllLocalLspArtifactsWithinManifestRange(DEFAULT_VERSION_RANGE).first().first

Check warning on line 79 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L79

Added line #L79 was not covered by tests
} else {
artifactHelper.tryDownloadLspArtifacts(project, lspVersions.inRangeVersions, target)
?: throw LspException("Failed to download LSP artifacts", LspException.ErrorCode.DOWNLOAD_FAILED)

Check warning on line 82 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L82

Added line #L82 was not covered by tests
}
artifactHelper.deleteOlderLspArtifacts(DEFAULT_VERSION_RANGE)
return@async artifactPath
}
}.also {
artifactDeferred = it
}
}.await()

Check warning on line 90 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/artifacts/ArtifactManager.kt#L84-L90

Added lines #L84 - L90 were not covered by tests
}

@VisibleForTesting
Expand All @@ -78,7 +99,7 @@
SemVer.parseFromText(serverVersion)?.let { semVer ->
when {
version.isDelisted != false -> Pair(version, true) // Is deListed
semVer in manifestVersionRanges.startVersion..manifestVersionRanges.endVersion -> Pair(version, false) // Is in range
semVer in DEFAULT_VERSION_RANGE.let { it.startVersion..it.endVersion } -> Pair(version, false) // Is in range
else -> null
}
}
Expand Down
Loading
Loading