From 7fd7573553b5362fdb3f7f9227ee02c5e43a1b3b Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Sun, 19 Jan 2025 18:59:45 +0200 Subject: [PATCH] feat: Refactor and replace BiomeRunner with BiomeServerService (#124) Removed BiomeRunner implementations and introduced BiomeServerService to handle formatting, safe fixes, and import sorting using LSP features. Added new actions for safe fixes and import sorting, and updated settings to include extension lists. Minor refactoring and formatting adjustments across various files. --- .../biomejs/intellijbiome/BiomeBundle.kt | 6 +- .../intellijbiome/BiomeConfigIconProvider.kt | 3 +- .../biomejs/intellijbiome/BiomeRunner.kt | 41 --- .../biomejs/intellijbiome/BiomeStdinRunner.kt | 94 ------- .../GeneralProcessCommandBuilder.kt | 3 +- .../NodeProcessCommandBuilder.kt | 2 +- .../actions/BiomeApplySafeFixesAction.kt | 62 +++++ .../actions/BiomeCheckOnSaveAction.kt | 43 +++- .../intellijbiome/actions/BiomeCheckRunner.kt | 120 --------- .../actions/BiomeSortImportAction.kt | 63 +++++ .../extensions/CapturingProcessAdapterExt.kt | 5 +- .../listeners/BiomeConfigListener.kt | 3 +- .../lsp/BiomeLspServerSupportProvider.kt | 18 +- .../services/BiomeServerService.kt | 140 +++++++++++ .../intellijbiome/settings/BiomeActionInfo.kt | 5 +- .../settings/BiomeConfigurable.kt | 237 +++++++----------- .../BiomeOnSaveApplySafeFixesActionInfo.kt | 30 +-- .../settings/BiomeOnSaveFormatActionInfo.kt | 20 +- .../settings/BiomeOnSaveInfoProvider.kt | 14 +- ....kt => BiomeOnSaveSortImportActionInfo.kt} | 29 +-- .../intellijbiome/settings/BiomeSettings.kt | 40 +-- .../settings/BiomeSettingsState.kt | 9 +- src/main/resources/META-INF/plugin.xml | 21 ++ .../resources/messages/BiomeBundle.properties | 28 ++- .../biomejs/intellijbiome/pages/Editor.kt | 3 +- .../biomejs/intellijbiome/pages/IdeaFrame.kt | 6 +- .../biomejs/intellijbiome/pages/StatusBar.kt | 3 +- .../intellijbiome/pages/WelcomeFrame.kt | 3 +- .../utils/RemoteRobotExtension.kt | 14 +- 29 files changed, 554 insertions(+), 511 deletions(-) delete mode 100644 src/main/kotlin/com/github/biomejs/intellijbiome/BiomeRunner.kt delete mode 100644 src/main/kotlin/com/github/biomejs/intellijbiome/BiomeStdinRunner.kt create mode 100644 src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeApplySafeFixesAction.kt delete mode 100644 src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeCheckRunner.kt create mode 100644 src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeSortImportAction.kt rename src/main/kotlin/com/github/biomejs/intellijbiome/settings/{BiomeOnSaveApplyUnsafeFixesActionInfo.kt => BiomeOnSaveSortImportActionInfo.kt} (65%) diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeBundle.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeBundle.kt index f039b99..6d8a228 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeBundle.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeBundle.kt @@ -11,11 +11,13 @@ object BiomeBundle : DynamicBundle(BUNDLE) { @Suppress("SpreadOperator") @JvmStatic - fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = + fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, + vararg params: Any) = getMessage(key, *params) @Suppress("SpreadOperator", "unused") @JvmStatic - fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = + fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, + vararg params: Any) = getLazyMessage(key, *params) } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeConfigIconProvider.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeConfigIconProvider.kt index 8656871..f296bf3 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeConfigIconProvider.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeConfigIconProvider.kt @@ -6,7 +6,8 @@ import com.intellij.psi.PsiFile import javax.swing.Icon class BiomeConfigIconProvider : IconProvider() { - override fun getIcon(element: PsiElement, flags: Int): Icon? { + override fun getIcon(element: PsiElement, + flags: Int): Icon? { val file = element as? PsiFile ?: return null if (!file.isValid || file.isDirectory) return null val virtualFile = file.virtualFile ?: return null diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeRunner.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeRunner.kt deleted file mode 100644 index 8070f76..0000000 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeRunner.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.github.biomejs.intellijbiome - -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 kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds - -enum class Feature { - Format, - SafeFixes, - UnsafeFixes -} - -interface BiomeRunner { - companion object { - val DEFAULT_TIMEOUT: Duration = 30000.milliseconds - } - - suspend fun check(request: Request): Response - - data class Request( - val document: Document, - val virtualFile: VirtualFile, - val timeout: Duration, - @NlsContexts.Command val commandDescription: String - ) - - sealed class Response { - class Success(val code: String) : Response() - - class Failure( - @Nls val title: String, - @NlsSafe val description: String, - val exitCode: Int? - ) : Response() - - } -} diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeStdinRunner.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeStdinRunner.kt deleted file mode 100644 index 3950ee5..0000000 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/BiomeStdinRunner.kt +++ /dev/null @@ -1,94 +0,0 @@ -package com.github.biomejs.intellijbiome - -import com.github.biomejs.intellijbiome.extensions.isSuccess -import com.github.biomejs.intellijbiome.extensions.runProcessFuture -import com.github.biomejs.intellijbiome.settings.BiomeSettings -import com.intellij.execution.ExecutionException -import com.intellij.openapi.project.Project -import com.intellij.util.SmartList -import kotlinx.coroutines.future.await -import java.util.EnumSet - -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 suspend fun check(request: BiomeRunner.Request): BiomeRunner.Response { - val file = request.virtualFile - val configPath = biomePackage.configPath(file) - val exePath = biomePackage.binaryPath(configPath, false) - ?: 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)) - - val processHandler = targetRunBuilder.getBuilder(exePath).apply { - if (!configPath.isNullOrEmpty()) { - setWorkingDirectory(configPath) - } - addParameters(params) - setInputFile(file) - }.build() - - val result = runProcessFuture(processHandler).await() - - val processOutput = result.processOutput - val stdout = processOutput.stdout - val stderr = processOutput.stderr.trim() - - 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) - } - } - - suspend fun getCheckFlags(features: EnumSet, - biomePackage: BiomePackage): List { - val args = SmartList() - - if (features.isEmpty()) return args - - if (features.contains(Feature.Format)) { - args.add("--formatter-enabled=true") - } else { - args.add("--formatter-enabled=false") - } - - if (!features.contains(Feature.SafeFixes) && !features.contains(Feature.UnsafeFixes)) { - args.add("--linter-enabled=false") - } - - val version = biomePackage.versionNumber() - if (version === null || version.isEmpty() || biomePackage.compareVersion(version, "1.8.0") >= 0) { - args.add("--write") - if (features.contains(Feature.UnsafeFixes)) { - args.add("--unsafe") - } - } else { - if (features.contains(Feature.UnsafeFixes)) { - args.add("--apply-unsafe") - } else { - args.add("--apply") - } - } - - args.add("--skip-errors") - - return args - } -} diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/GeneralProcessCommandBuilder.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/GeneralProcessCommandBuilder.kt index 9dfe3be..6dc71cd 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/GeneralProcessCommandBuilder.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/GeneralProcessCommandBuilder.kt @@ -11,7 +11,8 @@ import java.nio.charset.Charset import kotlin.io.path.Path class GeneralProcessCommandBuilder : ProcessCommandBuilder { - private val command = GeneralCommandLine().withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) + private val command = + GeneralCommandLine().withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) private var executable: String? = null private var workingDir: String? = null private var inputFile: VirtualFile? = null diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/NodeProcessCommandBuilder.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/NodeProcessCommandBuilder.kt index d1a6e00..e1420b6 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/NodeProcessCommandBuilder.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/NodeProcessCommandBuilder.kt @@ -4,9 +4,9 @@ import com.intellij.execution.ExecutionException import com.intellij.execution.process.OSProcessHandler import com.intellij.execution.target.value.TargetValue import com.intellij.javascript.nodejs.execution.NodeTargetRun -import com.intellij.openapi.project.Project import com.intellij.javascript.nodejs.execution.NodeTargetRunOptions.Companion.of import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreter +import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import java.nio.charset.Charset diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeApplySafeFixesAction.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeApplySafeFixesAction.kt new file mode 100644 index 0000000..4f71bda --- /dev/null +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeApplySafeFixesAction.kt @@ -0,0 +1,62 @@ +package com.github.biomejs.intellijbiome.actions + +import com.github.biomejs.intellijbiome.BiomeBundle +import com.github.biomejs.intellijbiome.BiomeIcons +import com.github.biomejs.intellijbiome.services.BiomeServerService +import com.github.biomejs.intellijbiome.services.BiomeServerService.Feature +import com.github.biomejs.intellijbiome.settings.BiomeConfigurable +import com.github.biomejs.intellijbiome.settings.BiomeSettings +import com.intellij.notification.NotificationAction +import com.intellij.notification.NotificationGroupManager +import com.intellij.notification.NotificationType +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.options.ShowSettingsUtil +import com.intellij.openapi.project.DumbAware +import com.intellij.platform.ide.progress.runWithModalProgressBlocking +import kotlinx.coroutines.withTimeout + +class BiomeApplySafeFixesAction : AnAction(), DumbAware { + init { + templatePresentation.icon = BiomeIcons.BiomeIcon + } + + override fun actionPerformed(event: AnActionEvent) { + val project = event.project ?: return + val editor = event.getData(CommonDataKeys.EDITOR) ?: return + + val notificationGroup = NotificationGroupManager.getInstance().getNotificationGroup("Biome") + + val settings = BiomeSettings.getInstance(project) + val manager = FileDocumentManager.getInstance() + val virtualFile = manager.getFile(editor.document) ?: return + + if (!settings.fileSupported(virtualFile)) { + notificationGroup.createNotification(title = BiomeBundle.message("biome.file.not.supported.title"), + content = BiomeBundle.message("biome.file.not.supported.description", virtualFile.name), + type = NotificationType.WARNING) + .addAction(NotificationAction.createSimple(BiomeBundle.message("biome.configure.extensions.link")) { + ShowSettingsUtil.getInstance().showSettingsDialog(project, BiomeConfigurable::class.java) + }).notify(project) + return + } + + runWithModalProgressBlocking(project, + BiomeBundle.message("biome.run.biome.check.with.features", Feature.ApplySafeFixes.toString())) { + try { + withTimeout(5_000) { + BiomeServerService.getInstance(project).applySafeFixes(editor.document) + } + notificationGroup.createNotification(title = BiomeBundle.message("biome.apply.safe.fixes.success.label"), + content = BiomeBundle.message("biome.apply.safe.fixes.success.description"), + type = NotificationType.INFORMATION).notify(project) + } catch (e: Exception) { + notificationGroup.createNotification(title = BiomeBundle.message("biome.apply.safe.fixes.failure.label"), + content = BiomeBundle.message("biome.apply.safe.fixes.failure.description", e.message.toString()), + type = NotificationType.ERROR).notify(project) + } + } + } +} diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeCheckOnSaveAction.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeCheckOnSaveAction.kt index d91816b..77daf7a 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeCheckOnSaveAction.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeCheckOnSaveAction.kt @@ -1,18 +1,51 @@ package com.github.biomejs.intellijbiome.actions +import com.github.biomejs.intellijbiome.BiomeBundle +import com.github.biomejs.intellijbiome.services.BiomeServerService import com.github.biomejs.intellijbiome.settings.BiomeSettings import com.intellij.ide.actionsOnSave.impl.ActionsOnSaveFileDocumentManagerListener +import com.intellij.notification.NotificationGroupManager +import com.intellij.notification.NotificationType import com.intellij.openapi.editor.Document +import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project +import com.intellij.platform.ide.progress.runWithModalProgressBlocking +import kotlinx.coroutines.withTimeout class BiomeCheckOnSaveAction : ActionsOnSaveFileDocumentManagerListener.ActionOnSave() { override fun isEnabledForProject(project: Project): Boolean { - val settings = BiomeSettings.getInstance(project) - - return settings.applySafeFixesOnSave || settings.applyUnsafeFixesOnSave || settings.formatOnSave + return !BiomeSettings.getInstance(project).getEnabledFeatures().isEmpty() } - override fun processDocuments(project: Project, documents: Array) { - BiomeCheckRunner(project).run(documents) + override fun processDocuments(project: Project, + documents: Array) { + val features = BiomeSettings.getInstance(project).getEnabledFeatures() + val featuresInfo = features.joinToString(prefix = "(", postfix = ")") { it.toString().lowercase() } + val notificationGroup = NotificationGroupManager.getInstance().getNotificationGroup("Biome") + + runWithModalProgressBlocking(project, + BiomeBundle.message("biome.run.biome.check.with.features", featuresInfo)) { + try { + withTimeout(5_000) { + documents.filter { + val settings = BiomeSettings.getInstance(project) + val manager = FileDocumentManager.getInstance() + val virtualFile = manager.getFile(it) ?: return@filter false + return@filter settings.fileSupported(virtualFile) + }.forEach { + BiomeServerService.getInstance(project).executeFeatures(it, features) + } + } + } catch (e: Exception) { + notificationGroup.createNotification( + title = BiomeBundle.message("biome.apply.feature.on.save.failure.label", featuresInfo), + content = BiomeBundle.message( + "biome.apply.feature.on.save.failure.description", + featuresInfo, + e.message.toString() + ), + type = NotificationType.ERROR).notify(project) + } + } } } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeCheckRunner.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeCheckRunner.kt deleted file mode 100644 index 025d77a..0000000 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeCheckRunner.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.github.biomejs.intellijbiome.actions - -import com.github.biomejs.intellijbiome.BiomeBundle -import com.github.biomejs.intellijbiome.BiomeRunner -import com.github.biomejs.intellijbiome.BiomeStdinRunner -import com.github.biomejs.intellijbiome.settings.BiomeSettings -import com.intellij.codeStyle.AbstractConvertLineSeparatorsAction -import com.intellij.lang.javascript.linter.GlobPatternUtil -import com.intellij.openapi.application.readAction -import com.intellij.openapi.command.writeCommandAction -import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.editor.Document -import com.intellij.openapi.fileEditor.FileDocumentManager -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.text.StringUtil -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.platform.ide.progress.runWithModalProgressBlocking -import com.intellij.util.LineSeparator -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.withTimeout - -class BiomeCheckRunner( - val project: Project, -) { - private val LOG = thisLogger() - private val settings = BiomeSettings.getInstance(project) - private val description: String by lazy { - BiomeBundle.message("biome.run.biome.check.with.features", - settings.getEnabledFeatures().joinToString(prefix = "(", postfix = ")") { it -> it.toString().lowercase() }) - } - - fun run(documents: Array) { - runWithModalProgressBlocking( - project, - BiomeBundle.message( - "biome.run.biome.check.with.features", - settings.getEnabledFeatures().joinToString(prefix = "(", postfix = ")") { it -> it.toString().lowercase() } - ) - ) { - withTimeout(5_000) { - val jobs = documents.map { - async(Dispatchers.IO) { - processDocument(it) - } - } - jobs.awaitAll() - } - } - } - - suspend fun processDocument(document: Document) { - val request = getRequest(document) ?: return - val runner = BiomeStdinRunner(project) - val response = runner.check(request) - applyChanges(request, response) - } - - private suspend fun getRequest(document: Document): BiomeRunner.Request? { - return readAction { - val manager = FileDocumentManager.getInstance() - val file = manager.getFile(document) ?: return@readAction null - - if (!GlobPatternUtil.isFileMatchingGlobPattern(project, settings.filePattern, file)) { - return@readAction null - } - - val request = BiomeRunner.Request(document, file, BiomeRunner.DEFAULT_TIMEOUT, description) - return@readAction request - } - } - - private suspend fun applyChanges( - request: BiomeRunner.Request, - response: BiomeRunner.Response, - ) { - when (response) { - is BiomeRunner.Response.Success -> { - val text = response.code - val lineSeparator = StringUtil.detectSeparators(text) - // internally we keep newlines as \n - val normalizedText = StringUtil.convertLineSeparators(text) - writeCommandAction(project, request.commandDescription) { - if (!StringUtil.equals(request.document.charsSequence, normalizedText)) { - request.document.setText(normalizedText) - } - - setDetectedLineSeparator(project, request.virtualFile, lineSeparator) - } - } - - is BiomeRunner.Response.Failure -> { - LOG.error("${response.title} - ${response.description}") - } - } - } - - /** - * [Taken from the JetBrains Prettier Plugin](https://github.com/JetBrains/intellij-plugins/blob/5673be79dd9e0fff7ed98e58a7d071a5a5f96d87/prettierJS/src/com/intellij/prettierjs/ReformatWithPrettierAction.java#L486) - * [Apache License 2.0](https://github.com/JetBrains/intellij-plugins/blob/5673be79dd9e0fff7ed98e58a7d071a5a5f96d87/prettierJS/LICENSE.TXT) - * - * @return true if the line separators were updated - */ - private fun setDetectedLineSeparator( - project: Project, - vFile: VirtualFile, - newSeparator: LineSeparator?, - ): Boolean { - if (newSeparator != null) { - val newSeparatorString: String = newSeparator.separatorString - - if (!StringUtil.equals(vFile.detectedLineSeparator, newSeparatorString)) { - AbstractConvertLineSeparatorsAction.changeLineSeparators(project, vFile, newSeparatorString) - return true - } - } - return false - } -} diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeSortImportAction.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeSortImportAction.kt new file mode 100644 index 0000000..fd6c721 --- /dev/null +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/actions/BiomeSortImportAction.kt @@ -0,0 +1,63 @@ +package com.github.biomejs.intellijbiome.actions + +import com.github.biomejs.intellijbiome.BiomeBundle +import com.github.biomejs.intellijbiome.BiomeIcons +import com.github.biomejs.intellijbiome.services.BiomeServerService +import com.github.biomejs.intellijbiome.services.BiomeServerService.Feature +import com.github.biomejs.intellijbiome.settings.BiomeConfigurable +import com.github.biomejs.intellijbiome.settings.BiomeSettings +import com.intellij.notification.NotificationAction +import com.intellij.notification.NotificationGroupManager +import com.intellij.notification.NotificationType +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.options.ShowSettingsUtil +import com.intellij.openapi.project.DumbAware +import com.intellij.platform.ide.progress.runWithModalProgressBlocking +import kotlinx.coroutines.withTimeout + +class BiomeSortImportAction : AnAction(), DumbAware { + init { + templatePresentation.icon = BiomeIcons.BiomeIcon + } + + override fun actionPerformed(event: AnActionEvent) { + val project = event.project ?: return + val editor = event.getData(CommonDataKeys.EDITOR) ?: return + + val notificationGroup = NotificationGroupManager.getInstance().getNotificationGroup("Biome") + + val settings = BiomeSettings.getInstance(project) + val manager = FileDocumentManager.getInstance() + val virtualFile = manager.getFile(editor.document) ?: return + + if (!settings.fileSupported(virtualFile)) { + notificationGroup.createNotification(title = BiomeBundle.message("biome.file.not.supported.title"), + content = BiomeBundle.message("biome.file.not.supported.description", virtualFile.name), + type = NotificationType.WARNING) + .addAction(NotificationAction.createSimple(BiomeBundle.message("biome.configure.extensions.link")) { + ShowSettingsUtil.getInstance().showSettingsDialog(project, BiomeConfigurable::class.java) + }).notify(project) + return + } + + runWithModalProgressBlocking(project, + BiomeBundle.message("biome.run.biome.check.with.features", Feature.SortImports.toString())) { + try { + withTimeout(5_000) { + BiomeServerService.getInstance(project).sortImports(editor.document) + } + notificationGroup.createNotification(title = BiomeBundle.message("biome.apply.sort.import.success.label"), + content = BiomeBundle.message("biome.apply.sort.import.success.description"), + type = NotificationType.INFORMATION).notify(project) + } catch (e: Exception) { + notificationGroup.createNotification(title = BiomeBundle.message("biome.apply.sort.import.failure.label"), + content = BiomeBundle.message("biome.apply.sort.import.failure.description", e.message.toString()), + type = NotificationType.ERROR).notify(project) + } + } + } +} + diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/extensions/CapturingProcessAdapterExt.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/extensions/CapturingProcessAdapterExt.kt index 73b346b..083318e 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/extensions/CapturingProcessAdapterExt.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/extensions/CapturingProcessAdapterExt.kt @@ -6,16 +6,15 @@ import com.intellij.execution.process.ProcessEvent import com.intellij.execution.process.ProcessOutput import java.util.concurrent.CompletableFuture -class ProcessResult(val processEvent: ProcessEvent, val processOutput: ProcessOutput) +class ProcessResult( val processOutput: ProcessOutput) -val ProcessEvent.isSuccess: Boolean get() = exitCode == 0 fun runProcessFuture(handler: OSProcessHandler): CompletableFuture { val future = CompletableFuture() handler.addProcessListener(object : CapturingProcessAdapter() { override fun processTerminated(event: ProcessEvent) { - future.complete(ProcessResult(event, output)) + future.complete(ProcessResult(output)) } }) diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/listeners/BiomeConfigListener.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/listeners/BiomeConfigListener.kt index 2c225fb..7531d69 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/listeners/BiomeConfigListener.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/listeners/BiomeConfigListener.kt @@ -12,7 +12,8 @@ class BiomeConfigListener(val project: Project) : BulkFileListener { super.after(events) events.forEach { if (it.file?.name?.contains(BiomePackage.configName) == true && BiomePackage.configValidExtensions.contains( - it.file?.extension)) { + it.file?.extension) + ) { val biomeServerService = project.service() biomeServerService.restartBiomeServer() } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/lsp/BiomeLspServerSupportProvider.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/lsp/BiomeLspServerSupportProvider.kt index 055beaf..f51ab0a 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/lsp/BiomeLspServerSupportProvider.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/lsp/BiomeLspServerSupportProvider.kt @@ -11,13 +11,15 @@ import com.intellij.execution.process.OSProcessHandler import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -import com.intellij.platform.lsp.api.* +import com.intellij.platform.lsp.api.LspServer +import com.intellij.platform.lsp.api.LspServerSupportProvider +import com.intellij.platform.lsp.api.ProjectWideLspServerDescriptor import com.intellij.platform.lsp.api.customization.LspFormattingSupport import com.intellij.platform.lsp.api.lsWidget.LspServerWidgetItem import com.intellij.util.SmartList -@Suppress("UnstableApiUsage") -class BiomeLspServerSupportProvider : LspServerSupportProvider { + +@Suppress("UnstableApiUsage") class BiomeLspServerSupportProvider : LspServerSupportProvider { override fun fileOpened( project: Project, file: VirtualFile, @@ -40,15 +42,13 @@ class BiomeLspServerSupportProvider : LspServerSupportProvider { LspServerWidgetItem(lspServer, currentFile, BiomeIcons.BiomeIcon, BiomeConfigurable::class.java) } -@Suppress("UnstableApiUsage") -private class BiomeLspServerDescriptor(project: Project, +@Suppress("UnstableApiUsage") private class BiomeLspServerDescriptor(project: Project, val executable: String, - val configPath: String?) : ProjectWideLspServerDescriptor( - project, "Biome") { + val configPath: String?) : ProjectWideLspServerDescriptor(project, "Biome") { private val targetRunBuilder = BiomeTargetRunBuilder(project) override fun isSupportedFile(file: VirtualFile): Boolean { - return BiomeSettings.getInstance(project).fileSupported(project, file) + return BiomeSettings.getInstance(project).fileSupported(file) } override fun createCommandLine(): GeneralCommandLine { @@ -80,7 +80,7 @@ private class BiomeLspServerDescriptor(project: Project, serverExplicitlyWantsToFormatThisFile: Boolean, ): Boolean { val settings = BiomeSettings.getInstance(project) - return settings.enableLspFormat && BiomeSettings.getInstance(project).fileSupported(project, file) + return settings.enableLspFormat } } } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/services/BiomeServerService.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/services/BiomeServerService.kt index 65dabac..aed9ce5 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/services/BiomeServerService.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/services/BiomeServerService.kt @@ -1,24 +1,64 @@ +@file:Suppress("UnstableApiUsage") + package com.github.biomejs.intellijbiome.services import com.github.biomejs.intellijbiome.BiomeBundle import com.github.biomejs.intellijbiome.listeners.BiomeEditorPanelListener import com.github.biomejs.intellijbiome.lsp.BiomeLspServerSupportProvider +import com.intellij.codeStyle.AbstractConvertLineSeparatorsAction import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationType +import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.components.Service +import com.intellij.openapi.editor.Document +import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.project.Project +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.VirtualFile import com.intellij.platform.lsp.api.LspServerManager +import com.intellij.platform.lsp.api.customization.LspIntentionAction +import com.intellij.platform.lsp.impl.LspServerImpl +import com.intellij.platform.lsp.util.getLsp4jRange +import com.intellij.util.LineSeparator +import org.eclipse.lsp4j.* +import java.util.* @Service(Service.Level.PROJECT) class BiomeServerService(private val project: Project) { private val editorPanelListener: BiomeEditorPanelListener + private val groupId = "Biome" + + enum class Feature { + Format, ApplySafeFixes, SortImports + } + init { editorPanelListener = BiomeEditorPanelListener(project) project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, editorPanelListener) } + companion object { + fun getInstance(project: Project): BiomeServerService = project.getService(BiomeServerService::class.java) + } + + fun getServer(): LspServerImpl? = + LspServerManager.getInstance(project).getServersForProvider(BiomeLspServerSupportProvider::class.java) + .firstOrNull().let { it as? LspServerImpl } + + suspend fun applySafeFixes(document: Document) { + executeFeatures(document, EnumSet.of(Feature.ApplySafeFixes)) + } + + suspend fun sortImports(document: Document) { + executeFeatures(document, EnumSet.of(Feature.SortImports)) + } + + suspend fun format(document: Document) { + executeFeatures(document, EnumSet.of(Feature.Format)) + } + fun getCurrentConfigPath(): String? { return editorPanelListener.getCurrentConfigPath() } @@ -31,6 +71,106 @@ class BiomeServerService(private val project: Project) { LspServerManager.getInstance(project).stopServers(BiomeLspServerSupportProvider::class.java) } + suspend fun executeFeatures(document: Document, + features: EnumSet) { + val server = getServer() ?: return + val manager = FileDocumentManager.getInstance() + val file = manager.getFile(document) ?: return + val commandName = BiomeBundle.message("biome.run.biome.check.with.features", + features.joinToString(prefix = "(", postfix = ")") { it -> it.toString().lowercase() }) + + val formattingActions = mutableListOf() + + if (features.contains(Feature.ApplySafeFixes) || features.contains(Feature.SortImports)) { + val result = mutableListOf() + + if (features.contains(Feature.ApplySafeFixes)) { + val codeActionParams = CodeActionParams(server.getDocumentIdentifier(file), + getLsp4jRange(document, 0, document.textLength), + CodeActionContext().apply { + diagnostics = emptyList() + only = listOf("quickfix.biome") + triggerKind = CodeActionTriggerKind.Automatic + }) + + val codeActionResults = server.sendRequest { it.textDocumentService.codeAction(codeActionParams) } + codeActionResults?.forEach { + if (it.isRight) { + result.add(LspIntentionAction(server, it.right)) + } + } + } + + if (features.contains(Feature.SortImports)) { + val codeActionParams = CodeActionParams(server.getDocumentIdentifier(file), + getLsp4jRange(document, 0, document.textLength), + CodeActionContext().apply { + diagnostics = emptyList() + only = listOf("source.organizeImports.biome") + triggerKind = CodeActionTriggerKind.Automatic + }) + + val codeActionResults = server.sendRequest { it.textDocumentService.codeAction(codeActionParams) } + codeActionResults?.forEach { + if (it.isRight) { + result.add(LspIntentionAction(server, it.right)) + } + } + } + + WriteCommandAction.runWriteCommandAction(project, commandName, groupId, { + result.forEach { + if (it.isAvailable()) { + it.invoke(null) + } + } + }) + } + + if (features.contains(Feature.Format)) { + val formattingParams = + DocumentFormattingParams(server.getDocumentIdentifier(file), FormattingOptions().apply { + tabSize = 2 // Biome doesn't use this information + isInsertSpaces = false // Biome doesn't use this information + }) + + val formattingResults = server.sendRequest { it.textDocumentService.formatting(formattingParams) } + + formattingResults?.forEach { + val lineSeparator = StringUtil.detectSeparators(it.newText) + val normalizedText = StringUtil.convertLineSeparators(it.newText) + + formattingActions.add(Runnable { + if (!StringUtil.equals(document.charsSequence, normalizedText)) { + document.setText(normalizedText) + } + + setDetectedLineSeparator(project, file, lineSeparator) + }) + } + + WriteCommandAction.runWriteCommandAction(project, commandName, groupId, { + formattingActions.forEach { it.run() } + }) + } + } + + private fun setDetectedLineSeparator( + project: Project, + vFile: VirtualFile, + newSeparator: LineSeparator?, + ): Boolean { + if (newSeparator != null) { + val newSeparatorString: String = newSeparator.separatorString + + if (!StringUtil.equals(vFile.detectedLineSeparator, newSeparatorString)) { + AbstractConvertLineSeparatorsAction.changeLineSeparators(project, vFile, newSeparatorString) + return true + } + } + return false + } + fun notifyRestart() { NotificationGroupManager.getInstance() .getNotificationGroup("Biome") diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeActionInfo.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeActionInfo.kt index 42e02ae..023ba67 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeActionInfo.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeActionInfo.kt @@ -8,7 +8,6 @@ class ActionInfo { companion object { fun defaultComment( version: String?, - filePattern: String, isActionOnSaveEnabled: Boolean ): ActionOnSaveComment? { if (version == null) { @@ -22,11 +21,11 @@ class ActionInfo { BiomeBundle.message( "biome.run.on.save.version.and.files.pattern", shorten(version, 15), - shorten(filePattern, 40) ) ) } - fun shorten(s: String, max: Int) = StringUtil.shortenTextWithEllipsis(s, max, 0, true) + fun shorten(s: String, + max: Int) = StringUtil.shortenTextWithEllipsis(s, max, 0, true) } } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeConfigurable.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeConfigurable.kt index 1fd97b6..52efecc 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeConfigurable.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeConfigurable.kt @@ -5,11 +5,8 @@ import com.github.biomejs.intellijbiome.BiomePackage import com.github.biomejs.intellijbiome.services.BiomeServerService import com.intellij.ide.actionsOnSave.ActionsOnSaveConfigurable import com.intellij.lang.javascript.JavaScriptBundle -import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationNamesInfo import com.intellij.openapi.components.service -import com.intellij.openapi.observable.properties.ObservableMutableProperty -import com.intellij.openapi.observable.util.whenItemSelected import com.intellij.openapi.options.BoundSearchableConfigurable import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogPanel @@ -17,7 +14,6 @@ import com.intellij.openapi.ui.TextFieldWithBrowseButton import com.intellij.openapi.ui.ValidationInfo import com.intellij.openapi.vfs.VirtualFile import com.intellij.ui.ContextHelpLabel -import com.intellij.ui.components.JBRadioButton import com.intellij.ui.components.JBTextField import com.intellij.ui.dsl.builder.* import com.intellij.ui.layout.ValidationInfoBuilder @@ -25,31 +21,26 @@ import com.intellij.ui.layout.not import com.intellij.ui.layout.selected import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil +import java.awt.event.ItemEvent import java.io.File -import java.nio.file.FileSystems -import java.util.regex.PatternSyntaxException import javax.swing.JCheckBox import javax.swing.JRadioButton -import javax.swing.text.JTextComponent +import javax.swing.event.HyperlinkEvent private const val HELP_TOPIC = "reference.settings.biome" class BiomeConfigurable(internal val project: Project) : - BoundSearchableConfigurable( - BiomeBundle.message("biome.settings.name"), - HELP_TOPIC, - CONFIGURABLE_ID - ) { + BoundSearchableConfigurable(BiomeBundle.message("biome.settings.name"), HELP_TOPIC, CONFIGURABLE_ID) { lateinit var runFormatOnSaveCheckBox: JCheckBox lateinit var enableLspFormatCheckBox: JCheckBox lateinit var runSafeFixesOnSaveCheckBox: JCheckBox - lateinit var runUnsafeFixesOnSaveCheckBox: JCheckBox - - lateinit var runForFilesField: JBTextField + lateinit var sortImportOnSaveCheckBox: JCheckBox lateinit var disabledConfiguration: JRadioButton private lateinit var automaticConfiguration: JRadioButton private lateinit var manualConfiguration: JRadioButton + private lateinit var extensionsField: JBTextField + override fun createPanel(): DialogPanel { val settings: BiomeSettings = BiomeSettings.getInstance(project) val biomeServerService = project.service() @@ -57,37 +48,34 @@ class BiomeConfigurable(internal val project: Project) : // ********************* // Configuration mode row // ********************* - return panel { buttonsGroup { row { disabledConfiguration = - radioButton( - JavaScriptBundle.message( - "settings.javascript.linters.autodetect.disabled", - displayName - ) - ) - .bindSelected(ConfigurationModeProperty(settings, ConfigurationMode.DISABLED)) - .component + radioButton(JavaScriptBundle.message("settings.javascript.linters.autodetect.disabled", + displayName)).bindSelected(ConfigurationModeProperty(settings, + ConfigurationMode.DISABLED)).component.apply { + addItemListener { e -> + if (e.stateChange == ItemEvent.SELECTED) { + runFormatOnSaveCheckBox.isSelected = false + enableLspFormatCheckBox.isSelected = false + runSafeFixesOnSaveCheckBox.isSelected = false + sortImportOnSaveCheckBox.isSelected = false + } + } + } } row { automaticConfiguration = - radioButton( - JavaScriptBundle.message( - "settings.javascript.linters.autodetect.configure.automatically", - displayName - ) - ) - .bindSelected(ConfigurationModeProperty(settings, ConfigurationMode.AUTOMATIC)) - .component - - val detectAutomaticallyHelpText = JavaScriptBundle.message( - "settings.javascript.linters.autodetect.configure.automatically.help.text", - ApplicationNamesInfo.getInstance().fullProductName, - displayName, - "${BiomePackage.configName}.json" - ) + radioButton(JavaScriptBundle.message("settings.javascript.linters.autodetect.configure.automatically", + displayName)).bindSelected(ConfigurationModeProperty(settings, + ConfigurationMode.AUTOMATIC)).component + + val detectAutomaticallyHelpText = + JavaScriptBundle.message("settings.javascript.linters.autodetect.configure.automatically.help.text", + ApplicationNamesInfo.getInstance().fullProductName, + displayName, + "${BiomePackage.configName}.json") val helpLabel = ContextHelpLabel.create(detectAutomaticallyHelpText) helpLabel.border = JBUI.Borders.emptyLeft(UIUtil.DEFAULT_HGAP) @@ -95,14 +83,9 @@ class BiomeConfigurable(internal val project: Project) : } row { manualConfiguration = - radioButton( - JavaScriptBundle.message( - "settings.javascript.linters.autodetect.configure.manually", - displayName - ) - ) - .bindSelected(ConfigurationModeProperty(settings, ConfigurationMode.MANUAL)) - .component + radioButton(JavaScriptBundle.message("settings.javascript.linters.autodetect.configure.manually", + displayName)).bindSelected(ConfigurationModeProperty(settings, + ConfigurationMode.MANUAL)).component } } @@ -111,46 +94,58 @@ class BiomeConfigurable(internal val project: Project) : // ********************* panel { row(BiomeBundle.message("biome.path.executable")) { - textFieldWithBrowseButton(BiomeBundle.message("biome.path.executable")) { fileChosen(it) } - .bindText(settings::executablePath) + textFieldWithBrowseButton(BiomeBundle.message("biome.path.executable")) { fileChosen(it) }.bindText( + settings::executablePath) }.visibleIf(manualConfiguration.selected) row(BiomeBundle.message("biome.config.path.label")) { textFieldWithBrowseButton( BiomeBundle.message("biome.config.path.label"), project, - ) { fileChosen(it) } - .bindText(settings::configPath) - .validationOnInput(validateConfigDir()) + ) { fileChosen(it) }.bindText(settings::configPath).validationOnInput(validateConfigDir()) }.visibleIf(manualConfiguration.selected) } // ********************* - // Format pattern row + // Supported file extensions row // ********************* - row(BiomeBundle.message("biome.run.format.for.files.label")) { - runForFilesField = textField() + row(BiomeBundle.message("biome.supported.extensions.label")) { + extensionsField = textField() .align(AlignX.FILL) - .bind( - { textField -> textField.text.trim() }, - JTextComponent::setText, - MutableProperty({ settings.filePattern }, { settings.filePattern = it }) - ) - .validationOnInput(validateGlob()) + .bindText({ settings.supportedExtensions.joinToString(",") }, { value -> + settings.supportedExtensions = + value.split(",").map { it.trim() }.filter { it.isNotBlank() }.toMutableList() + }) + .validationOnInput { validateExtensions(it) } + .applyToComponent { + font = font.deriveFont(font.size2D - 2f) // Reduce font size by 2 points + } .component + }.enabledIf(!disabledConfiguration.selected) + // Add help text with a "Reset" link below the field + row { + comment(BiomeBundle.message("biome.supported.extensions.comment") + " Reset to Defaults") + .applyToComponent { + addHyperlinkListener { event -> + if (event.eventType == HyperlinkEvent.EventType.ACTIVATED && event.description == "reset") { + extensionsField.text = BiomeSettingsState.DEFAULT_EXTENSION_LIST.joinToString(",") + } + } + } + } + .bottomGap(BottomGap.MEDIUM) + .enabledIf(!disabledConfiguration.selected) + // ********************* // LSP row // ********************* row { - enableLspFormatCheckBox = checkBox(BiomeBundle.message("biome.enable.lsp.format.label")) - .bindSelected(RunOnObservableProperty( - { settings.configurationMode != ConfigurationMode.DISABLED && settings.enableLspFormat }, - { settings.enableLspFormat = it }, - { !disabledConfiguration.isSelected && enableLspFormatCheckBox.isSelected } - )) - .component + enableLspFormatCheckBox = checkBox(BiomeBundle.message("biome.enable.lsp.format.label")).bindSelected( + { settings.configurationMode != ConfigurationMode.DISABLED && settings.enableLspFormat }, + { settings.enableLspFormat = it }, + ).component val helpLabel = ContextHelpLabel.create(BiomeBundle.message("biome.enable.lsp.format.help.label")) helpLabel.border = JBUI.Borders.emptyLeft(UIUtil.DEFAULT_HGAP) @@ -164,13 +159,10 @@ class BiomeConfigurable(internal val project: Project) : // Format on save row // ********************* row { - runFormatOnSaveCheckBox = checkBox(BiomeBundle.message("biome.run.format.on.save.label")) - .bindSelected(RunOnObservableProperty( - { settings.configurationMode != ConfigurationMode.DISABLED && settings.formatOnSave }, - { settings.formatOnSave = it }, - { !disabledConfiguration.isSelected && runFormatOnSaveCheckBox.isSelected } - )) - .component + runFormatOnSaveCheckBox = checkBox(BiomeBundle.message("biome.run.format.on.save.label")).bindSelected( + { settings.configurationMode != ConfigurationMode.DISABLED && settings.formatOnSave }, + { settings.formatOnSave = it }, + ).component val link = ActionsOnSaveConfigurable.createGoToActionsOnSavePageLink() cell(link) @@ -180,13 +172,11 @@ class BiomeConfigurable(internal val project: Project) : // Apply safe fixes on save row // ********************* row { - runSafeFixesOnSaveCheckBox = checkBox(BiomeBundle.message("biome.run.safe.fixes.on.save.label")) - .bindSelected(RunOnObservableProperty( + runSafeFixesOnSaveCheckBox = + checkBox(BiomeBundle.message("biome.run.safe.fixes.on.save.label")).bindSelected( { settings.configurationMode != ConfigurationMode.DISABLED && settings.applySafeFixesOnSave }, { settings.applySafeFixesOnSave = it }, - { !disabledConfiguration.isSelected && runSafeFixesOnSaveCheckBox.isSelected } - )) - .component + ).component val link = ActionsOnSaveConfigurable.createGoToActionsOnSavePageLink() cell(link) @@ -194,16 +184,14 @@ class BiomeConfigurable(internal val project: Project) : // ********************* - // Apply unsafe fixes on save row + // Sort import on save row // ********************* row { - runUnsafeFixesOnSaveCheckBox = checkBox(BiomeBundle.message("biome.run.unsafe.fixes.on.save.label")) - .bindSelected(RunOnObservableProperty( - { settings.configurationMode != ConfigurationMode.DISABLED && settings.applyUnsafeFixesOnSave }, - { settings.applyUnsafeFixesOnSave = it }, - { !disabledConfiguration.isSelected && runUnsafeFixesOnSaveCheckBox.isSelected } - )) - .component + sortImportOnSaveCheckBox = + checkBox(BiomeBundle.message("biome.sort.import.on.save.label")).bindSelected( + { settings.configurationMode != ConfigurationMode.DISABLED && settings.sortImportOnSave }, + { settings.sortImportOnSave = it }, + ).component val link = ActionsOnSaveConfigurable.createGoToActionsOnSavePageLink() cell(link) @@ -214,39 +202,34 @@ class BiomeConfigurable(internal val project: Project) : biomeServerService.notifyRestart() } } - } - private fun validateGlob(): ValidationInfoBuilder.(JBTextField) -> ValidationInfo? = - { - try { - FileSystems.getDefault().getPathMatcher("glob:" + it.text) - null - } - catch (e: PatternSyntaxException) { - ValidationInfo(BiomeBundle.message("biome.invalid.pattern"), it) - } + private fun validateExtensions(field: JBTextField): ValidationInfo? { + val input = field.text + val extensions = input.split(",").map { it.trim() } + + val invalidExtension = extensions.find { !it.matches(Regex("^\\.[a-zA-Z0-9]+$")) } + return if (invalidExtension != null) { + ValidationInfo("Invalid extension: $invalidExtension. Must start with '.' and contain only alphanumeric characters.", + field) + } else { + null } + } - private fun validateConfigDir(): ValidationInfoBuilder.(TextFieldWithBrowseButton) -> ValidationInfo? = - { - val selected = File(it.text) + private fun validateConfigDir(): ValidationInfoBuilder.(TextFieldWithBrowseButton) -> ValidationInfo? = { + val selected = File(it.text) - if (!selected.exists()) { + if (!selected.exists()) { + ValidationInfo(BiomeBundle.message("biome.configuration.file.not.found"), it) + } else { + if (!selected.name.contains(BiomePackage.configName) && BiomePackage.configValidExtensions.contains(selected.extension)) { ValidationInfo(BiomeBundle.message("biome.configuration.file.not.found"), it) - } - else { - if (!selected.name.contains(BiomePackage.configName) && BiomePackage.configValidExtensions.contains( - selected.extension - ) - ) { - ValidationInfo(BiomeBundle.message("biome.configuration.file.not.found"), it) - } - else { - null - } + } else { + null } } + } private fun fileChosen(file: VirtualFile): String { return file.path @@ -256,8 +239,7 @@ class BiomeConfigurable(internal val project: Project) : private val settings: BiomeSettings, private val mode: ConfigurationMode, ) : MutableProperty { - override fun get(): Boolean = - settings.configurationMode == mode + override fun get(): Boolean = settings.configurationMode == mode override fun set(value: Boolean) { if (value) { @@ -266,31 +248,6 @@ class BiomeConfigurable(internal val project: Project) : } } - private inner class RunOnObservableProperty( - private val getter: () -> Boolean, - private val setter: (Boolean) -> Unit, - private val afterConfigModeChangeGetter: () -> Boolean, - ) : ObservableMutableProperty { - override fun set(value: Boolean) { - setter(value) - } - - override fun get(): Boolean = - getter() - - override fun afterChange(parentDisposable: Disposable?, listener: (Boolean) -> Unit) { - fun emitChange(radio: JBRadioButton) { - if (radio.isSelected) { - listener(afterConfigModeChangeGetter()) - } - } - - manualConfiguration.whenItemSelected(parentDisposable, ::emitChange) - automaticConfiguration.whenItemSelected(parentDisposable, ::emitChange) - disabledConfiguration.whenItemSelected(parentDisposable, ::emitChange) - } - } - companion object { const val CONFIGURABLE_ID = "Settings.Biome" } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveApplySafeFixesActionInfo.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveApplySafeFixesActionInfo.kt index fce365f..833acb9 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveApplySafeFixesActionInfo.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveApplySafeFixesActionInfo.kt @@ -8,14 +8,12 @@ import com.intellij.ide.actionsOnSave.ActionOnSaveContext import com.intellij.platform.ide.progress.runWithModalProgressBlocking class BiomeOnSaveApplySafeFixesActionInfo(actionOnSaveContext: ActionOnSaveContext) : - ActionOnSaveBackedByOwnConfigurable( - actionOnSaveContext, + ActionOnSaveBackedByOwnConfigurable(actionOnSaveContext, BiomeConfigurable.CONFIGURABLE_ID, - BiomeConfigurable::class.java - ) { + BiomeConfigurable::class.java) { override fun getActionOnSaveName() = - BiomeBundle.message("biome.run.safe.fixes.on.save.checkbox.on.actions.on.save.page") + BiomeBundle.message("biome.apply.safe.fixes.on.save.checkbox.on.actions.on.save.page") override fun isApplicableAccordingToStoredState(): Boolean = BiomeSettings.getInstance(project).configurationMode != ConfigurationMode.DISABLED @@ -28,30 +26,28 @@ class BiomeOnSaveApplySafeFixesActionInfo(actionOnSaveContext: ActionOnSaveConte override fun isActionOnSaveEnabledAccordingToUiState(configurable: BiomeConfigurable) = configurable.runSafeFixesOnSaveCheckBox.isSelected - override fun setActionOnSaveEnabled(configurable: BiomeConfigurable, enabled: Boolean) { + override fun setActionOnSaveEnabled(configurable: BiomeConfigurable, + enabled: Boolean) { configurable.runSafeFixesOnSaveCheckBox.isSelected = enabled } override fun getCommentAccordingToUiState(configurable: BiomeConfigurable): ActionOnSaveComment? { - if (!isSaveActionApplicable) return ActionOnSaveComment.info(BiomeBundle.message("biome.on.save.comment.disabled")) - - val biomePackage = BiomePackage(project) - val version = runWithModalProgressBlocking(project, BiomeBundle.message("biome.version")) { - biomePackage.versionNumber() - } - return ActionInfo.defaultComment(version, configurable.runForFilesField.text.trim(), isActionOnSaveEnabled) + return comment() } override fun getCommentAccordingToStoredState(): ActionOnSaveComment? { + return comment() + } + + override fun getActionLinks() = listOf(createGoToPageInSettingsLink(BiomeConfigurable.CONFIGURABLE_ID)) + + private fun comment(): ActionOnSaveComment? { if (!isSaveActionApplicable) return ActionOnSaveComment.info(BiomeBundle.message("biome.on.save.comment.disabled")) val biomePackage = BiomePackage(project) - val settings = BiomeSettings.getInstance(project) val version = runWithModalProgressBlocking(project, BiomeBundle.message("biome.version")) { biomePackage.versionNumber() } - return ActionInfo.defaultComment(version, settings.filePattern, isActionOnSaveEnabled) + return ActionInfo.defaultComment(version, isActionOnSaveEnabled) } - - override fun getActionLinks() = listOf(createGoToPageInSettingsLink(BiomeConfigurable.CONFIGURABLE_ID)) } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveFormatActionInfo.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveFormatActionInfo.kt index 9df03fc..f9f580f 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveFormatActionInfo.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveFormatActionInfo.kt @@ -15,7 +15,7 @@ class BiomeOnSaveFormatActionInfo(actionOnSaveContext: ActionOnSaveContext) : ) { override fun getActionOnSaveName() = - BiomeBundle.message("biome.run.format.on.save.checkbox.on.actions.on.save.page") + BiomeBundle.message("biome.format.on.save.checkbox.on.actions.on.save.page") override fun isApplicableAccordingToStoredState(): Boolean = BiomeSettings.getInstance(project).configurationMode != ConfigurationMode.DISABLED @@ -28,31 +28,29 @@ class BiomeOnSaveFormatActionInfo(actionOnSaveContext: ActionOnSaveContext) : override fun isActionOnSaveEnabledAccordingToUiState(configurable: BiomeConfigurable) = configurable.runFormatOnSaveCheckBox.isSelected - override fun setActionOnSaveEnabled(configurable: BiomeConfigurable, enabled: Boolean) { + override fun setActionOnSaveEnabled(configurable: BiomeConfigurable, + enabled: Boolean) { configurable.runFormatOnSaveCheckBox.isSelected = enabled } override fun getActionLinks() = listOf(createGoToPageInSettingsLink(BiomeConfigurable.CONFIGURABLE_ID)) override fun getCommentAccordingToUiState(configurable: BiomeConfigurable): ActionOnSaveComment? { - if (!isSaveActionApplicable) return ActionOnSaveComment.info(BiomeBundle.message("biome.on.save.comment.disabled")) - - val biomePackage = BiomePackage(project) - val version = runWithModalProgressBlocking(project, BiomeBundle.message("biome.version")) { - biomePackage.versionNumber() - } - return ActionInfo.defaultComment(version, configurable.runForFilesField.text.trim(), isActionOnSaveEnabled) + return comment() } override fun getCommentAccordingToStoredState(): ActionOnSaveComment? { + return comment() + } + + private fun comment(): ActionOnSaveComment? { if (!isSaveActionApplicable) return ActionOnSaveComment.info(BiomeBundle.message("biome.on.save.comment.disabled")) val biomePackage = BiomePackage(project) - val settings = BiomeSettings.getInstance(project) val version = runWithModalProgressBlocking(project, BiomeBundle.message("biome.version")) { biomePackage.versionNumber() } - return ActionInfo.defaultComment(version, settings.filePattern, isActionOnSaveEnabled) + return ActionInfo.defaultComment(version, isActionOnSaveEnabled) } } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveInfoProvider.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveInfoProvider.kt index a3a7045..19d684f 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveInfoProvider.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveInfoProvider.kt @@ -6,19 +6,15 @@ import com.intellij.ide.actionsOnSave.ActionOnSaveInfo import com.intellij.ide.actionsOnSave.ActionOnSaveInfoProvider class BiomeOnSaveInfoProvider : ActionOnSaveInfoProvider() { - override fun getActionOnSaveInfos(context: ActionOnSaveContext): - List = listOf( + override fun getActionOnSaveInfos(context: ActionOnSaveContext): List = listOf( BiomeOnSaveFormatActionInfo(context), BiomeOnSaveApplySafeFixesActionInfo(context), - BiomeOnSaveApplyUnsafeFixesActionInfo(context) - ) + BiomeOnSaveSortImportActionInfo(context)) override fun getSearchableOptions(): Collection { - return listOf( - BiomeBundle.message("biome.run.format.on.save.checkbox.on.actions.on.save.page"), - BiomeBundle.message("biome.run.safe.fixes.on.save.checkbox.on.actions.on.save.page"), - BiomeBundle.message("biome.run.unsafe.fixes.on.save.checkbox.on.actions.on.save.page") - ) + return listOf(BiomeBundle.message("biome.format.on.save.checkbox.on.actions.on.save.page"), + BiomeBundle.message("biome.apply.safe.fixes.on.save.checkbox.on.actions.on.save.page"), + BiomeBundle.message("biome.run.sort.import.on.save.checkbox.on.actions.on.save.page")) } } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveApplyUnsafeFixesActionInfo.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveSortImportActionInfo.kt similarity index 65% rename from src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveApplyUnsafeFixesActionInfo.kt rename to src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveSortImportActionInfo.kt index 0f36c1a..96a89da 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveApplyUnsafeFixesActionInfo.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeOnSaveSortImportActionInfo.kt @@ -7,7 +7,7 @@ import com.intellij.ide.actionsOnSave.ActionOnSaveComment import com.intellij.ide.actionsOnSave.ActionOnSaveContext import com.intellij.platform.ide.progress.runWithModalProgressBlocking -class BiomeOnSaveApplyUnsafeFixesActionInfo(actionOnSaveContext: ActionOnSaveContext) : +class BiomeOnSaveSortImportActionInfo(actionOnSaveContext: ActionOnSaveContext) : ActionOnSaveBackedByOwnConfigurable( actionOnSaveContext, BiomeConfigurable.CONFIGURABLE_ID, @@ -15,7 +15,7 @@ class BiomeOnSaveApplyUnsafeFixesActionInfo(actionOnSaveContext: ActionOnSaveCon ) { override fun getActionOnSaveName() = - BiomeBundle.message("biome.run.unsafe.fixes.on.save.checkbox.on.actions.on.save.page") + BiomeBundle.message("biome.run.sort.import.on.save.checkbox.on.actions.on.save.page") override fun isApplicableAccordingToStoredState(): Boolean = BiomeSettings.getInstance(project).configurationMode != ConfigurationMode.DISABLED @@ -24,36 +24,33 @@ class BiomeOnSaveApplyUnsafeFixesActionInfo(actionOnSaveContext: ActionOnSaveCon !configurable.disabledConfiguration.isSelected override fun isActionOnSaveEnabledAccordingToStoredState() = - BiomeSettings.getInstance(project).applyUnsafeFixesOnSave + BiomeSettings.getInstance(project).sortImportOnSave override fun isActionOnSaveEnabledAccordingToUiState(configurable: BiomeConfigurable) = - configurable.runUnsafeFixesOnSaveCheckBox.isSelected + configurable.sortImportOnSaveCheckBox.isSelected override fun setActionOnSaveEnabled(configurable: BiomeConfigurable, enabled: Boolean) { - configurable.runUnsafeFixesOnSaveCheckBox.isSelected = enabled + configurable.sortImportOnSaveCheckBox.isSelected = enabled } override fun getCommentAccordingToUiState(configurable: BiomeConfigurable): ActionOnSaveComment? { - if (!isSaveActionApplicable) return ActionOnSaveComment.info(BiomeBundle.message("biome.on.save.comment.disabled")) - - val biomePackage = BiomePackage(project) - val version = runWithModalProgressBlocking(project, BiomeBundle.message("biome.version")) { - biomePackage.versionNumber() - } - return ActionInfo.defaultComment(version, configurable.runForFilesField.text.trim(), isActionOnSaveEnabled) + return comment() } override fun getCommentAccordingToStoredState(): ActionOnSaveComment? { + return comment() + } + + override fun getActionLinks() = listOf(createGoToPageInSettingsLink(BiomeConfigurable.CONFIGURABLE_ID)) + + private fun comment(): ActionOnSaveComment? { if (!isSaveActionApplicable) return ActionOnSaveComment.info(BiomeBundle.message("biome.on.save.comment.disabled")) val biomePackage = BiomePackage(project) - val settings = BiomeSettings.getInstance(project) val version = runWithModalProgressBlocking(project, BiomeBundle.message("biome.version")) { biomePackage.versionNumber() } - return ActionInfo.defaultComment(version, settings.filePattern, isActionOnSaveEnabled) + return ActionInfo.defaultComment(version, isActionOnSaveEnabled) } - - override fun getActionLinks() = listOf(createGoToPageInSettingsLink(BiomeConfigurable.CONFIGURABLE_ID)) } diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeSettings.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeSettings.kt index 95f528e..a7fc807 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeSettings.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeSettings.kt @@ -1,7 +1,7 @@ package com.github.biomejs.intellijbiome.settings -import com.github.biomejs.intellijbiome.Feature -import com.intellij.lang.javascript.linter.GlobPatternUtil +import com.github.biomejs.intellijbiome.services.BiomeServerService.Feature +import com.github.biomejs.intellijbiome.settings.BiomeSettingsState.Companion.DEFAULT_EXTENSION_LIST import com.intellij.openapi.components.* import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile @@ -29,10 +29,11 @@ class BiomeSettings : state.configPath = value } - var filePattern: String - get() = state.filePattern ?: BiomeSettingsState.DEFAULT_FILE_PATTERN + var supportedExtensions: MutableList + get() = state.supportedExtensions.takeIf { it.isNotEmpty() } ?: DEFAULT_EXTENSION_LIST.toMutableList() set(value) { - state.filePattern = value + state.supportedExtensions.clear() + state.supportedExtensions.addAll(value) } var configurationMode: ConfigurationMode @@ -53,16 +54,16 @@ class BiomeSettings : state.formatOnSave = value } - var applySafeFixesOnSave: Boolean - get() = isEnabled() && state.applySafeFixesOnSave + var sortImportOnSave: Boolean + get() = isEnabled() && state.sortImportOnSave set(value) { - state.applySafeFixesOnSave = value + state.sortImportOnSave = value } - var applyUnsafeFixesOnSave: Boolean - get() = isEnabled() && state.applyUnsafeFixesOnSave + var applySafeFixesOnSave: Boolean + get() = isEnabled() && state.applySafeFixesOnSave set(value) { - state.applyUnsafeFixesOnSave = value + state.applySafeFixesOnSave = value } fun getEnabledFeatures(): EnumSet { @@ -71,10 +72,10 @@ class BiomeSettings : features.add(Feature.Format) } if (applySafeFixesOnSave) { - features.add(Feature.SafeFixes) + features.add(Feature.ApplySafeFixes) } - if (applyUnsafeFixesOnSave) { - features.add(Feature.UnsafeFixes) + if (sortImportOnSave) { + features.add(Feature.SortImports) } return features } @@ -83,9 +84,14 @@ class BiomeSettings : return configurationMode !== ConfigurationMode.DISABLED } - fun fileSupported(project: Project, - file: VirtualFile): Boolean = - GlobPatternUtil.isFileMatchingGlobPattern(project, filePattern, file) + fun fileSupported(file: VirtualFile): Boolean { + val fileExtension = file.extension + return if (fileExtension != null) { + supportedExtensions.contains(".$fileExtension") + } else { + false + } + } companion object { @JvmStatic diff --git a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeSettingsState.kt b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeSettingsState.kt index 85d3e11..1021921 100644 --- a/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeSettingsState.kt +++ b/src/main/kotlin/com/github/biomejs/intellijbiome/settings/BiomeSettingsState.kt @@ -16,14 +16,17 @@ enum class ConfigurationMode { class BiomeSettingsState : BaseState() { var executablePath by string() var configPath by string() - var filePattern by string(DEFAULT_FILE_PATTERN) var formatOnSave by property(false) var enableLspFormat by property(false) var applySafeFixesOnSave by property(false) - var applyUnsafeFixesOnSave by property(false) + var sortImportOnSave by property(false) var configurationMode by enum(ConfigurationMode.AUTOMATIC) + var supportedExtensions by list() companion object { - const val DEFAULT_FILE_PATTERN = "**/*.{js,mjs,cjs,ts,jsx,tsx,cts,json,jsonc,vue,svelte,astro,css}" + val DEFAULT_EXTENSION_LIST = listOf( + ".astro", ".css", ".gql", ".graphql", ".js", ".mjs", ".cjs", ".jsx", + ".json", ".jsonc", ".svelte", ".html", ".ts", ".mts", ".cts", ".tsx", ".vue" + ) } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index de5a228..1194d8e 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -63,7 +63,28 @@ + /> + + + + + + + + + + diff --git a/src/main/resources/messages/BiomeBundle.properties b/src/main/resources/messages/BiomeBundle.properties index 81655f7..cfc8994 100644 --- a/src/main/resources/messages/BiomeBundle.properties +++ b/src/main/resources/messages/BiomeBundle.properties @@ -12,14 +12,23 @@ biome.run.format.for.files.label=Run biome for &files: biome.enable.lsp.format.label=Enable LSP-based code formatting biome.run.safe.fixes.on.save.label=Run safe fixes on &save biome.run.format.on.save.label=Run format on &save -biome.run.unsafe.fixes.on.save.label=Run unsafe fixes on &save -biome.run.format.on.save.checkbox.on.actions.on.save.page=Run Biome check -biome.run.safe.fixes.on.save.checkbox.on.actions.on.save.page=Run Biome check with --apply -biome.run.unsafe.fixes.on.save.checkbox.on.actions.on.save.page=Run Biome check with --apply-unsafe -biome.run.biome.check.with.features=Running Biome check with {0} -biome.failed.to.run.biome.check.with.features=Failed to run Biome check with {0} for file {1} +biome.apply.safe.fixes.success.label=Safe fixes applied +biome.apply.safe.fixes.success.description=The safe fixes have been successfully applied +biome.apply.safe.fixes.failure.label=Failed to apply safe fixes +biome.apply.safe.fixes.failure.description=An error occurred while applying safe fixes: {0} +biome.apply.sort.import.success.label=Imports sorted +biome.apply.sort.import.success.description=The imports have been successfully sorted +biome.apply.sort.import.failure.label=Failed to sort imports +biome.apply.sort.import.failure.description=An error occurred while sorting imports: {0} +biome.apply.feature.on.save.failure.label=Failed to Execute Features: {0} +biome.apply.feature.on.save.failure.description=An error occurred while executing features {0}: {1} +biome.sort.import.on.save.label=Sort import on &save +biome.format.on.save.checkbox.on.actions.on.save.page=Run Biome format +biome.apply.safe.fixes.on.save.checkbox.on.actions.on.save.page=Run Biome apply safe fixes +biome.run.sort.import.on.save.checkbox.on.actions.on.save.page=Run Biome sort import +biome.run.biome.check.with.features=Running Biome with {0} biome.run.on.save.package.not.specified.warning=Biome package not specified -biome.run.on.save.version.and.files.pattern=Version {0}. Run for: {1} +biome.run.on.save.version.and.files.pattern=Version {0} biome.enable.lsp.format.help.label=If LSP formatting is enabled, you can use it as a Save Action in IntelliJ:
    \
  • Open Preferences/Settings in IntelliJ.
  • \
  • Navigate to Tools > Actions on Save.
  • \ @@ -28,5 +37,10 @@ biome.enable.lsp.format.help.label=If LSP formatting is enabled, you can u
\

This setup allows automatic LSP-based code formatting on save.

\

This feature also allows you to format code using the designated hotkey (Alt+Shift+Command+L on Mac or Ctrl+Alt+L on Windows/Linux).

+biome.supported.extensions.label=Supported extensions: +biome.supported.extensions.comment=Enter file extensions separated by commas (e.g., .js, .ts, .jsx). Each extension must start with a '.' and contain only alphanumeric characters. +biome.file.not.supported.title=File not supported +biome.file.not.supported.description=The file "{0}" is not supported by Biome. +biome.configure.extensions.link=Configure extensions diff --git a/src/test/kotlin/com/github/biomejs/intellijbiome/pages/Editor.kt b/src/test/kotlin/com/github/biomejs/intellijbiome/pages/Editor.kt index b109a8b..250bf00 100644 --- a/src/test/kotlin/com/github/biomejs/intellijbiome/pages/Editor.kt +++ b/src/test/kotlin/com/github/biomejs/intellijbiome/pages/Editor.kt @@ -9,7 +9,8 @@ import com.intellij.remoterobot.fixtures.FixtureName import com.intellij.remoterobot.search.locators.byXpath @JvmOverloads -fun ContainerFixture.editor(title: String, function: Editor.() -> Unit = {}): Editor { +fun ContainerFixture.editor(title: String, + function: Editor.() -> Unit = {}): Editor { find( byXpath("//div[@class='EditorTabs']//div[@accessiblename='$title' and @class='SimpleColoredComponent']"), ).click() diff --git a/src/test/kotlin/com/github/biomejs/intellijbiome/pages/IdeaFrame.kt b/src/test/kotlin/com/github/biomejs/intellijbiome/pages/IdeaFrame.kt index e5245a1..126275b 100644 --- a/src/test/kotlin/com/github/biomejs/intellijbiome/pages/IdeaFrame.kt +++ b/src/test/kotlin/com/github/biomejs/intellijbiome/pages/IdeaFrame.kt @@ -16,7 +16,8 @@ fun RemoteRobot.idea(function: IdeaFrame.() -> Unit) { @FixtureName("Idea frame") @DefaultXpath("IdeFrameImpl type", "//div[@class='IdeFrameImpl']") -class IdeaFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : +class IdeaFrame(remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) { val menuBar: JMenuBarFixture get() = step("Menu...") { @@ -24,7 +25,8 @@ class IdeaFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : } @JvmOverloads - fun dumbAware(timeout: Duration = Duration.ofMinutes(5), function: () -> Unit) { + fun dumbAware(timeout: Duration = Duration.ofMinutes(5), + function: () -> Unit) { step("Wait for smart mode") { waitFor(duration = timeout, interval = Duration.ofSeconds(5)) { runCatching { isDumbMode().not() }.getOrDefault(false) diff --git a/src/test/kotlin/com/github/biomejs/intellijbiome/pages/StatusBar.kt b/src/test/kotlin/com/github/biomejs/intellijbiome/pages/StatusBar.kt index 99a646d..c953739 100644 --- a/src/test/kotlin/com/github/biomejs/intellijbiome/pages/StatusBar.kt +++ b/src/test/kotlin/com/github/biomejs/intellijbiome/pages/StatusBar.kt @@ -12,7 +12,8 @@ fun RemoteRobot.statusBar(function: StatusbarFrame.() -> Unit) { @FixtureName("Statusbar frame") @DefaultXpath("IdeStatusBarImpl type", "//div[@class='IdeStatusBarImpl']") -class StatusbarFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : +class StatusbarFrame(remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) { val statusBarPanel diff --git a/src/test/kotlin/com/github/biomejs/intellijbiome/pages/WelcomeFrame.kt b/src/test/kotlin/com/github/biomejs/intellijbiome/pages/WelcomeFrame.kt index 0d55130..6e36362 100644 --- a/src/test/kotlin/com/github/biomejs/intellijbiome/pages/WelcomeFrame.kt +++ b/src/test/kotlin/com/github/biomejs/intellijbiome/pages/WelcomeFrame.kt @@ -15,7 +15,8 @@ fun RemoteRobot.welcomeFrame(function: WelcomeFrame.() -> Unit) { @FixtureName("Welcome Frame") @DefaultXpath("type", "//div[@class='FlatWelcomeFrame']") -class WelcomeFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteComponent) : +class WelcomeFrame(remoteRobot: RemoteRobot, + remoteComponent: RemoteComponent) : CommonContainerFixture(remoteRobot, remoteComponent) { val openProjectLink get() = actionLink( diff --git a/src/test/kotlin/com/github/biomejs/intellijbiome/utils/RemoteRobotExtension.kt b/src/test/kotlin/com/github/biomejs/intellijbiome/utils/RemoteRobotExtension.kt index e9beec0..768c525 100644 --- a/src/test/kotlin/com/github/biomejs/intellijbiome/utils/RemoteRobotExtension.kt +++ b/src/test/kotlin/com/github/biomejs/intellijbiome/utils/RemoteRobotExtension.kt @@ -31,18 +31,20 @@ class RemoteRobotExtension : AfterTestExecutionCallback, ParameterResolver { } private val client = OkHttpClient() - override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean { - return parameterContext?.parameter?.type?.equals(RemoteRobot::class.java) ?: false + override fun supportsParameter(parameterContext: ParameterContext?, + extensionContext: ExtensionContext?): Boolean { + return parameterContext?.parameter?.type?.equals(RemoteRobot::class.java) == true } - override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any { + override fun resolveParameter(parameterContext: ParameterContext?, + extensionContext: ExtensionContext?): Any { return remoteRobot } override fun afterTestExecution(context: ExtensionContext?) { val testMethod: Method = context?.requiredTestMethod ?: throw IllegalStateException("test method is null") val testMethodName = testMethod.name - val testFailed: Boolean = context.executionException?.isPresent ?: false + val testFailed: Boolean = context.executionException?.isPresent == true if (testFailed) { // saveScreenshot(testMethodName) saveIdeaFrames(testMethodName) @@ -63,7 +65,9 @@ class RemoteRobotExtension : AfterTestExecutionCallback, ParameterResolver { println("Hierarchy snapshot: ${hierarchySnapshot.absolutePath}") } - private fun saveFile(url: String, folder: String, name: String): File { + private fun saveFile(url: String, + folder: String, + name: String): File { val response = client.newCall(Request.Builder().url(url).build()).execute() return File(folder).apply { mkdirs()