generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: initial Intelij plugin (#2564)
Thanks to @bradleydwyer I have tested it out and added Hermit support so we run FTL with the right env. It's not perfect but I think we should just get this in an iterate. #2549 --------- Co-authored-by: Bradley Dwyer <bradleydwyer@tbd.email>
- Loading branch information
1 parent
76d4b9e
commit eb2bd33
Showing
17 changed files
with
782 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
plugins { | ||
id("java") | ||
id("org.jetbrains.kotlin.jvm") version "1.9.24" | ||
id("org.jetbrains.intellij") version "1.17.3" | ||
} | ||
|
||
group = "xyz.block.ftl" | ||
version = "1.0-SNAPSHOT" | ||
|
||
repositories { | ||
mavenCentral() | ||
} | ||
|
||
// Configure Gradle IntelliJ Plugin | ||
// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html | ||
intellij { | ||
version.set("2024.1.3") | ||
type.set("IU") // Target IDE Platform | ||
|
||
plugins.set(listOf(/* Plugin Dependencies */)) | ||
} | ||
|
||
tasks { | ||
// Set the JVM compatibility versions | ||
withType<JavaCompile> { | ||
sourceCompatibility = "17" | ||
targetCompatibility = "17" | ||
} | ||
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> { | ||
kotlinOptions.jvmTarget = "17" | ||
} | ||
|
||
patchPluginXml { | ||
sinceBuild.set("241") | ||
untilBuild.set("242.*") | ||
} | ||
|
||
signPlugin { | ||
certificateChain.set(System.getenv("CERTIFICATE_CHAIN")) | ||
privateKey.set(System.getenv("PRIVATE_KEY")) | ||
password.set(System.getenv("PRIVATE_KEY_PASSWORD")) | ||
} | ||
|
||
publishPlugin { | ||
token.set(System.getenv("PUBLISH_TOKEN")) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib | ||
kotlin.stdlib.default.dependency = false | ||
|
||
# Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html | ||
org.gradle.configuration-cache = true | ||
|
||
# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html | ||
org.gradle.caching = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
pluginManagement { | ||
repositories { | ||
mavenCentral() | ||
gradlePluginPortal() | ||
} | ||
} | ||
|
||
rootProject.name = "intellij" |
10 changes: 10 additions & 0 deletions
10
extensions/intellij/src/main/kotlin/xyz/block/ftl/intellij/CustomLsp4jClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package xyz.block.ftl.intellij | ||
|
||
import com.intellij.platform.lsp.api.Lsp4jClient | ||
import com.intellij.platform.lsp.api.LspServerNotificationsHandler | ||
|
||
class CustomLsp4jClient(handler: LspServerNotificationsHandler) : Lsp4jClient(handler) { | ||
override fun telemetryEvent(`object`: Any) { | ||
super.telemetryEvent(`object`) | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
extensions/intellij/src/main/kotlin/xyz/block/ftl/intellij/FTL.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package xyz.block.ftl.intellij | ||
|
||
import com.intellij.openapi.application.ApplicationManager | ||
|
||
fun runOnEDT(runnable: () -> Unit) { | ||
ApplicationManager.getApplication().invokeLater { | ||
runnable() | ||
} | ||
} |
86 changes: 86 additions & 0 deletions
86
extensions/intellij/src/main/kotlin/xyz/block/ftl/intellij/FTLLspServerDescriptor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package xyz.block.ftl.intellij | ||
|
||
import com.intellij.execution.configurations.GeneralCommandLine | ||
import com.intellij.execution.process.OSProcessHandler | ||
import com.intellij.execution.process.ProcessAdapter | ||
import com.intellij.execution.process.ProcessEvent | ||
import com.intellij.ide.DataManager | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.openapi.util.Key | ||
import com.intellij.openapi.vfs.VirtualFile | ||
import com.intellij.openapi.wm.ToolWindowManager | ||
import com.intellij.platform.lsp.api.LspServerNotificationsHandler | ||
import com.intellij.platform.lsp.api.ProjectWideLspServerDescriptor | ||
import com.intellij.tools.ToolsCustomizer | ||
import xyz.block.ftl.intellij.toolWindow.FTLMessagesToolWindowFactory | ||
import java.util.concurrent.CompletableFuture | ||
import java.util.regex.Pattern | ||
|
||
class FTLLspServerDescriptor(project: Project) : ProjectWideLspServerDescriptor(project, "FTL") { | ||
override fun isSupportedFile(file: VirtualFile) = file.extension == "go" | ||
|
||
override fun createLsp4jClient(handler: LspServerNotificationsHandler): CustomLsp4jClient { | ||
return CustomLsp4jClient(handler) | ||
} | ||
|
||
override fun createCommandLine(): GeneralCommandLine { | ||
val settings = AppSettings.getInstance().state | ||
val generalCommandLine = | ||
GeneralCommandLine(listOf(settings.lspServerPath) + settings.lspServerArguments.split(Pattern.compile("\\s+"))) | ||
generalCommandLine.setWorkDirectory(project.basePath) | ||
displayMessageInToolWindow("LSP Server Command: " + generalCommandLine.commandLineString) | ||
displayMessageInToolWindow("Working Directory: " + generalCommandLine.workDirectory) | ||
try { | ||
// Hermit support, we need to get the environment variables so we use the correct FTL | ||
val result = CompletableFuture<GeneralCommandLine>() | ||
runOnEDT { | ||
val toolWindow = ToolWindowManager.getInstance(project).getToolWindow("FTL") | ||
if (toolWindow != null) { | ||
val dataContext = DataManager.getInstance().getDataContext(toolWindow.component) | ||
val customizeCommandLine = | ||
ToolsCustomizer.customizeCommandLine(generalCommandLine, dataContext) | ||
result.complete(customizeCommandLine) | ||
} | ||
} | ||
val res = result.get() | ||
return if (res != null) res else generalCommandLine | ||
} catch (e: Exception) { | ||
displayMessageInToolWindow("Failed to customize LSP Server Command: " + e.message) | ||
} | ||
return generalCommandLine | ||
} | ||
|
||
override fun startServerProcess(): OSProcessHandler { | ||
displayMessageInToolWindow("Starting FTL LSP Server") | ||
val processHandler = super.startServerProcess() | ||
processHandler.addProcessListener(object : ProcessAdapter() { | ||
|
||
override fun startNotified(event: ProcessEvent) { | ||
super.startNotified(event) | ||
displayMessageInToolWindow("LSP Started") | ||
} | ||
|
||
override fun processTerminated(event: ProcessEvent) { | ||
super.processTerminated(event) | ||
displayMessageInToolWindow("LSP Terminated") | ||
} | ||
|
||
override fun processWillTerminate(event: ProcessEvent, willBeDestroyed: Boolean) { | ||
super.processWillTerminate(event, willBeDestroyed) | ||
displayMessageInToolWindow("LSP Will Terminate") | ||
} | ||
|
||
override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { | ||
val message = event.text.trim() | ||
if (message.isNotBlank()) { | ||
displayMessageInToolWindow(message) | ||
} | ||
} | ||
}) | ||
return processHandler | ||
} | ||
|
||
private fun displayMessageInToolWindow(message: String) { | ||
FTLMessagesToolWindowFactory.Util.displayMessageInToolWindow(project, message) | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
extensions/intellij/src/main/kotlin/xyz/block/ftl/intellij/FTLLspServerService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package xyz.block.ftl.intellij | ||
|
||
import com.intellij.openapi.components.Service | ||
import com.intellij.openapi.project.Project | ||
|
||
@Service(Service.Level.PROJECT) | ||
class FTLLspServerService(val project: Project) { | ||
val lspServerSupportProvider = FTLLspServerSupportProvider() | ||
|
||
companion object { | ||
fun getInstance(project: Project): FTLLspServerService { | ||
return project.getService(FTLLspServerService::class.java) | ||
} | ||
} | ||
} |
123 changes: 123 additions & 0 deletions
123
extensions/intellij/src/main/kotlin/xyz/block/ftl/intellij/FTLLspServerSupportProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package xyz.block.ftl.intellij | ||
|
||
import com.intellij.execution.configurations.GeneralCommandLine | ||
import com.intellij.execution.process.OSProcessHandler | ||
import com.intellij.icons.AllIcons.Icons | ||
import com.intellij.openapi.project.Project | ||
import com.intellij.openapi.vfs.VirtualFile | ||
import com.intellij.platform.lsp.api.LspServer | ||
import com.intellij.platform.lsp.api.LspServerDescriptor.Companion.LOG | ||
import com.intellij.platform.lsp.api.LspServerManager | ||
import com.intellij.platform.lsp.api.LspServerManagerListener | ||
import com.intellij.platform.lsp.api.LspServerState | ||
import com.intellij.platform.lsp.api.LspServerSupportProvider | ||
import com.intellij.platform.lsp.api.lsWidget.LspServerWidgetItem | ||
import com.intellij.util.io.BaseOutputReader | ||
import com.intellij.util.messages.Topic | ||
import xyz.block.ftl.intellij.toolWindow.FTLMessagesToolWindowFactory.Util.displayMessageInToolWindow | ||
import java.util.regex.Pattern | ||
|
||
interface FTLLSPNotifier { | ||
fun lspServerStateChange(state: LspServerState) | ||
|
||
companion object { | ||
@Topic.ProjectLevel | ||
val SERVER_STATE_CHANGE_TOPIC: Topic<FTLLSPNotifier> = Topic.create( | ||
"FTL Server State Changed", | ||
FTLLSPNotifier::class.java | ||
) | ||
} | ||
} | ||
|
||
class FTLLspServerSupportProvider : LspServerSupportProvider { | ||
private var listenerAdded: Boolean = false | ||
|
||
override fun createLspServerWidgetItem(lspServer: LspServer, currentFile: VirtualFile?): LspServerWidgetItem = | ||
LspServerWidgetItem( | ||
lspServer = lspServer, | ||
currentFile = currentFile, | ||
settingsPageClass = FTLSettingsConfigurable::class.java, | ||
widgetMainActionBaseIcon = Icons.Ide.MenuArrow | ||
) | ||
|
||
override fun fileOpened( | ||
project: Project, | ||
file: VirtualFile, | ||
serverStarter: LspServerSupportProvider.LspServerStarter | ||
) { | ||
if (!listenerAdded) { | ||
try { | ||
listenerAdded = true | ||
val lspServerManager = LspServerManager.getInstance(project) | ||
lspServerManager.addLspServerManagerListener(listener = object : LspServerManagerListener { | ||
override fun serverStateChanged(lspServer: LspServer) { | ||
val publisher = project.messageBus.syncPublisher(FTLLSPNotifier.SERVER_STATE_CHANGE_TOPIC) | ||
publisher.lspServerStateChange(lspServer.state) | ||
} | ||
}, parentDisposable = { }, sendEventsForExistingServers = true) | ||
} catch (e: Exception) { | ||
listenerAdded = false | ||
} | ||
} | ||
|
||
val isFtlSupportLanguage = file.extension == "go" || file.extension == "kt" || file.extension == "java" | ||
if (isFtlSupportLanguage && hasFtlProjectFile(project)) { | ||
serverStarter.ensureServerStarted(FTLLspServerDescriptor(project)) | ||
} | ||
} | ||
|
||
private fun hasFtlProjectFile(project: Project): Boolean { | ||
val projectBaseDir = project.baseDir ?: return false | ||
val ftlProjectFile = projectBaseDir.findChild("ftl-project.toml") | ||
return ftlProjectFile != null && ftlProjectFile.exists() | ||
} | ||
|
||
fun startLspServer(project: Project) { | ||
val lspServerManager = LspServerManager.getInstance(project) | ||
lspServerManager.startServersIfNeeded(FTLLspServerSupportProvider::class.java) | ||
} | ||
|
||
fun stopLspServer(project: Project): OSProcessHandler? { | ||
return when (getLspServerStatus(project)) { | ||
LspServerState.ShutdownUnexpectedly -> { | ||
stopViaCommand(project) | ||
} | ||
|
||
else -> { | ||
val lspServerManager = LspServerManager.getInstance(project) | ||
lspServerManager.stopServers(FTLLspServerSupportProvider::class.java) | ||
null | ||
} | ||
} | ||
} | ||
|
||
private fun stopViaCommand(project: Project): OSProcessHandler { | ||
val settings = AppSettings.getInstance().state | ||
val generalCommandLine = | ||
GeneralCommandLine(listOf(settings.lspServerPath) + settings.lspServerStopArguments.split(Pattern.compile("\\s+"))).withCharset( | ||
Charsets.UTF_8 | ||
) | ||
generalCommandLine.setWorkDirectory(project.basePath) | ||
displayMessageInToolWindow(project, "LSP Server Command: " + generalCommandLine.commandLineString) | ||
displayMessageInToolWindow(project, "Working Directory: " + generalCommandLine.workDirectory) | ||
|
||
LOG.info("$this: stopping LSP server: $generalCommandLine") | ||
val process: OSProcessHandler = object : OSProcessHandler(generalCommandLine) { | ||
override fun readerOptions(): BaseOutputReader.Options = BaseOutputReader.Options.forMostlySilentProcess() | ||
} | ||
|
||
return process | ||
} | ||
|
||
fun restartLspServer(project: Project) { | ||
val lspServerManager = LspServerManager.getInstance(project) | ||
lspServerManager.stopAndRestartIfNeeded(FTLLspServerSupportProvider::class.java) | ||
} | ||
|
||
fun getLspServerStatus(project: Project): LspServerState { | ||
val lspServerManager = LspServerManager.getInstance(project) | ||
val server = lspServerManager.getServersForProvider(FTLLspServerSupportProvider::class.java).firstOrNull() | ||
|
||
return server?.state ?: LspServerState.ShutdownNormally | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
extensions/intellij/src/main/kotlin/xyz/block/ftl/intellij/FTLSettings.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package xyz.block.ftl.intellij | ||
|
||
import com.intellij.openapi.application.ApplicationManager | ||
import com.intellij.openapi.components.PersistentStateComponent | ||
import com.intellij.openapi.components.Service | ||
import com.intellij.openapi.components.State | ||
import com.intellij.openapi.components.Storage | ||
import org.jetbrains.annotations.NonNls | ||
|
||
@State( | ||
name = "org.intellij.sdk.settings.AppSettings", | ||
storages = [Storage("SdkSettingsPlugin.xml")] | ||
) | ||
@Service | ||
class AppSettings : PersistentStateComponent<AppSettings.State> { | ||
|
||
data class State( | ||
@NonNls var lspServerPath: String = "ftl", | ||
var lspServerArguments: String = "--recreate --lsp", | ||
var lspServerStopArguments: String = "serve --stop", | ||
var autoRestartLspServer: Boolean = false, | ||
) | ||
|
||
private var myState = State() | ||
|
||
companion object { | ||
fun getInstance(): AppSettings { | ||
return ApplicationManager.getApplication().getService(AppSettings::class.java) | ||
} | ||
} | ||
|
||
override fun getState(): State { | ||
return myState | ||
} | ||
|
||
override fun loadState(state: State) { | ||
myState = state | ||
} | ||
} |
Oops, something went wrong.