Skip to content

Commit

Permalink
fix: infinite indexing / intellisense blocked (#102)
Browse files Browse the repository at this point in the history
* chore: Refactor Biome CLI integration and streamline process handling

Removed obsolete action, listener, and extension files and refactored the Biome CLI processing and document management to improve efficiency and maintainability. Introduced new process command builder classes for better process handling, and replaced Kotlin coroutines for non-blocking execution and simplified error handling. Updated settings and actions-on-save to align with the new architecture.
  • Loading branch information
denbezrukov authored Dec 10, 2024
1 parent 43ad101 commit 2bfccaf
Show file tree
Hide file tree
Showing 25 changed files with 437 additions and 454 deletions.
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))

## 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

0 comments on commit 2bfccaf

Please sign in to comment.