From a2904d3545e0b9002ecff389ebe40f6fe095df0d Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Fri, 1 Mar 2024 12:02:53 +0000 Subject: [PATCH 1/6] Migrate formatter to AsyncDocumentFormattingService --- resources/META-INF/plugin.xml | 5 +++-- src/com/koxudaxi/ruff/Ruff.kt | 22 +++++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 60d676b8..f5de0c2c 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -10,13 +10,14 @@ - - + + + diff --git a/src/com/koxudaxi/ruff/Ruff.kt b/src/com/koxudaxi/ruff/Ruff.kt index 99235ae2..da3d1403 100644 --- a/src/com/koxudaxi/ruff/Ruff.kt +++ b/src/com/koxudaxi/ruff/Ruff.kt @@ -198,7 +198,18 @@ fun runCommand( commandArgs.executable, commandArgs.project.basePath, commandArgs.stdin, *commandArgs.args.toTypedArray() ) +fun getGeneralCommandLine(executable: File, projectPath: String?, vararg args: String): GeneralCommandLine? { + if (!WslPath.isWslUncPath(executable.path)) { + return getGeneralCommandLine(listOf(executable.path) + args, projectPath) + } + + val windowsUncPath = WslPath.parseWindowsUncPath(executable.path) ?: return null + val options = WSLCommandLineOptions() + options.setExecuteCommandInShell(false) + val commandLine = getGeneralCommandLine(listOf(windowsUncPath.linuxPath) + args, projectPath) + return windowsUncPath.distribution.patchCommandLine(commandLine, null, WSLCommandLineOptions()) +} fun getGeneralCommandLine(command: List, projectPath: String?): GeneralCommandLine = GeneralCommandLine(command).withWorkDirectory(projectPath).withCharset(Charsets.UTF_8) @@ -207,15 +218,8 @@ fun runCommand( ): String? { val indicator = ProgressManager.getInstance().progressIndicator - val handler = if (WslPath.isWslUncPath(executable.path)) { - val windowsUncPath = WslPath.parseWindowsUncPath(executable.path) ?: return null - val options = WSLCommandLineOptions() - options.setExecuteCommandInShell(false) - val commandLine = getGeneralCommandLine(listOf(windowsUncPath.linuxPath) + args, projectPath) - windowsUncPath.distribution.patchCommandLine(commandLine, null, WSLCommandLineOptions()) - } else { - getGeneralCommandLine(listOf(executable.path) + args, projectPath) - }.let { CapturingProcessHandler(it) } + val handler = getGeneralCommandLine(executable, projectPath, *args) + ?.let { CapturingProcessHandler(it) } ?: return null val result = with(handler) { if (stdin is ByteArray) { From 20315ae054b4230f54c3abe1e8cf300d5f443e49 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Fri, 1 Mar 2024 12:35:22 +0000 Subject: [PATCH 2/6] Add AsyncDocumentFormattingService --- src/com/koxudaxi/ruff/RuffAsyncFormatter.kt | 79 +++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/com/koxudaxi/ruff/RuffAsyncFormatter.kt diff --git a/src/com/koxudaxi/ruff/RuffAsyncFormatter.kt b/src/com/koxudaxi/ruff/RuffAsyncFormatter.kt new file mode 100644 index 00000000..afa9ef78 --- /dev/null +++ b/src/com/koxudaxi/ruff/RuffAsyncFormatter.kt @@ -0,0 +1,79 @@ +package com.koxudaxi.ruff + +import com.intellij.execution.ExecutionException +import com.intellij.execution.process.CapturingProcessAdapter +import com.intellij.execution.process.OSProcessHandler +import com.intellij.execution.process.ProcessEvent +import com.intellij.formatting.FormattingContext +import com.intellij.formatting.service.AsyncDocumentFormattingService +import com.intellij.formatting.service.AsyncFormattingRequest +import com.intellij.formatting.service.FormattingService +import com.intellij.psi.PsiFile +import java.nio.charset.StandardCharsets +import java.util.* + + +class RuffAsyncFormatter : AsyncDocumentFormattingService() { + private val FEATURES: MutableSet = EnumSet.noneOf( + FormattingService.Feature::class.java + ) + + override fun getFeatures(): MutableSet { + return FEATURES + } + + override fun canFormat(file: PsiFile): Boolean { + return file.isApplicableTo + } + + override fun createFormattingTask(request: AsyncFormattingRequest): FormattingTask? { + val formattingContext: FormattingContext = request.context + val ioFile = request.ioFile ?: return null + val sourceFile = formattingContext.containingFile.sourceFile + val commandArgs = generateCommandArgs(sourceFile, FORMAT_ARGS) ?: return null + try { + val commandLine = getGeneralCommandLine(commandArgs.executable, commandArgs.project.basePath, *commandArgs.args.toTypedArray()) + ?: return null + val handler = OSProcessHandler(commandLine.withCharset(StandardCharsets.UTF_8)) + with(handler) { + processInput.write(ioFile.readText().toByteArray()) + processInput.close() + } + return object : FormattingTask { + override fun run() { + handler.addProcessListener(object : CapturingProcessAdapter() { + override fun processTerminated(event: ProcessEvent) { + val exitCode = event.exitCode + if (exitCode == 0) { + request.onTextReady(output.stdout) + } else { + request.onError("Ruff Error", output.stderr) + } + } + }) + handler.startNotify() + } + + override fun cancel(): Boolean { + handler.destroyProcess() + return true + } + + override fun isRunUnderProgress(): Boolean { + return true + } + } + } catch (e: ExecutionException) { + e.message?.let { request.onError("Ruff Error", it) } + return null + } + } + + override fun getNotificationGroupId(): String { + return "Ruff" + } + + override fun getName(): String { + return "Ruff formatter" + } +} \ No newline at end of file From 47433aca9b06cf9b3135c188ac1d8470622405f1 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 14 Sep 2024 00:03:09 +0900 Subject: [PATCH 3/6] Remove duplicated getGeneralCommandLine --- resources/META-INF/plugin.xml | 4 +--- src/com/koxudaxi/ruff/Ruff.kt | 25 ++----------------------- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 787d34d7..c35b7736 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -11,14 +11,12 @@ - - - + diff --git a/src/com/koxudaxi/ruff/Ruff.kt b/src/com/koxudaxi/ruff/Ruff.kt index 470d896a..810cb443 100644 --- a/src/com/koxudaxi/ruff/Ruff.kt +++ b/src/com/koxudaxi/ruff/Ruff.kt @@ -273,36 +273,15 @@ fun runCommand( commandArgs.executable, commandArgs.project, commandArgs.stdin, *commandArgs.args.toTypedArray() ) -fun getGeneralCommandLine(executable: File, project: Project?, vararg args: String): GeneralCommandLine? { - val projectPath = project?.basePath - if (!WslPath.isWslUncPath(executable.path)) { - return getGeneralCommandLine(listOf(executable.path) + args, projectPath) - } - - val windowsUncPath = WslPath.parseWindowsUncPath(executable.path) ?: return null - val configArgIndex = args.indexOf(CONFIG_ARG) - val injectedArgs = if (configArgIndex != -1 && configArgIndex < args.size - 1) { - val configPathIndex = configArgIndex + 1 - val configPath = args[configPathIndex] - val windowsUncConfigPath = WslPath.parseWindowsUncPath(configPath)?.linuxPath ?: configPath - args.toMutableList().apply { this[configPathIndex] = windowsUncConfigPath }.toTypedArray() - } else { - args - } - val commandLine = getGeneralCommandLine(listOf(windowsUncPath.linuxPath) + injectedArgs, projectPath) - val options = WSLCommandLineOptions() - options.setExecuteCommandInShell(false) - options.setLaunchWithWslExe(true) - return windowsUncPath.distribution.patchCommandLine(commandLine, project, options) -} private fun getGeneralCommandLine(command: List, projectPath: String?): GeneralCommandLine = GeneralCommandLine(command).withWorkDirectory(projectPath).withCharset(Charsets.UTF_8) fun getGeneralCommandLine(executable: File, project: Project?, vararg args: String): GeneralCommandLine? { val projectPath = project?.basePath ?: return null if (!WslPath.isWslUncPath(executable.path)) { - return getGeneralCommandLine(listOf(executable.path) + args, projectPath) + return getGeneralCommandLine(listOf(executable.path) + args, projectPath) } + val windowsUncPath = WslPath.parseWindowsUncPath(executable.path) ?: return null val configArgIndex = args.indexOf(CONFIG_ARG) val injectedArgs = if (configArgIndex != -1 && configArgIndex < args.size - 1) { From 73b9d135e5083a43e63a160bdcb1a633703f2209 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Wed, 18 Sep 2024 01:55:14 +0900 Subject: [PATCH 4/6] Add RuffAsyncFormatFormatters --- resources/META-INF/plugin.xml | 3 +- ...xProcessor.kt => RuffAsyncFixFormatter.kt} | 5 +- ...ocessor.kt => RuffAsyncFormatFormatter.kt} | 10 +-- src/com/koxudaxi/ruff/RuffAsyncFormatter.kt | 18 +++--- .../koxudaxi/ruff/RuffPostFormatProcessor.kt | 61 ------------------- .../ruff/RuffPostFormatProcessorTest.kt | 57 ----------------- 6 files changed, 20 insertions(+), 134 deletions(-) rename src/com/koxudaxi/ruff/{RuffFixProcessor.kt => RuffAsyncFixFormatter.kt} (51%) rename src/com/koxudaxi/ruff/{RuffFormatProcessor.kt => RuffAsyncFormatFormatter.kt} (55%) delete mode 100644 src/com/koxudaxi/ruff/RuffPostFormatProcessor.kt delete mode 100644 testSrc/com/koxudaxi/ruff/RuffPostFormatProcessorTest.kt diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index c35b7736..c45ccee5 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -16,7 +16,8 @@ enabledByDefault="true" level="WARNING" implementationClass="com.koxudaxi.ruff.RuffInspection"/> - + + diff --git a/src/com/koxudaxi/ruff/RuffFixProcessor.kt b/src/com/koxudaxi/ruff/RuffAsyncFixFormatter.kt similarity index 51% rename from src/com/koxudaxi/ruff/RuffFixProcessor.kt rename to src/com/koxudaxi/ruff/RuffAsyncFixFormatter.kt index 9b9b5c07..c8718da9 100644 --- a/src/com/koxudaxi/ruff/RuffFixProcessor.kt +++ b/src/com/koxudaxi/ruff/RuffAsyncFixFormatter.kt @@ -3,9 +3,10 @@ package com.koxudaxi.ruff import com.intellij.openapi.project.Project -class RuffFixProcessor : RuffPostFormatProcessor() { +class RuffAsyncFixFormatter : RuffAsyncFormatterBase() { override fun isEnabled(project: Project): Boolean = RuffConfigService.getInstance(project).runRuffOnReformatCode - override fun process(sourceFile: SourceFile): String? = fix(sourceFile) + override fun getArgs(project: Project): List = project.FIX_ARGS + override fun getName(): String = "Ruff Fix Formatter" } \ No newline at end of file diff --git a/src/com/koxudaxi/ruff/RuffFormatProcessor.kt b/src/com/koxudaxi/ruff/RuffAsyncFormatFormatter.kt similarity index 55% rename from src/com/koxudaxi/ruff/RuffFormatProcessor.kt rename to src/com/koxudaxi/ruff/RuffAsyncFormatFormatter.kt index 2b926aa6..98720965 100644 --- a/src/com/koxudaxi/ruff/RuffFormatProcessor.kt +++ b/src/com/koxudaxi/ruff/RuffAsyncFormatFormatter.kt @@ -2,11 +2,13 @@ package com.koxudaxi.ruff import com.intellij.openapi.project.Project - -class RuffFormatProcessor : RuffPostFormatProcessor() { +class RuffAsyncFormatFormatter : RuffAsyncFormatterBase() { override fun isEnabled(project: Project): Boolean { val ruffConfigService = RuffConfigService.getInstance(project) - return ruffConfigService.runRuffOnReformatCode && ruffConfigService.useRuffFormat && RuffCacheService.hasFormatter(project) + return ruffConfigService.runRuffOnReformatCode && ruffConfigService.useRuffFormat } - override fun process(sourceFile: SourceFile): String? = format(sourceFile) + + override fun getArgs(project: Project): List = FORMAT_ARGS + override fun getName(): String = "Ruff Format Formatter" + } \ No newline at end of file diff --git a/src/com/koxudaxi/ruff/RuffAsyncFormatter.kt b/src/com/koxudaxi/ruff/RuffAsyncFormatter.kt index 4fb201d3..7ce0e0c5 100644 --- a/src/com/koxudaxi/ruff/RuffAsyncFormatter.kt +++ b/src/com/koxudaxi/ruff/RuffAsyncFormatter.kt @@ -8,14 +8,17 @@ import com.intellij.formatting.FormattingContext import com.intellij.formatting.service.AsyncDocumentFormattingService import com.intellij.formatting.service.AsyncFormattingRequest import com.intellij.formatting.service.FormattingService +import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import java.nio.charset.StandardCharsets import java.util.* -class RuffAsyncFormatter : AsyncDocumentFormattingService() { +abstract class RuffAsyncFormatterBase : AsyncDocumentFormattingService() { + abstract fun isEnabled(project: Project): Boolean + abstract fun getArgs(project: Project): List private val FEATURES: MutableSet = EnumSet.noneOf( - FormattingService.Feature::class.java + FormattingService.Feature::class.java ) override fun getFeatures(): MutableSet { @@ -23,16 +26,17 @@ class RuffAsyncFormatter : AsyncDocumentFormattingService() { } override fun canFormat(file: PsiFile): Boolean { - return file.isApplicableTo + return isEnabled(file.project) && file.isApplicableTo } override fun createFormattingTask(request: AsyncFormattingRequest): FormattingTask? { val formattingContext: FormattingContext = request.context val ioFile = request.ioFile ?: return null val sourceFile = formattingContext.containingFile.sourceFile - val commandArgs = generateCommandArgs(sourceFile, FORMAT_ARGS) ?: return null + val commandArgs = generateCommandArgs(sourceFile, getArgs(formattingContext.project)) ?: return null try { - val commandLine = getGeneralCommandLine(commandArgs.executable, commandArgs.project, *commandArgs.args.toTypedArray()) + val commandLine = + getGeneralCommandLine(commandArgs.executable, commandArgs.project, *commandArgs.args.toTypedArray()) ?: return null val handler = OSProcessHandler(commandLine.withCharset(StandardCharsets.UTF_8)) with(handler) { @@ -72,8 +76,4 @@ class RuffAsyncFormatter : AsyncDocumentFormattingService() { override fun getNotificationGroupId(): String { return "Ruff" } - - override fun getName(): String { - return "Ruff formatter" - } } \ No newline at end of file diff --git a/src/com/koxudaxi/ruff/RuffPostFormatProcessor.kt b/src/com/koxudaxi/ruff/RuffPostFormatProcessor.kt deleted file mode 100644 index 05c6bdb2..00000000 --- a/src/com/koxudaxi/ruff/RuffPostFormatProcessor.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.koxudaxi.ruff - -import com.intellij.openapi.editor.Document -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.codeStyle.CodeStyleSettings -import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor -import com.jetbrains.python.psi.PyUtil - - -abstract class RuffPostFormatProcessor : PostFormatProcessor { - override fun processElement(source: PsiElement, settings: CodeStyleSettings): PsiElement = source - override fun processText(source: PsiFile, rangeToReformat: TextRange, settings: CodeStyleSettings): TextRange { - if (!isEnabled(source.project)) return rangeToReformat - if (!source.isApplicableTo) return rangeToReformat - - val sourceFile = source.getSourceFile(rangeToReformat, true) - val text = sourceFile.text ?: return rangeToReformat - val formatted = executeOnPooledThread(null) { - process(sourceFile) - } ?: return rangeToReformat - if (text == formatted) return rangeToReformat - val sourceDiffRange = diffRange(text, formatted) ?: return rangeToReformat - - return PyUtil.updateDocumentUnblockedAndCommitted( - source - ) { document: Document -> - // Verify that the document has not been modified since the reformatting started - if (!sourceFile.hasSameContentAsDocument(document)) return@updateDocumentUnblockedAndCommitted rangeToReformat - - // document.replaceString(startOffset, endOffset, formattedPart) does not keep the line break point markers and caret position - document.setText(formatted) - - val endOffset = rangeToReformat.startOffset + sourceDiffRange.length - when { - endOffset <= rangeToReformat.endOffset -> rangeToReformat - else -> TextRange.create(rangeToReformat.startOffset, endOffset) - } - } ?: rangeToReformat - } - - abstract fun isEnabled(project: Project): Boolean - abstract fun process(sourceFile: SourceFile): String? - - internal fun diffRange(source: String, target: String): TextRange? { - if (source == target) return null - if (source.isEmpty() || target.isEmpty()) return TextRange(0, source.length) - - val start = source.zip(target).takeWhile { it.first == it.second }.count() - - var endSource = source.lastIndex - var endTarget = target.lastIndex - while (endSource >= start && endTarget >= start && source[endSource] == target[endTarget]) { - endSource-- - endTarget-- - } - return TextRange(start, endSource + 1) - } -} \ No newline at end of file diff --git a/testSrc/com/koxudaxi/ruff/RuffPostFormatProcessorTest.kt b/testSrc/com/koxudaxi/ruff/RuffPostFormatProcessorTest.kt deleted file mode 100644 index 8380cbf4..00000000 --- a/testSrc/com/koxudaxi/ruff/RuffPostFormatProcessorTest.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.koxudaxi.ruff - -import com.intellij.openapi.project.Project -import com.intellij.openapi.util.TextRange -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull - - -class RuffPostFormatProcessorTest { - class RuffPostFormatProcessorBase : RuffPostFormatProcessor() { - override fun isEnabled(project: Project): Boolean { - TODO("Not yet implemented") - } - - override fun process(sourceFile: SourceFile): String? { - TODO("Not yet implemented") - } - } - - private val postFormatProcessor = RuffPostFormatProcessorBase() - private fun diffRange(source: String, target: String): TextRange? { - return postFormatProcessor.diffRange(source, target) - } - - private fun doTestDiffRange(source: String, target: String, expectTextRange: TextRange, expectInserted: String) { - val sourceDiffRange = diffRange(source, target) - assertNotNull(sourceDiffRange) - assertEquals(expectTextRange, sourceDiffRange) - val targetDiffRange = diffRange(target, source) - assertNotNull(targetDiffRange) - val inserted = target.substring(targetDiffRange.startOffset, targetDiffRange.endOffset) - assertEquals(expectInserted, targetDiffRange.substring(target)) - assertEquals(target, sourceDiffRange.replace(source, inserted)) - } - - @Test - fun testDiffRange() { - assertNull(diffRange("hello", "hello")) // no difference - doTestDiffRange("hello", "", TextRange(0, 5), "") // complete difference - doTestDiffRange("hello", "hell", TextRange(4, 5), "") // difference at the end - doTestDiffRange("hell", "hello", TextRange(4, 4), "o") // difference at the end - doTestDiffRange("hello", "jello", TextRange(0, 1), "j") // difference at the start - doTestDiffRange("hello world", "hello world!", TextRange(11, 11), "!") // difference at the end - doTestDiffRange("hello world!", "hello world", TextRange(11, 12), "") // difference at the end - doTestDiffRange("hello world", "Hello world", TextRange(0, 1), "H") // difference at the start - doTestDiffRange("hello world", "hEllo world", TextRange(1, 2), "E") // difference in the middle - doTestDiffRange("import a\nimport b\nimport c\n", "import a\nimport c\n", TextRange(16, 25), "") - doTestDiffRange( - "import a\nimport c\n", - "import a\nimport b\nimport c\n", - TextRange(16, 16), - "b\nimport " - ) // difference in the middle - } -} \ No newline at end of file From 3acdcde61b2181fd350bf05e73577909251feb21 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Wed, 18 Sep 2024 02:04:18 +0900 Subject: [PATCH 5/6] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 392a5b39..64f829bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## [Unreleased] +- Migrate formatter to AsyncDocumentFormattingService [[#391](https://github.com/koxudaxi/ruff-pycharm-plugin/pull/391)] - Remove projectRuffExecutablePath in config file [[#505](https://github.com/koxudaxi/ruff-pycharm-plugin/pull/505)] ## [0.0.39] - 2024-09-12 From f5936147ff8af159b3937922f845a6532b3d56aa Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Wed, 18 Sep 2024 02:19:50 +0900 Subject: [PATCH 6/6] Update JetBrains/qodana-action to v2024.2 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 08062925..ddd805ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -193,7 +193,7 @@ jobs: # Run Qodana inspections - name: Qodana - Code Inspection - uses: JetBrains/qodana-action@v2024.1.9 + uses: JetBrains/qodana-action@v2024.2 with: cache-default-branch-only: true