From d012875cb6a26b943e73b96937c7c77c0b0d6bab Mon Sep 17 00:00:00 2001 From: Luke Gareth Ribchester Date: Fri, 31 May 2024 14:59:07 +0200 Subject: [PATCH] feat: support the Pact 5 language server (#75) * refactor(settings): rename `pactPath` and `pactLanguageServerPath` Signed-off-by: Luke Gareth Ribchester * wip(settings): create `PactSettingsUtils` Signed-off-by: Luke Gareth Ribchester * wip(settings): update `AppSettingsComponent` Signed-off-by: Luke Gareth Ribchester * docs(readme): update settings label Signed-off-by: Luke Gareth Ribchester * refactor(settings): rename `AppSettingsComponent` Signed-off-by: Luke Gareth Ribchester * refactor(settings): rename `AppSettingsConfigurable` Signed-off-by: Luke Gareth Ribchester * refactor(settings): rename `AppSettingsState` Signed-off-by: Luke Gareth Ribchester * wip(settings): update `PactSettingsEditor` Signed-off-by: Luke Gareth Ribchester * wip(lsp): update `PactLspServerDescriptor` Signed-off-by: Luke Gareth Ribchester * wip(settings): update `PactSettingsUtils` Signed-off-by: Luke Gareth Ribchester * wip(settings): update `PactSettingsUtils` Signed-off-by: Luke Gareth Ribchester * wip(settings): update `PactSettingsUtils` Signed-off-by: Luke Gareth Ribchester * wip(settings): update `PactSettingsEditor` Signed-off-by: Luke Gareth Ribchester * wip(settings): update `PactSettingsEditor` Signed-off-by: Luke Gareth Ribchester * wip(settings): update `PactRunConfigurationOptions` Signed-off-by: Luke Gareth Ribchester * build: update minor version to `0.5.0` Signed-off-by: Luke Gareth Ribchester * docs(changelog): add entry Signed-off-by: Luke Gareth Ribchester --------- Signed-off-by: Luke Gareth Ribchester --- CHANGELOG.md | 4 + README.md | 2 +- gradle.properties | 2 +- .../ide/runner/PactRunConfigurationOptions.kt | 10 +- .../pact/ide/runner/PactSettingsEditor.kt | 58 ++++--- .../pact/ide/settings/AppSettingsComponent.kt | 148 ------------------ .../ide/settings/AppSettingsConfigurable.kt | 51 ------ .../ide/settings/PactSettingsComponent.kt | 88 +++++++++++ .../ide/settings/PactSettingsConfigurable.kt | 51 ++++++ ...pSettingsState.kt => PactSettingsState.kt} | 20 +-- .../pact/ide/settings/PactSettingsUtils.kt | 68 ++++++++ .../pact/lsp/PactLspServerDescriptor.kt | 18 ++- .../pact/lsp/PactLspServerSupportProvider.kt | 8 +- src/main/resources/META-INF/plugin.xml | 6 +- src/main/resources/icons/logo.png | Bin 0 -> 31277 bytes 15 files changed, 286 insertions(+), 248 deletions(-) delete mode 100644 src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsComponent.kt delete mode 100644 src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsConfigurable.kt create mode 100644 src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsComponent.kt create mode 100644 src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsConfigurable.kt rename src/main/kotlin/io/kadena/pact/ide/settings/{AppSettingsState.kt => PactSettingsState.kt} (69%) create mode 100644 src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsUtils.kt create mode 100644 src/main/resources/icons/logo.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a52ae8..df19229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ## [Unreleased] +### Added + +- Support for the [Pact 5 Language Server (LSP)](https://github.com/kadena-io/pact-5) + ## [0.4.0] - 2024-05-13 ### Added diff --git a/README.md b/README.md index 482ca2c..c6785b7 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Configuration is required to enable functionality from the Pact Language Server: 1. Navigate to Settings/Preferences 2. Select Languages & Frameworks > Pact -3. Specify your Pact and Pact Language Server paths +3. Specify your Pact Compiler and Pact Language Server paths #### Homebrew diff --git a/gradle.properties b/gradle.properties index dbef16f..219a4bf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = io.kadena.pact pluginName = Pact pluginRepositoryUrl = https://github.com/lukeribchester/pact-intellij # SemVer format -> https://semver.org -pluginVersion = 0.4.0 +pluginVersion = 0.5.0 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 232 diff --git a/src/main/kotlin/io/kadena/pact/ide/runner/PactRunConfigurationOptions.kt b/src/main/kotlin/io/kadena/pact/ide/runner/PactRunConfigurationOptions.kt index 32e0d6e..8ad7de4 100644 --- a/src/main/kotlin/io/kadena/pact/ide/runner/PactRunConfigurationOptions.kt +++ b/src/main/kotlin/io/kadena/pact/ide/runner/PactRunConfigurationOptions.kt @@ -2,14 +2,14 @@ package io.kadena.pact.ide.runner import com.intellij.execution.configurations.RunConfigurationOptions import com.intellij.openapi.components.StoredProperty -import io.kadena.pact.ide.settings.AppSettingsState +import io.kadena.pact.ide.settings.PactSettingsState class PactRunConfigurationOptions : RunConfigurationOptions() { - private val settings: AppSettingsState = AppSettingsState.instance + private val settings: PactSettingsState = PactSettingsState.instance private val _compilerPath: StoredProperty = - string(settings.pactPath.takeIf { it.isNotBlank() } ?: "") + string(settings.compilerPath.takeIf { it.isNotBlank() } ?: "") .provideDelegate(this, "compilerPath") private val _modulePath: StoredProperty = @@ -17,13 +17,13 @@ class PactRunConfigurationOptions : RunConfigurationOptions() { .provideDelegate(this, "modulePath") var compilerPath: String - get() = _compilerPath.getValue(this).toString() + get() = _compilerPath.getValue(this).takeIf { !it.equals(null) } ?: "" set(newCompilerPath) { _compilerPath.setValue(this, newCompilerPath) } var modulePath: String - get() = _modulePath.getValue(this).toString() + get() = _modulePath.getValue(this).takeIf { !it.equals(null) } ?: "" set(newModulePath) { _modulePath.setValue(this, newModulePath) } diff --git a/src/main/kotlin/io/kadena/pact/ide/runner/PactSettingsEditor.kt b/src/main/kotlin/io/kadena/pact/ide/runner/PactSettingsEditor.kt index ec9e851..f6bc1cb 100644 --- a/src/main/kotlin/io/kadena/pact/ide/runner/PactSettingsEditor.kt +++ b/src/main/kotlin/io/kadena/pact/ide/runner/PactSettingsEditor.kt @@ -1,44 +1,66 @@ package io.kadena.pact.ide.runner -import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory +import com.intellij.openapi.fileChooser.FileChooserDescriptor import com.intellij.openapi.options.SettingsEditor +import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.TextFieldWithBrowseButton -import com.intellij.util.ui.FormBuilder +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.RowLayout +import io.kadena.pact.ide.settings.createComboBox import org.jetbrains.annotations.NotNull import javax.swing.JComponent import javax.swing.JPanel +import com.intellij.ui.dsl.builder.panel as dslPanel class PactSettingsEditor : SettingsEditor() { private val panel: JPanel - private val compilerPathField = TextFieldWithBrowseButton() - private val modulePathField = TextFieldWithBrowseButton() + private val compilerPathField: ComboBox = createComboBox( + "Select a Pact Compiler", "Choose an executable file" + ) - init { - compilerPathField.addBrowseFolderListener( - "Select a Pact Compiler", null, null, - FileChooserDescriptorFactory.createSingleFileDescriptor() + private val modulePathField = TextFieldWithBrowseButton().apply { + addBrowseFolderListener( + "Select a Pact Module", + "Choose a Pact file", + null, + FileChooserDescriptor( + true, + false, + false, + false, + false, + false + ) ) + } - modulePathField.addBrowseFolderListener( - "Select a Pact Module", null, null, - FileChooserDescriptorFactory.createSingleFileDescriptor() - ) + init { + panel = dslPanel { + // Compiler path + row("Pact compiler:") { + cell(compilerPathField) + .align(Align.FILL) + .resizableColumn() + }.layout(RowLayout.LABEL_ALIGNED) - panel = FormBuilder.createFormBuilder() - .addLabeledComponent("Pact compiler:", compilerPathField) - .addLabeledComponent("Pact module:", modulePathField) - .panel + // Module path + row("Pact module:") { + cell(modulePathField) + .align(Align.FILL) + .resizableColumn() + }.layout(RowLayout.LABEL_ALIGNED) + } } override fun resetEditorFrom(pactRunConfiguration: PactRunConfiguration) { - compilerPathField.text = pactRunConfiguration.compilerPath + compilerPathField.selectedItem = pactRunConfiguration.compilerPath modulePathField.text = pactRunConfiguration.modulePath } override fun applyEditorTo(@NotNull pactRunConfiguration: PactRunConfiguration) { - pactRunConfiguration.compilerPath = compilerPathField.text + pactRunConfiguration.compilerPath = compilerPathField.selectedItem?.toString() ?: "" pactRunConfiguration.modulePath = modulePathField.text } diff --git a/src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsComponent.kt b/src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsComponent.kt deleted file mode 100644 index b6ef964..0000000 --- a/src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsComponent.kt +++ /dev/null @@ -1,148 +0,0 @@ -package io.kadena.pact.ide.settings - -import com.intellij.openapi.fileChooser.FileChooser -import com.intellij.openapi.fileChooser.FileChooserDescriptor -import com.intellij.openapi.ui.ComboBox -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.ui.components.JBLabel -import com.intellij.ui.components.JBPanel -import com.intellij.ui.util.maximumWidth -import com.intellij.ui.util.minimumWidth -import com.intellij.ui.util.preferredWidth -import com.intellij.util.ui.FormBuilder -import org.jetbrains.annotations.NotNull -import java.awt.Dimension -import javax.swing.Box -import javax.swing.BoxLayout -import javax.swing.JButton -import javax.swing.JComponent -import javax.swing.JPanel - - -/** - * Supports creating and managing a [JPanel] for the Settings Dialog. - */ -class AppSettingsComponent { - val panel: JPanel - - private val pactPathLabel = JBLabel("Pact:") - private val pactPathField = ComboBox() - private val pactPathBrowseButton = JButton("...") - - private val pactLanguageServerPathLabel = JBLabel("Language Server (LSP):") - private val pactLanguageServerPathField = ComboBox() - private val pactLanguageServerPathBrowseButton = JButton("...") - - init { - panel = FormBuilder.createFormBuilder() - .addComponent(pactPathPanel()) - .addComponent(pactLanguageServerPathPanel()) - .addComponentFillVertically(JPanel(), 0) - .panel - } - - val preferredFocusedComponent: JComponent - get() = pactPathField - - @get:NotNull - var pactPath: String? - get() = pactPathField.selectedItem?.toString() - set(newPath) { - pactPathField.selectedItem = newPath - } - - @get:NotNull - var pactLanguageServerPath: String? - get() = pactLanguageServerPathField.selectedItem?.toString() - set(newPath) { - pactLanguageServerPathField.selectedItem = newPath - } - - private fun pactPathPanel(): JBPanel> { - pactPathField.apply { - addItem("/path/to/pact") - isEditable = true - preferredWidth = 300 - } - pactPathBrowseButton.addActionListener { - onBrowseForPactPath( - "Select a Pact Compiler", - "Choose an executable file", - pactPathField - ) - } - pactPathBrowseButton.apply { - preferredWidth = 30 - minimumWidth = 30 - maximumWidth = 30 - } - - return JBPanel>().apply { - // Align the components horizontally - layout = BoxLayout(this, BoxLayout.X_AXIS) - - val gap = Box.createRigidArea(Dimension(10, 0)) - - add(pactPathLabel) - add(gap) - add(pactPathField) - add(pactPathBrowseButton) - } - } - - private fun pactLanguageServerPathPanel(): JBPanel> { - pactLanguageServerPathField.apply { - addItem("/path/to/pact-lsp") - isEditable = true - preferredWidth = 300 - } - pactLanguageServerPathBrowseButton.addActionListener { - onBrowseForPactPath( - "Select a Pact Language Server (LSP)", - "Choose an executable file", - pactLanguageServerPathField - ) - } - pactLanguageServerPathBrowseButton.apply { - preferredWidth = 30 - minimumWidth = 30 - maximumWidth = 30 - } - - return JBPanel>().apply { - // Align the components horizontally - layout = BoxLayout(this, BoxLayout.X_AXIS) - - val gap = Box.createRigidArea(Dimension(10, 0)) - - add(pactLanguageServerPathLabel) - add(gap) - add(pactLanguageServerPathField) - add(pactLanguageServerPathBrowseButton) - } - } - - private fun onBrowseForPactPath(title: String, description: String, field: ComboBox) { - // Configure the file browser window - val fileChooserDescriptor = FileChooserDescriptor( - true, - true, - false, - false, - false, - false - ) - .withTitle(title) - .withDescription(description) - - // Open the file browser window - val file: VirtualFile? = FileChooser.chooseFile(fileChooserDescriptor, null, null) - file?.let { - val path = it.path - - // Add the path to the ComboBox and select it - field.addItem(path) - field.selectedItem = path - } - } -} diff --git a/src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsConfigurable.kt b/src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsConfigurable.kt deleted file mode 100644 index ba8ab9e..0000000 --- a/src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsConfigurable.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.kadena.pact.ide.settings - -import com.intellij.openapi.options.Configurable -import org.jetbrains.annotations.Nullable -import javax.swing.JComponent - - -/** - * Provides controller functionality for application settings. - */ -internal class AppSettingsConfigurable : Configurable { - private var appSettingsComponent: AppSettingsComponent? = null - - override fun getDisplayName(): String { - return "Pact" - } - - fun preferredFocusedComponent(): JComponent { - return appSettingsComponent!!.preferredFocusedComponent - } - - @Nullable - override fun createComponent(): JComponent { - appSettingsComponent = AppSettingsComponent() - return appSettingsComponent!!.panel - } - - override fun isModified(): Boolean { - val settings: AppSettingsState = AppSettingsState.instance - var modified: Boolean = !appSettingsComponent?.pactPath.equals(settings.pactPath) - modified = modified or (!appSettingsComponent?.pactLanguageServerPath.equals(settings.pactLanguageServerPath)) - return modified - } - - override fun apply() { - val settings: AppSettingsState = AppSettingsState.instance - settings.pactPath = appSettingsComponent?.pactPath.toString() - settings.pactLanguageServerPath = appSettingsComponent?.pactLanguageServerPath.toString() - settings.notifyAppSettingsStateChanged(settings) - } - - override fun reset() { - val settings: AppSettingsState = AppSettingsState.instance - appSettingsComponent?.pactPath = settings.pactPath - appSettingsComponent?.pactLanguageServerPath = settings.pactLanguageServerPath - } - - override fun disposeUIResources() { - appSettingsComponent = null - } -} diff --git a/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsComponent.kt b/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsComponent.kt new file mode 100644 index 0000000..3f24080 --- /dev/null +++ b/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsComponent.kt @@ -0,0 +1,88 @@ +package io.kadena.pact.ide.settings + +import com.intellij.openapi.ui.ComboBox +import com.intellij.ui.dsl.builder.Align +import com.intellij.ui.dsl.builder.RowLayout +import com.intellij.ui.dsl.builder.TopGap +import org.jetbrains.annotations.NotNull +import java.awt.Image +import javax.swing.ImageIcon +import javax.swing.JComponent +import javax.swing.JLabel +import javax.swing.JPanel +import com.intellij.ui.dsl.builder.panel as dslPanel + + +/** + * Supports creating and managing a [JPanel] for the Settings Dialog. + */ +class PactSettingsComponent { + val panel: JPanel + + private val compilerPathField: ComboBox = createComboBox( + "Select a Pact Compiler", "Choose an executable file" + ) + + private val languageServerPathField: ComboBox = createComboBox( + "Select a Pact Language Server", "Choose an executable file" + ) + + init { + val logo = ImageIcon(javaClass.getResource("/icons/logo.png")) + .image.getScaledInstance(64, 64, Image.SCALE_SMOOTH) + + panel = dslPanel { + // Compiler path + row("Pact compiler:") { + cell(compilerPathField) + .comment("Pact compiler versions 4.x.x and 5.x.x are supported".trimIndent()) + .align(Align.FILL) + .resizableColumn() + }.layout(RowLayout.LABEL_ALIGNED) + + // Language server path + row("Pact language server:") { + cell(languageServerPathField) + .comment("Specify a Pact 5 compiler path or a standalone Pact 4 language server path".trimIndent()) + .align(Align.FILL) + .resizableColumn() + }.layout(RowLayout.LABEL_ALIGNED) + + separator().topGap(TopGap.SMALL) + + // Check for updates + row { + cell(JLabel(ImageIcon(logo))) + text( + """ +

Check for updates

+ Visit the + Pact 4 + and + Pact 5 + repositories +
+ to stay up to date with the latest versions + """.trimIndent() + ) + } + } + } + + val preferredFocusedComponent: JComponent + get() = compilerPathField + + @get:NotNull + var compilerPath: String? + get() = compilerPathField.selectedItem?.toString() + set(newPath) { + compilerPathField.selectedItem = newPath + } + + @get:NotNull + var languageServerPath: String? + get() = languageServerPathField.selectedItem?.toString() + set(newPath) { + languageServerPathField.selectedItem = newPath + } +} diff --git a/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsConfigurable.kt b/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsConfigurable.kt new file mode 100644 index 0000000..436fcff --- /dev/null +++ b/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsConfigurable.kt @@ -0,0 +1,51 @@ +package io.kadena.pact.ide.settings + +import com.intellij.openapi.options.Configurable +import org.jetbrains.annotations.Nullable +import javax.swing.JComponent + + +/** + * Provides controller functionality for application settings. + */ +internal class PactSettingsConfigurable : Configurable { + private var pactSettingsComponent: PactSettingsComponent? = null + + override fun getDisplayName(): String { + return "Pact" + } + + fun preferredFocusedComponent(): JComponent { + return pactSettingsComponent!!.preferredFocusedComponent + } + + @Nullable + override fun createComponent(): JComponent { + pactSettingsComponent = PactSettingsComponent() + return pactSettingsComponent!!.panel + } + + override fun isModified(): Boolean { + val settings: PactSettingsState = PactSettingsState.instance + var modified: Boolean = !pactSettingsComponent?.compilerPath.equals(settings.compilerPath) + modified = modified or (!pactSettingsComponent?.languageServerPath.equals(settings.languageServerPath)) + return modified + } + + override fun apply() { + val settings: PactSettingsState = PactSettingsState.instance + settings.compilerPath = pactSettingsComponent?.compilerPath.toString() + settings.languageServerPath = pactSettingsComponent?.languageServerPath.toString() + settings.notifyAppSettingsStateChanged(settings) + } + + override fun reset() { + val settings: PactSettingsState = PactSettingsState.instance + pactSettingsComponent?.compilerPath = settings.compilerPath + pactSettingsComponent?.languageServerPath = settings.languageServerPath + } + + override fun disposeUIResources() { + pactSettingsComponent = null + } +} diff --git a/src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsState.kt b/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsState.kt similarity index 69% rename from src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsState.kt rename to src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsState.kt index 15efbdf..9a8753e 100644 --- a/src/main/kotlin/io/kadena/pact/ide/settings/AppSettingsState.kt +++ b/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsState.kt @@ -15,40 +15,40 @@ import org.jetbrains.annotations.Nullable * these persistent application settings are stored. */ @State( - name = "io.kadena.pact.ide.settings.AppSettingsState", + name = "io.kadena.pact.ide.settings.PactSettingsState", storages = [Storage("PactPlugin.xml")] ) -class AppSettingsState : PersistentStateComponent { - var pactPath: String = "" - var pactLanguageServerPath: String = "" +class PactSettingsState : PersistentStateComponent { + var compilerPath: String = "" + var languageServerPath: String = "" private val listeners = mutableSetOf() @Nullable - override fun getState(): AppSettingsState { + override fun getState(): PactSettingsState { return this } - override fun loadState(@NotNull state: AppSettingsState) { + override fun loadState(@NotNull state: PactSettingsState) { XmlSerializerUtil.copyBean(state, this) } companion object { - val instance: AppSettingsState - get() = ApplicationManager.getApplication().getService(AppSettingsState::class.java) + val instance: PactSettingsState + get() = ApplicationManager.getApplication().getService(PactSettingsState::class.java) } fun addAppSettingsStateListener(listener: AppSettingsStateListener) { listeners.add(listener) } - fun notifyAppSettingsStateChanged(state: AppSettingsState) { + fun notifyAppSettingsStateChanged(state: PactSettingsState) { for (listener in listeners) { listener.onAppSettingsStateChanged(state) } } interface AppSettingsStateListener { - fun onAppSettingsStateChanged(state: AppSettingsState) + fun onAppSettingsStateChanged(state: PactSettingsState) } } diff --git a/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsUtils.kt b/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsUtils.kt new file mode 100644 index 0000000..e257349 --- /dev/null +++ b/src/main/kotlin/io/kadena/pact/ide/settings/PactSettingsUtils.kt @@ -0,0 +1,68 @@ +package io.kadena.pact.ide.settings + +import com.intellij.icons.AllIcons +import com.intellij.openapi.fileChooser.FileChooser +import com.intellij.openapi.fileChooser.FileChooserDescriptor +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.ui.components.fields.ExtendableTextComponent +import com.intellij.ui.components.fields.ExtendableTextField +import java.awt.Dimension +import javax.swing.JTextField +import javax.swing.plaf.basic.BasicComboBoxEditor + + +fun createComboBox(title: String, description: String): ComboBox { + val comboBox: ComboBox = ComboBox() + + val browseExtension = ExtendableTextComponent.Extension.create( + AllIcons.General.OpenDisk, AllIcons.General.OpenDiskHover, "Browse files" + ) { + // Configure the file browser window + val fileChooserDescriptor = FileChooserDescriptor( + true, + false, + false, + false, + false, + false + ) + .withTitle(title) + .withDescription(description) + + // Set the initial file browser window directory + val directory = LocalFileSystem.getInstance().findFileByPath( + (comboBox.selectedItem as? String).takeIf { !it.isNullOrEmpty() } ?: System.getProperty("user.home") + ) + + // Open the file browser window + val file: VirtualFile? = FileChooser.chooseFile(fileChooserDescriptor, null, null, directory) + + file?.let { + // Add the path to the ComboBox and select it + val path = it.path + comboBox.addItem(path) + comboBox.selectedItem = path + } + } + + comboBox.apply { + isEnabled = true + isEditable = true + preferredSize = Dimension(300, preferredSize.height) + editor = object : BasicComboBoxEditor() { + + override fun createEditorComponent(): JTextField { + val editor = ExtendableTextField().apply { + addExtension(browseExtension) + border = null + } + + return editor + } + } + } + + return comboBox +} diff --git a/src/main/kotlin/io/kadena/pact/lsp/PactLspServerDescriptor.kt b/src/main/kotlin/io/kadena/pact/lsp/PactLspServerDescriptor.kt index bd47b5c..7c514e7 100644 --- a/src/main/kotlin/io/kadena/pact/lsp/PactLspServerDescriptor.kt +++ b/src/main/kotlin/io/kadena/pact/lsp/PactLspServerDescriptor.kt @@ -7,7 +7,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.platform.lsp.api.ProjectWideLspServerDescriptor -import io.kadena.pact.ide.settings.AppSettingsState +import io.kadena.pact.ide.settings.PactSettingsState import java.io.File class PactLspServerDescriptor(project: Project) : ProjectWideLspServerDescriptor(project, "Pact") { @@ -32,32 +32,36 @@ class PactLspServerDescriptor(project: Project) : ProjectWideLspServerDescriptor // return pluginPath?.let { Paths.get(it, executableRelativePath).toString() } // } - fun doesExecutableExist(executablePath: String): Boolean { + private fun doesExecutableExist(executablePath: String): Boolean { val file = File(executablePath) return file.exists() && file.isFile } + private fun isIntegratedLanguageServer(): Boolean { + return PactSettingsState.instance.languageServerPath == PactSettingsState.instance.compilerPath + } + override fun isSupportedFile(file: VirtualFile) = file.extension == "pact" override fun createCommandLine(): GeneralCommandLine { // Retrieve the configured Pact Language Server executable path - val pactLanguageServerPath = AppSettingsState.instance.pactLanguageServerPath + val languageServerPath = PactSettingsState.instance.languageServerPath - if (pactLanguageServerPath == "" || !doesExecutableExist(pactLanguageServerPath)) { + if (languageServerPath == "" || !doesExecutableExist(languageServerPath)) { throw ExecutionException("Pact Language Server (LSP) executable not found") } // Start the Pact Language Server - return GeneralCommandLine(pactLanguageServerPath).apply { + return GeneralCommandLine(languageServerPath).apply { withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) withCharset(Charsets.UTF_8) - addParameter("--stdio") + if (isIntegratedLanguageServer()) addParameter("--lsp") } } override fun createInitializationOptions(): String { // Retrieve the configured Pact executable path - val pactPath = AppSettingsState.instance.pactPath + val pactPath = PactSettingsState.instance.compilerPath val pactExe = JsonObject() pactExe.addProperty("pactExe", pactPath) diff --git a/src/main/kotlin/io/kadena/pact/lsp/PactLspServerSupportProvider.kt b/src/main/kotlin/io/kadena/pact/lsp/PactLspServerSupportProvider.kt index 7c49db6..dc1f977 100644 --- a/src/main/kotlin/io/kadena/pact/lsp/PactLspServerSupportProvider.kt +++ b/src/main/kotlin/io/kadena/pact/lsp/PactLspServerSupportProvider.kt @@ -4,14 +4,14 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.platform.lsp.api.LspServerManager import com.intellij.platform.lsp.api.LspServerSupportProvider -import io.kadena.pact.ide.settings.AppSettingsState +import io.kadena.pact.ide.settings.PactSettingsState -class PactLspServerSupportProvider : LspServerSupportProvider, AppSettingsState.AppSettingsStateListener { +class PactLspServerSupportProvider : LspServerSupportProvider, PactSettingsState.AppSettingsStateListener { private var _project: Project? = null init { - val settings = AppSettingsState.instance + val settings = PactSettingsState.instance settings.addAppSettingsStateListener(this) } @@ -26,7 +26,7 @@ class PactLspServerSupportProvider : LspServerSupportProvider, AppSettingsState. } } - override fun onAppSettingsStateChanged(state: AppSettingsState) { + override fun onAppSettingsStateChanged(state: PactSettingsState) { restart() } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 6cb20c7..877dd31 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -18,12 +18,12 @@ + serviceImplementation="io.kadena.pact.ide.settings.PactSettingsState"/> diff --git a/src/main/resources/icons/logo.png b/src/main/resources/icons/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9b0dd922d6c25e2d85ba8265b8937d50874e1114 GIT binary patch literal 31277 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 literal 0 HcmV?d00001