Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: infinite indexing / intellisense blocked #102

Merged
merged 2 commits into from
Dec 10, 2024
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to this project will be documented in this file.

## Unreleased

### Bug Fixes

- Fix infinite indexing / intellisense blocked ([#66](https://github.com/biomejs/biome-intellij/issues/66))
Comment on lines +5 to +9
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to update the changelog, that's done automatically by git-cliff.


## 1.0.0

### Bug Fixes
Expand Down
56 changes: 30 additions & 26 deletions src/main/kotlin/com/github/biomejs/intellijbiome/BiomePackage.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.github.biomejs.intellijbiome

import com.github.biomejs.intellijbiome.extensions.runBiomeCLI
import com.github.biomejs.intellijbiome.extensions.runProcessFuture
import com.github.biomejs.intellijbiome.settings.BiomeSettings
import com.github.biomejs.intellijbiome.settings.ConfigurationMode
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.util.ExecUtil
import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterManager
import com.intellij.javascript.nodejs.util.NodePackage
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import kotlinx.coroutines.future.await
import java.nio.file.Paths


private val versionRegex: Regex = Regex("\\d{1,2}\\.\\d{1,2}\\.\\d{1,3}")

class BiomePackage(private val project: Project) {
private val interpreter = NodeJsInterpreterManager.getInstance(project).interpreter
private val nodePackage: NodePackage?
Expand All @@ -28,7 +30,7 @@ class BiomePackage(private val project: Project) {
}
}

fun versionNumber(): String? {
suspend fun versionNumber(): String? {
val settings = BiomeSettings.getInstance(project)
val configurationMode = settings.configurationMode
return when (configurationMode) {
Expand All @@ -38,41 +40,41 @@ class BiomePackage(private val project: Project) {
}
}

fun binaryPath(configPath: String?, showVersion: Boolean): String? {
fun binaryPath(configPath: String?,
showVersion: Boolean): String? {
val settings = BiomeSettings.getInstance(project)
val configurationMode = settings.configurationMode
return when (configurationMode) {
ConfigurationMode.DISABLED -> null
// don't try to find the executable path if the configuration file does not exist.
ConfigurationMode.DISABLED -> null // don't try to find the executable path if the configuration file does not exist.
// This will prevent start LSP and formatting in case if biome is not used in the project.
ConfigurationMode.AUTOMATIC -> if (configPath != null || showVersion) findBiomeExecutable() else null
// if configuration mode is manual, return the executable path if it is not empty string.
ConfigurationMode.AUTOMATIC -> if (configPath != null || showVersion) findBiomeExecutable() else null // if configuration mode is manual, return the executable path if it is not empty string.
// Otherwise, try to find the executable path.
ConfigurationMode.MANUAL -> if (settings.executablePath == "") findBiomeExecutable() else settings.executablePath
}
}

private fun findBiomeExecutable() = nodePackage?.getAbsolutePackagePathToRequire(project)?.let {
Paths.get(
it,
"bin/biome"
)
}?.toString()
private fun findBiomeExecutable(): String? {
val path = nodePackage?.getAbsolutePackagePathToRequire(project)
if (path != null) {
return Paths.get(path, "bin/biome").toString()
}

return null
}


private fun getBinaryVersion(binaryPath: String?): String? {
private suspend fun getBinaryVersion(binaryPath: String?): String? {
if (binaryPath.isNullOrEmpty()) {
return null
}

val versionRegex = Regex("\\d{1,2}\\.\\d{1,2}\\.\\d{1,3}")
val commandLine = GeneralCommandLine().runBiomeCLI(project, binaryPath).apply {
addParameter("--version")
}

val processHandler =
BiomeTargetRunBuilder(project).getBuilder(binaryPath).addParameters(listOf("--version")).build()
return runCatching {
val output = ExecUtil.execAndGetOutput(commandLine)
val matchResult = versionRegex.find(output.stdout)
val result = processHandler.runProcessFuture().await()
val processOutput = result.processOutput
val stdout = processOutput.stdout.trim()
val matchResult = versionRegex.find(stdout)
return matchResult?.value
}.getOrNull()
}
Expand All @@ -82,7 +84,8 @@ class BiomePackage(private val project: Project) {
val configValidExtensions = listOf("json", "jsonc")
}

private fun findPathUpwards(file: VirtualFile, fileName: List<String>): VirtualFile? {
private fun findPathUpwards(file: VirtualFile,
fileName: List<String>): VirtualFile? {
var cur = file.parent
while (cur != null) {
if (cur.children.find { name -> fileName.any { it == name.name } } != null) {
Expand All @@ -93,13 +96,14 @@ class BiomePackage(private val project: Project) {
return null
}

fun compareVersion(version1: String, version2: String): Int {
fun compareVersion(version1: String,
version2: String): Int {
val parts1 = version1.split(".").map { it.toInt() }
val parts2 = version2.split(".").map { it.toInt() }

val maxLength = maxOf(parts1.size, parts2.size)

for (i in 0 until maxLength){
for (i in 0 until maxLength) {
val v1 = if (i < parts1.size) parts1[i] else 0
val v2 = if (i < parts2.size) parts2[i] else 0

Expand Down
13 changes: 4 additions & 9 deletions src/main/kotlin/com/github/biomejs/intellijbiome/BiomeRunner.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package com.github.biomejs.intellijbiome

import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.openapi.editor.Document
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.annotations.Nls
import java.util.*
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds

Expand All @@ -17,18 +16,16 @@ enum class Feature {

interface BiomeRunner {
companion object {
val DEFAULT_TIMEOUT = 30000.milliseconds
val DEFAULT_TIMEOUT: Duration = 30000.milliseconds
}

fun check(request: Request, features: EnumSet<Feature>, biomePackage: BiomePackage): Response
fun createCommandLine(file: VirtualFile, action: String, args: List<String> = listOf()): GeneralCommandLine

suspend fun check(request: Request): Response

data class Request(
val document: Document,
val virtualFile: VirtualFile,
val timeout: Duration,
val commandDescription: String
@NlsContexts.Command val commandDescription: String
)

sealed class Response {
Expand All @@ -42,5 +39,3 @@ interface BiomeRunner {

}
}


101 changes: 38 additions & 63 deletions src/main/kotlin/com/github/biomejs/intellijbiome/BiomeStdinRunner.kt
Original file line number Diff line number Diff line change
@@ -1,89 +1,64 @@
package com.github.biomejs.intellijbiome

import com.github.biomejs.intellijbiome.extensions.isSuccess
import com.github.biomejs.intellijbiome.extensions.runBiomeCLI
import com.github.biomejs.intellijbiome.extensions.runProcessFuture
import com.github.biomejs.intellijbiome.settings.BiomeSettings
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.openapi.progress.util.ProgressIndicatorUtils
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.SmartList
import com.jetbrains.rd.util.EnumSet
import java.io.File
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
import kotlin.time.Duration
import kotlinx.coroutines.future.await
import java.util.EnumSet

class BiomeStdinRunner(private val project: Project) : BiomeRunner {
class BiomeStdinRunner(project: Project) : BiomeRunner {
private val biomePackage = BiomePackage(project)
private val settings = BiomeSettings.getInstance(project)
private val features = settings.getEnabledFeatures()
private val targetRunBuilder = BiomeTargetRunBuilder(project)

override fun check(request: BiomeRunner.Request, features: EnumSet<Feature>, biomePackage: BiomePackage): BiomeRunner.Response {
val commandLine = createCommandLine(request.virtualFile, "check", getCheckFlags(features, biomePackage))
override suspend fun check(request: BiomeRunner.Request): BiomeRunner.Response {
val file = request.virtualFile
val timeout = request.timeout
val failureMessage = BiomeBundle.message(
"biome.failed.to.run.biome.check.with.features",
features.joinToString(prefix = "(", postfix = ")") { it -> it.toString().lowercase() },
file.name
)
val future = startTheFuture(
failureMessage, timeout
)

commandLine.runProcessFuture().thenAccept { result ->
if (result.processEvent.isSuccess) {
future.complete(BiomeRunner.Response.Success(result.processOutput.stdout))
} else {
future.complete(
BiomeRunner.Response.Failure(
failureMessage,
result.processOutput.stderr, result.processEvent.exitCode
)
)
}
}

return ProgressIndicatorUtils.awaitWithCheckCanceled(future)
}

override fun createCommandLine(file: VirtualFile, action: String, args: List<String>): GeneralCommandLine {
val configPath = biomePackage.configPath(file)
val exePath = biomePackage.binaryPath(configPath, false)
val params = SmartList(action, "--stdin-file-path", file.path)
params.addAll(args)
?: throw ExecutionException(BiomeBundle.message("biome.language.server.not.found"))

val params = SmartList("check", "--stdin-file-path", file.path)
if (!configPath.isNullOrEmpty()) {
params.add("--config-path")
params.add(configPath)
}
params.addAll(getCheckFlags(features, biomePackage))

if (exePath.isNullOrEmpty()) {
throw ExecutionException(BiomeBundle.message("biome.language.server.not.found"))
}

return GeneralCommandLine().runBiomeCLI(project, exePath).apply {
withInput(File(file.path))
withWorkDirectory(configPath)
val processHandler = targetRunBuilder.getBuilder(exePath).apply {
if (!configPath.isNullOrEmpty()) {
setWorkingDirectory(configPath)
}
addParameters(params)
}
}
setInputFile(file)
}.build()

val result = processHandler.runProcessFuture().await()

val processOutput = result.processOutput
val stdout = processOutput.stdout.trim()
val stderr = processOutput.stderr.trim()

private fun startTheFuture(
timeoutMessage: String,
timeout: Duration
): CompletableFuture<BiomeRunner.Response> {
val future = CompletableFuture<BiomeRunner.Response>()
.completeOnTimeout(
BiomeRunner.Response.Failure(
timeoutMessage, "Timeout exceeded", null
),
timeout.inWholeMilliseconds, TimeUnit.MILLISECONDS
)
return future
if (result.processEvent.isSuccess) {
return BiomeRunner.Response.Success(stdout)
} else {
if (processOutput.isTimeout) {
return BiomeRunner.Response.Failure(BiomeBundle.message("biome.failed.to.run.biome.check.with.features",
features.joinToString(prefix = "(", postfix = ")") { it -> it.toString().lowercase() },
file.name), "Timeout exceeded", null)
}

return BiomeRunner.Response.Failure(BiomeBundle.message("biome.failed.to.run.biome.check.with.features",
features.joinToString(prefix = "(", postfix = ")") { it -> it.toString().lowercase() },
file.name), stderr, processOutput.exitCode)
}
}

fun getCheckFlags(features: EnumSet<Feature>, biomePackage: BiomePackage): List<String> {
suspend fun getCheckFlags(features: EnumSet<Feature>,
biomePackage: BiomePackage): List<String> {
val args = SmartList<String>()

if (features.isEmpty()) return args
Expand Down
34 changes: 34 additions & 0 deletions src/main/kotlin/com/github/biomejs/intellijbiome/BiomeTargetRun.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.github.biomejs.intellijbiome

import com.github.biomejs.intellijbiome.extensions.isNodeScript
import com.intellij.execution.ExecutionException
import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterManager
import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter
import com.intellij.javascript.nodejs.interpreter.wsl.WslNodeInterpreter
import com.intellij.lang.javascript.JavaScriptBundle
import com.intellij.openapi.project.Project
import java.io.File

class BiomeTargetRunBuilder(val project: Project) {
fun getBuilder(
executable: String,
): ProcessCommandBuilder {
if (executable.isEmpty()) {
throw ExecutionException(BiomeBundle.message("biome.language.server.not.found"))
}

val executableFile = File(executable)

val builder: ProcessCommandBuilder = if (executableFile.isFile && executableFile.isNodeScript()) {
val interpreter = NodeJsInterpreterManager.getInstance(project).interpreter
if (interpreter !is NodeJsLocalInterpreter && interpreter !is WslNodeInterpreter) {
throw ExecutionException(JavaScriptBundle.message("lsp.interpreter.error"))
}
NodeProcessCommandBuilder(project, interpreter)
} else {
GeneralProcessCommandBuilder()
}

return builder.setExecutable(executable).setWorkingDirectory(project.basePath).setCharset(Charsets.UTF_8)
}
}
Loading
Loading