Skip to content

Commit b5288b0

Browse files
authored
Add automatic database detection and binary control (#6)
* Add Database autoconfig This commit adds the ability for the plugin to automatically configure database connectivity to the Encore app. * Add settings panel to Tools section of settings
1 parent 24cfb85 commit b5288b0

14 files changed

Lines changed: 482 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33
# Encore IntelliJ Plugin Changelog
44

55
## [Unreleased]
6+
### Added
7+
- From Encore v1.9.3 services databases can be automatically detected and configured within the IDE.
8+
This will add both the local database used by `encore run` and the test database used by `encore test`
9+
- New settings panel which allows the plugin to be configured to use a different encore binary (useful when testing features)
610

711
## [0.0.3]
8-
### Bugs
9-
- Fix support for directory tests (previously only file and function level tests worked).
12+
### Bugs
13+
- Fix support for directory tests (previously only file and function level tests worked).
1014
- Changed the `newapi` live template to fix a typo and renamed the parameters prop to `p`
1115

1216
## [0.0.2]
1317
### Added
1418
- Added support for running unit tests of Encore applications in GoLand
1519
- Added a live template `newapi` for creating a new API easily within GoLand
16-
- Add syntax highlighting and icon for `encore.app` files
20+
- Add syntax highlighting and icon for `encore.app` files

gradle.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
pluginGroup = dev.encore.intellij
55
pluginName = Encore
66
# SemVer format -> https://semver.org
7-
pluginVersion = 0.0.3
7+
pluginVersion = 0.1.0
88

99
# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
1010
# for insight into build numbers and IntelliJ Platform versions.
@@ -18,7 +18,7 @@ platformDownloadSources = true
1818

1919
# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
2020
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
21-
platformPlugins = org.jetbrains.plugins.go
21+
platformPlugins = org.jetbrains.plugins.go, com.intellij.database
2222

2323
# Gradle Releases -> https://github.com/gradle/gradle/releases
2424
gradleVersion = 7.5.1
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dev.encore.intellij
2+
3+
import com.intellij.openapi.diagnostic.Logger
4+
import com.intellij.util.text.SemVer
5+
6+
object Encore {
7+
val LOG: Logger = Logger.getInstance(Encore.javaClass)
8+
9+
/* Is encore installed? */
10+
@Volatile
11+
var encoreInstalled = false
12+
13+
/* What version is Encore? */
14+
@Volatile
15+
var encoreVersion: SemVer? = null
16+
17+
/*
18+
Returns true if Encore is at least at the given release
19+
*/
20+
fun isAtLeast(major: Int, minor: Int, patch: Int): Boolean {
21+
val version = encoreVersion ?: return false
22+
23+
return version.isGreaterOrEqualThan(major, minor, patch)
24+
}
25+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package dev.encore.intellij
2+
3+
import com.intellij.openapi.project.Project
4+
import com.intellij.openapi.startup.StartupActivity
5+
import com.intellij.util.text.SemVer
6+
import dev.encore.intellij.sqldb.Detector
7+
import dev.encore.intellij.utils.isInEncoreApp
8+
import dev.encore.intellij.utils.runEncoreCommand
9+
10+
/*
11+
* Startup Activity allows us to run blocking external calls to the encore daemon to extract information
12+
* about the project, which we can then store and reference at runtime
13+
*/
14+
class StartupActivity : StartupActivity.Background, StartupActivity.RequiredForSmartMode {
15+
override fun runActivity(project: Project) {
16+
// Skip our startup if it's not an Encore app
17+
if (!isInEncoreApp(project)) {
18+
return
19+
}
20+
21+
Encore.LOG.info("Performing startup activity for Encore...")
22+
23+
// Verify Encore is installed
24+
val versionOutput = runEncoreCommand(project, "version") ?: return
25+
if (!versionOutput.startsWith("encore version ")) {
26+
return
27+
}
28+
Encore.encoreInstalled = true
29+
30+
// Get the version
31+
val versionString = versionOutput.trim().lines()[0].removePrefix("encore version ")
32+
if (versionString.startsWith("v")) {
33+
Encore.encoreVersion = SemVer.parseFromText(versionString.removePrefix("v"))
34+
} else {
35+
// If the version string doesn't start with v then it's a dev build, so default to 999.999.999
36+
Encore.encoreVersion = SemVer(versionString, 999, 999, 999)
37+
}
38+
Encore.LOG.info("Verified that Encore is installed and running version ${Encore.encoreVersion}")
39+
40+
// Ask encore for the database connection info
41+
if (Encore.isAtLeast(1, 9, 2)) {
42+
Encore.LOG.info("Getting database connection information from Encore...")
43+
Detector.loadDatabaseConnection(project)
44+
}
45+
}
46+
}

src/main/kotlin/dev/encore/intellij/liveTemplates/EncoreContext.kt

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,10 @@ package dev.encore.intellij.liveTemplates
22

33
import com.intellij.codeInsight.template.TemplateActionContext
44
import com.intellij.codeInsight.template.TemplateContextType
5-
import com.intellij.psi.PsiDirectory
5+
import dev.encore.intellij.utils.isInEncoreApp
66

77
class EncoreContext : TemplateContextType("ENCORE_GO_FILE", "Encore Go File") {
88
override fun isInContext(templateActionContext: TemplateActionContext): Boolean {
9-
return isEncoreApp(templateActionContext.file.originalFile.containingDirectory) && templateActionContext.file.name.endsWith(".go")
10-
}
11-
12-
private fun isEncoreApp(dir: PsiDirectory?): Boolean {
13-
if (dir == null) {
14-
return false
15-
}
16-
17-
val appFile = dir.findFile("encore.app")
18-
if (appFile != null && appFile.isValid) {
19-
return true
20-
}
21-
22-
return isEncoreApp(dir.parentDirectory)
9+
return isInEncoreApp(templateActionContext.file.originalFile.containingDirectory) && templateActionContext.file.name.endsWith(".go")
2310
}
2411
}

src/main/kotlin/dev/encore/intellij/runconfig/EncoreRunConfig.kt

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import com.goide.execution.testing.GoTestRunConfiguration
88
import com.intellij.execution.configurations.RunnerSettings
99
import com.intellij.execution.target.TargetedCommandLineBuilder
1010
import com.intellij.execution.target.value.TargetValue
11-
import com.intellij.openapi.project.rootManager
11+
import dev.encore.intellij.settings.settingsState
12+
import dev.encore.intellij.utils.isInEncoreApp
1213

1314
class EncoreRunConfig : GoRunConfigurationExtension() {
1415
override fun isApplicableFor(configuration: GoRunConfigurationBase<*>): Boolean {
1516
if (configuration is GoTestRunConfiguration) {
16-
return isEncoreApp(configuration)
17+
return isInEncoreApp(configuration.defaultModule)
1718
}
1819
return false
1920
}
@@ -23,7 +24,7 @@ class EncoreRunConfig : GoRunConfigurationExtension() {
2324
runnerSettings: RunnerSettings?
2425
): Boolean {
2526
if (applicableConfiguration is GoTestRunConfiguration) {
26-
return isEncoreApp(applicableConfiguration)
27+
return isInEncoreApp(applicableConfiguration.defaultModule)
2728
}
2829
return false
2930
}
@@ -50,18 +51,8 @@ class EncoreRunConfig : GoRunConfigurationExtension() {
5051
}
5152
}
5253
if (useEncoreBinary) {
53-
cmdLine.exePath = TargetValue.fixed("encore")
54+
cmdLine.exePath = TargetValue.fixed(settingsState().encoreBinary)
5455
}
5556
super.patchCommandLine(configuration, runnerSettings, cmdLine, runnerId, state, commandLineType)
5657
}
57-
58-
private fun isEncoreApp(configuration: GoRunConfigurationBase<*>): Boolean {
59-
for (folder in configuration.getDefaultModule().rootManager.contentRoots) {
60-
val appFile = folder.findChild("encore.app")
61-
if (appFile != null && appFile.exists()) {
62-
return true
63-
}
64-
}
65-
return false
66-
}
6758
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dev.encore.intellij.settings
2+
3+
import com.intellij.openapi.options.Configurable
4+
import javax.swing.JComponent
5+
6+
class Settings : Configurable {
7+
private var mySettingsComponent: SettingsPanel? = null
8+
9+
override fun createComponent(): JComponent {
10+
mySettingsComponent = SettingsPanel()
11+
return mySettingsComponent!!.getPanel()
12+
}
13+
14+
override fun isModified(): Boolean {
15+
val settings = settingsState()
16+
var modified = false
17+
modified = modified || mySettingsComponent!!.getEnable() != settings.enabled
18+
modified = modified || mySettingsComponent!!.getFile() != settings.encoreBinary
19+
20+
return modified
21+
}
22+
23+
override fun apply() {
24+
val settings = settingsState()
25+
settings.enabled = mySettingsComponent!!.getEnable()
26+
settings.encoreBinary = mySettingsComponent!!.getFile()
27+
}
28+
29+
override fun reset() {
30+
val settings = settingsState()
31+
mySettingsComponent!!.setEnable(settings.enabled)
32+
mySettingsComponent!!.setFile(settings.encoreBinary)
33+
}
34+
35+
override fun disposeUIResources() {
36+
mySettingsComponent = null
37+
}
38+
39+
override fun getDisplayName(): String {
40+
return "Encore"
41+
}
42+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package dev.encore.intellij.settings
2+
3+
import com.intellij.openapi.ui.TextComponentAccessor
4+
import com.intellij.openapi.ui.TextFieldWithBrowseButton
5+
import com.intellij.ui.components.JBCheckBox
6+
import com.intellij.ui.components.JBLabel
7+
import com.intellij.util.ui.FormBuilder
8+
import javax.swing.JComponent
9+
import javax.swing.JPanel
10+
11+
class SettingsPanel {
12+
private val myMainPanel: JPanel
13+
private val enableStatus: JBCheckBox = JBCheckBox("Enable Encore plugin", true)
14+
private val file = TextFieldWithBrowseButton()
15+
16+
init {
17+
myMainPanel = FormBuilder.createFormBuilder()
18+
.addSeparator()
19+
.addLabeledComponent("Enable plugin", enableStatus)
20+
.addLabeledComponent("Path to Encore binary", file)
21+
.addComponent(JBLabel("If changing the Encore binary, you will need to reopen your project to pickup the new version"))
22+
.addComponentFillVertically(JPanel(), 0)
23+
.panel
24+
}
25+
26+
fun getPanel(): JPanel {
27+
file.addBrowseFolderListener(
28+
"",
29+
"Path to encore binary",
30+
null,
31+
encoreBinaryDescriptor(),
32+
TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT,
33+
)
34+
35+
return myMainPanel
36+
}
37+
38+
fun getPreferredFocusedComponent(): JComponent {
39+
return enableStatus
40+
}
41+
42+
fun getEnable(): Boolean {
43+
return enableStatus.isSelected
44+
}
45+
46+
fun setEnable(newStatus: Boolean) {
47+
enableStatus.isSelected = newStatus
48+
}
49+
50+
fun getFile(): String {
51+
return file.text
52+
}
53+
54+
fun setFile(newFile: String) {
55+
file.text = newFile
56+
}
57+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dev.encore.intellij.settings
2+
3+
import com.intellij.openapi.application.ApplicationManager
4+
import com.intellij.openapi.components.PersistentStateComponent
5+
import com.intellij.openapi.components.State
6+
import com.intellij.openapi.components.Storage
7+
import com.intellij.util.xmlb.XmlSerializerUtil
8+
9+
@State(
10+
name = "SettingsState",
11+
storages = [Storage("EncorePlugin.xml")]
12+
)
13+
class SettingsState : PersistentStateComponent<SettingsState> {
14+
var enabled: Boolean = true
15+
var encoreBinary: String = "encore"
16+
17+
override fun getState(): SettingsState {
18+
return this
19+
}
20+
21+
override fun loadState(state: SettingsState) {
22+
XmlSerializerUtil.copyBean(state, this)
23+
}
24+
}
25+
26+
fun settingsState(): SettingsState {
27+
return ApplicationManager.getApplication().getService(SettingsState::class.java)
28+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package dev.encore.intellij.settings
2+
3+
import com.intellij.openapi.fileChooser.FileChooserDescriptor
4+
import com.intellij.openapi.vfs.VirtualFile
5+
import java.util.concurrent.TimeUnit
6+
7+
class encoreBinaryDescriptor: FileChooserDescriptor(true, false, false, false, false, false) {
8+
override fun validateSelectedFiles(files: Array<out VirtualFile>) {
9+
if (files.isEmpty()) {
10+
throw Exception("You must select an path to the encore binary")
11+
}
12+
13+
if (files.size > 1) {
14+
throw Exception("You must only pick one binary")
15+
}
16+
17+
if (files[0].isDirectory) {
18+
throw Exception("Cannot be passed a directory")
19+
}
20+
21+
val command = "${files[0].path} version"
22+
val parts = command.split("\\s".toRegex())
23+
val proc = ProcessBuilder(*parts.toTypedArray())
24+
.redirectOutput(ProcessBuilder.Redirect.PIPE)
25+
.redirectError(ProcessBuilder.Redirect.PIPE)
26+
.start()
27+
28+
proc.waitFor(5, TimeUnit.SECONDS)
29+
if (proc.exitValue() != 0) {
30+
val error = proc.errorStream.bufferedReader().readText()
31+
throw Exception("When querying for the Encore version, got an error:\n\n${error}")
32+
}
33+
proc.destroy()
34+
val versionString = proc.inputStream.bufferedReader().readText()
35+
if (!versionString.trim().startsWith("encore version ")) {
36+
throw Exception("Binary did not report `encore version ` as a response to a request for version parameters")
37+
}
38+
}
39+
}

0 commit comments

Comments
 (0)