Skip to content

Commit

Permalink
Merge pull request #243 from pontem-network/integrate-with-new-formatter
Browse files Browse the repository at this point in the history
add `movefmt` formatter integration
  • Loading branch information
mkurnikov authored Nov 22, 2024
2 parents e76d9d4 + 94fc6cb commit ab8044f
Show file tree
Hide file tree
Showing 10 changed files with 377 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.move.cli.externalFormatter

import com.intellij.execution.configuration.EnvironmentVariablesComponent
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.options.BoundConfigurable
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogPanel
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.ui.RawCommandLineEditor
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.bindSelected
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.builder.toMutableProperty
import org.move.cli.settings.VersionLabel
import org.move.openapiext.pathField

class MovefmtConfigurable(val project: Project): BoundConfigurable("Movefmt") {
private val innerDisposable =
Disposer.newCheckedDisposable("Internal checked disposable for MovefmtConfigurable")

private val movefmtPathField =
pathField(
FileChooserDescriptorFactory.createSingleFileOrExecutableAppDescriptor(),
innerDisposable,
"Movefmt location",
onTextChanged = { it ->
val path = it.toNioPathOrNull() ?: return@pathField
versionLabel.update(path)
})
private val versionLabel = VersionLabel(
innerDisposable,
envs = EnvironmentVariablesData.create(mapOf("MOVEFMT_LOG" to "error"), true)
)

private val additionalArguments: RawCommandLineEditor = RawCommandLineEditor()
private val environmentVariables: EnvironmentVariablesComponent = EnvironmentVariablesComponent()


override fun createPanel(): DialogPanel {
this.disposable?.let {
Disposer.register(it, innerDisposable)
}
return panel {
val settings = project.movefmtSettings
val state = settings.state.copy()

row("Movefmt:") {
cell(movefmtPathField)
.align(AlignX.FILL).resizableColumn()
.bind(
componentGet = { it.text },
componentSet = { component, value -> component.text = value },
prop = state::movefmtPath.toMutableProperty()
)
}
row("--version :") { cell(versionLabel) }
separator()
row("Additional arguments:") {
cell(additionalArguments)
.align(AlignX.FILL)
.comment("Additional arguments to pass to <b>movefmt</b> command")
.bind(
componentGet = { it.text },
componentSet = { component, value -> component.text = value },
prop = state::additionalArguments.toMutableProperty()
)
}
row(environmentVariables.label) {
cell(environmentVariables).align(AlignX.FILL)
.bind(
componentGet = { it.envs },
componentSet = { component, value -> component.envs = value },
prop = state::envs.toMutableProperty()
)
}

row { checkBox("Use movefmt instead of the built-in formatter").bindSelected(state::useMovefmt) }
// row { checkBox("Run movefmt on Save").bindSelected(state::runRustfmtOnSave) }

onApply {
settings.modify {
it.movefmtPath = state.movefmtPath
it.additionalArguments = state.additionalArguments
it.envs = state.envs
it.useMovefmt = state.useMovefmt
// it.runRustfmtOnSave = state.runRustfmtOnSave
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.move.cli.externalFormatter

import com.intellij.codeInsight.actions.ReformatCodeProcessor
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.execution.process.CapturingProcessAdapter
import com.intellij.execution.process.ProcessEvent
import com.intellij.formatting.service.AsyncDocumentFormattingService
import com.intellij.formatting.service.AsyncFormattingRequest
import com.intellij.formatting.service.FormattingService
import com.intellij.notification.NotificationType.ERROR
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.progress.util.ProgressIndicatorBase
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.psi.PsiFile
import com.intellij.psi.formatter.FormatterUtil
import org.move.cli.externalFormatter.MovefmtFormattingService.Companion.FormattingReason.*
import org.move.cli.tools.Movefmt
import org.move.ide.notifications.showBalloon
import org.move.lang.MoveFile
import org.move.openapiext.rootPath
import org.move.openapiext.showSettingsDialog
import org.move.stdext.blankToNull
import org.move.stdext.enumSetOf
import org.move.stdext.unwrapOrThrow

class MovefmtFormattingService: AsyncDocumentFormattingService() {
// only whole file formatting is supported
override fun getFeatures(): Set<FormattingService.Feature> = enumSetOf()

override fun canFormat(file: PsiFile): Boolean =
file is MoveFile && file.project.movefmtSettings.useMovefmt && getFormattingReason() == ReformatCode

override fun createFormattingTask(request: AsyncFormattingRequest): FormattingTask? {
val context = request.context
val project = context.project
val settings = project.movefmtSettings

val disposable = Disposer.newDisposable()
val movefmt = project.getMovefmt(disposable)
if (movefmt == null) {
project.showBalloon(MOVEFMT_ERROR,
"movefmt executable configured incorrectly",
ERROR,
object : DumbAwareAction("Edit movefmt settings") {
override fun actionPerformed(e: AnActionEvent) {
e.project?.showSettingsDialog<MovefmtConfigurable>()
}
}
)
return null
}

val projectDirectory = project.rootPath ?: return null
val fileOnDisk = request.ioFile ?: return null

return object: FormattingTask {
private val indicator: ProgressIndicatorBase = ProgressIndicatorBase()

override fun run() {
val arguments = settings.additionalArguments.blankToNull()?.split(" ").orEmpty()
val envs = EnvironmentVariablesData.create(settings.envs, true)
movefmt.reformatFile(
fileOnDisk,
additionalArguments = arguments,
workingDirectory = projectDirectory,
envs,
runner = {
addProcessListener(object: CapturingProcessAdapter() {
override fun processTerminated(event: ProcessEvent) {
val exitCode = event.exitCode
if (exitCode == 0) {
val filteredStdout = filterBuggyLines(output.stdout)
request.onTextReady(filteredStdout)
} else {
request.onError("Movefmt", output.stderr)
}
}
})
runProcessWithProgressIndicator(indicator)
}
)
.unwrapOrThrow()
}

override fun cancel(): Boolean {
indicator.cancel()
disposable.dispose()
return true
}

override fun isRunUnderProgress(): Boolean = true
}
}

private fun filterBuggyLines(stdout: String): String {
return stdout.lines()
.takeWhile { !it.contains("files successfully formatted") }
.joinToString("\n")
}

override fun getNotificationGroupId(): String = "Move Language"

override fun getName(): String = "movefmt"

companion object {
private const val MOVEFMT_ERROR = "movefmt error"

private enum class FormattingReason {
ReformatCode,
ReformatCodeBeforeCommit,
Implicit
}

private fun getFormattingReason(): FormattingReason =
when (CommandProcessor.getInstance().currentCommandName) {
ReformatCodeProcessor.getCommandName() -> ReformatCode
FormatterUtil.getReformatBeforeCommitCommandName() -> ReformatCodeBeforeCommit
else -> Implicit
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.move.cli.externalFormatter

import com.intellij.openapi.Disposable
import com.intellij.openapi.components.*
import com.intellij.openapi.components.Service.Level.PROJECT
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.util.text.nullize
import org.move.cli.externalFormatter.MoveFmtSettingsService.MoveFmtSettings
import org.move.cli.runConfigurations.aptos.Aptos
import org.move.cli.settings.MvProjectSettingsServiceBase
import org.move.cli.settings.aptosCliPath
import org.move.cli.settings.isValidExecutable
import org.move.cli.tools.Movefmt
import org.move.openapiext.RootPluginDisposable

private const val SERVICE_NAME: String = "org.move.MoveFmtSettingsService"

@State(
name = SERVICE_NAME,
storages = [Storage(StoragePathMacros.WORKSPACE_FILE)],
)
@Service(PROJECT)
class MoveFmtSettingsService(
project: Project,
): MvProjectSettingsServiceBase<MoveFmtSettings>(project, MoveFmtSettings()) {

val useMovefmt: Boolean get() = state.useMovefmt
// val runMovefmtOnSave: Boolean get() = state.runMovefmtOnSave

val movefmtPath: String? get() = state.movefmtPath.nullize()
val additionalArguments: String get() = state.additionalArguments
val envs: Map<String, String> get() = state.envs

class MoveFmtSettings: MvProjectSettingsBase<MoveFmtSettings>() {
var useMovefmt by property(false)
// var runMovefmtOnSave by property(false)

var movefmtPath by property("") { it.isEmpty() }
var additionalArguments by property("") { it.isEmpty() }
var envs by map<String, String>()

override fun copy(): MoveFmtSettings {
val state = MoveFmtSettings()
state.copyFrom(this)
return state
}
}

override fun createSettingsChangedEvent(
oldEvent: MoveFmtSettings,
newEvent: MoveFmtSettings
): SettingsChangedEvent = SettingsChangedEvent(oldEvent, newEvent)

class SettingsChangedEvent(
oldState: MoveFmtSettings,
newState: MoveFmtSettings
): SettingsChangedEventBase<MoveFmtSettings>(oldState, newState)
}

val Project.movefmtSettings: MoveFmtSettingsService get() = service<MoveFmtSettingsService>()

fun Project.getMovefmt(disposable: Disposable): Movefmt? {
val settings = this.movefmtSettings
val movefmtPath = settings.movefmtPath?.toNioPathOrNull()?.takeIf { it.isValidExecutable() } ?: return null
return Movefmt(movefmtPath, disposable)
}

14 changes: 9 additions & 5 deletions src/main/kotlin/org/move/cli/settings/VersionLabel.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.move.cli.settings

import com.intellij.openapi.Disposable
import com.intellij.execution.configuration.EnvironmentVariablesData
import com.intellij.openapi.util.CheckedDisposable
import com.intellij.ui.JBColor
import com.intellij.ui.components.JBLabel
import org.move.cli.runConfigurations.AptosCommandLine
import org.move.cli.tools.MvCommandLine
import org.move.openapiext.UiDebouncer
import org.move.openapiext.checkIsBackgroundThread
import org.move.openapiext.common.isUnitTestMode
Expand Down Expand Up @@ -32,13 +32,14 @@ open class TextOrErrorLabel(icon: Icon?): JBLabel(icon) {

class VersionLabel(
parentDisposable: CheckedDisposable,
private val envs: EnvironmentVariablesData = EnvironmentVariablesData.DEFAULT,
private val versionUpdateListener: (() -> Unit)? = null
):
TextOrErrorLabel(null) {

private val versionUpdateDebouncer = UiDebouncer(parentDisposable)

fun updateAndNotifyListeners(execPath: Path?) {
fun update(execPath: Path?) {
versionUpdateDebouncer.update(
onPooledThread = {
if (!isUnitTestMode) {
Expand All @@ -48,8 +49,11 @@ class VersionLabel(
return@update null
}

val commandLineArgs =
AptosCommandLine(null, listOf("--version"), workingDirectory = null)
val commandLineArgs = MvCommandLine(
listOf("--version"),
workingDirectory = null,
environmentVariables = envs
)
commandLineArgs
.toGeneralCommandLine(execPath)
.execute()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class ChooseAptosCliPanel(versionUpdateListener: (() -> Unit)?): Disposable {
onTextChanged = { _ ->
updateVersion()
})
private val versionLabel = VersionLabel(innerDisposable, versionUpdateListener)
private val versionLabel = VersionLabel(innerDisposable, versionUpdateListener = versionUpdateListener)

private val bundledRadioButton = JBRadioButton("Bundled")
private val localRadioButton = JBRadioButton("Local")
Expand Down Expand Up @@ -194,7 +194,7 @@ class ChooseAptosCliPanel(versionUpdateListener: (() -> Unit)?): Disposable {
private fun updateVersion() {
val aptosPath =
if (isBundledSelected) AptosExecType.bundledAptosCLIPath else localPathField.text.toNioPathOrNull()
versionLabel.updateAndNotifyListeners(aptosPath)
versionLabel.update(aptosPath)
}

fun updateAptosSdks(sdkPath: String) {
Expand Down
Loading

0 comments on commit ab8044f

Please sign in to comment.