├── .idea
├── .name
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── kotlinc.xml
├── vcs.xml
├── .gitignore
├── compiler.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
└── jarRepositories.xml
├── plugin-api-test-client
├── settings.gradle.kts
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── src
│ ├── test
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── plugin
│ │ │ │ └── testviews
│ │ │ │ ├── Role.java
│ │ │ │ ├── UserRepository.java
│ │ │ │ ├── CustomButton.java
│ │ │ │ ├── HelloWorldView.java
│ │ │ │ ├── CustomView.java
│ │ │ │ ├── SecurityConfig.java
│ │ │ │ ├── User.java
│ │ │ │ └── UserDetailsServiceImpl.java
│ │ └── resources
│ │ │ └── samples
│ │ │ └── image.png
│ └── main
│ │ └── java
│ │ └── com
│ │ └── vaadin
│ │ └── plugin
│ │ ├── TestApplication.java
│ │ ├── Message.java
│ │ └── Client.java
├── .idea
│ ├── kotlinc.xml
│ ├── vcs.xml
│ ├── .gitignore
│ ├── misc.xml
│ ├── compiler.xml
│ ├── gradle.xml
│ └── jarRepositories.xml
├── .gitignore
├── build.gradle.kts
└── gradlew.bat
├── plugin
├── src
│ ├── main
│ │ ├── resources
│ │ │ ├── META-INF
│ │ │ │ ├── vaadin-with-microservices-jvm.xml
│ │ │ │ ├── vaadin-injections.xml
│ │ │ │ ├── vaadin-with-ultimate.xml
│ │ │ │ ├── vaadin-with-microservices.xml
│ │ │ │ ├── vaadin-with-javascript.xml
│ │ │ │ └── pluginIcon.svg
│ │ │ ├── icon.png
│ │ │ ├── hotswap-agent.jar
│ │ │ └── vaadin
│ │ │ │ ├── flow-language-injections.xml
│ │ │ │ └── icons
│ │ │ │ ├── module.svg
│ │ │ │ ├── vaadin.svg
│ │ │ │ ├── vaadin_dark.svg
│ │ │ │ ├── rerun.svg
│ │ │ │ ├── rerun_dark.svg
│ │ │ │ ├── hilla.svg
│ │ │ │ ├── debug.svg
│ │ │ │ └── debug_dark.svg
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── vaadin
│ │ │ └── plugin
│ │ │ ├── copilot
│ │ │ ├── handler
│ │ │ │ ├── Handler.kt
│ │ │ │ ├── HandlerResponse.kt
│ │ │ │ ├── RefreshHandler.kt
│ │ │ │ ├── GetModulePathsHandler.kt
│ │ │ │ ├── RedoHandler.kt
│ │ │ │ ├── HeartbeatHandler.kt
│ │ │ │ ├── GetVaadinRoutesHandler.kt
│ │ │ │ ├── ReloadMavenModuleHandler.kt
│ │ │ │ ├── WriteBase64FileHandler.kt
│ │ │ │ ├── CompileFilesHandler.kt
│ │ │ │ ├── GetVaadinEntitiesHandler.kt
│ │ │ │ ├── GetVaadinComponentsHandler.kt
│ │ │ │ ├── GetVaadinSecurityHandler.kt
│ │ │ │ ├── RestartApplicationHandler.kt
│ │ │ │ ├── DeleteFileHandler.kt
│ │ │ │ ├── ShowInIdeHandler.kt
│ │ │ │ ├── GetVaadinVersionHandler.kt
│ │ │ │ ├── UndoHandler.kt
│ │ │ │ ├── AbstractHandler.kt
│ │ │ │ └── WriteFileHandler.kt
│ │ │ ├── CommandRequest.kt
│ │ │ ├── service
│ │ │ │ ├── CopilotDotfileService.kt
│ │ │ │ ├── CopilotRestService.kt
│ │ │ │ ├── CompilationStatusManagerService.kt
│ │ │ │ ├── CopilotDotfileServiceImpl.kt
│ │ │ │ └── CopilotUndoManager.kt
│ │ │ ├── RestUtil.kt
│ │ │ ├── listeners
│ │ │ │ ├── CopilotVaadinProjectListener.kt
│ │ │ │ └── CopilotDynamicPluginListener.kt
│ │ │ ├── DefaultProgramPatcher.kt
│ │ │ └── CopilotErrorHandler.kt
│ │ │ ├── endpoints
│ │ │ ├── Entity.kt
│ │ │ ├── VaadinRoute.kt
│ │ │ ├── VaadinSecurity.kt
│ │ │ ├── VaadinUserDetails.kt
│ │ │ ├── VaadinComponent.kt
│ │ │ ├── VaadinHillaEndpointsProvider.kt
│ │ │ ├── VaadinImplicitUsageProvider.kt
│ │ │ ├── VaadinEndpointsProvider.kt
│ │ │ ├── VaadinFlowEndpointsProvider.kt
│ │ │ ├── VaadinReferenceContributor.kt
│ │ │ └── VaadinUrlResolver.kt
│ │ │ ├── starter
│ │ │ ├── DownloadableModel.kt
│ │ │ ├── StarterSupportMatrixElement.kt
│ │ │ ├── StarterSupport.kt
│ │ │ ├── HelloWorldModel.kt
│ │ │ └── StarterProjectModel.kt
│ │ │ ├── utils
│ │ │ ├── ArtifactIdUtil.kt
│ │ │ ├── VaadinIcons.kt
│ │ │ ├── VaadinProjectUtil.kt
│ │ │ ├── AmpliUtil.kt
│ │ │ └── DownloadUtil.kt
│ │ │ ├── listeners
│ │ │ └── VaadinProjectListener.kt
│ │ │ ├── actions
│ │ │ ├── VaadinOnSaveInfoProvider.kt
│ │ │ ├── InstallOrUpdateHotSwapAction.kt
│ │ │ ├── DebugUsingHotSwapAgentAction.kt
│ │ │ ├── VaadinCompileOnSaveActionInfo.kt
│ │ │ └── VaadinCompileOnSaveAction.kt
│ │ │ ├── ui
│ │ │ ├── VaadinStatusBarWidgetFactory.kt
│ │ │ ├── settings
│ │ │ │ ├── VaadinSettings.kt
│ │ │ │ ├── VaadinSettingsConfigurable.kt
│ │ │ │ └── VaadinSettingsComponent.kt
│ │ │ └── VaadinStatusBarWidget.kt
│ │ │ ├── module
│ │ │ ├── StarterProjectPanel.kt
│ │ │ ├── VaadinProjectWizardStep.kt
│ │ │ ├── VaadinProjectWizard.kt
│ │ │ ├── VaadinProjectBuilderAdapter.kt
│ │ │ └── HelloWorldPanel.kt
│ │ │ ├── symbols
│ │ │ ├── HillaSymbolReferenceProvider.kt
│ │ │ ├── HillaSymbol.kt
│ │ │ └── HillaSymbolReference.kt
│ │ │ ├── hotswapagent
│ │ │ ├── BadJBRFoundDialog.kt
│ │ │ ├── HotswapAgentExecutor.kt
│ │ │ ├── HotswapAgentProgramPatcher.kt
│ │ │ └── HotswapAgentRunner.kt
│ │ │ ├── VaadinProjectDetector.kt
│ │ │ └── psi
│ │ │ └── HillaReferenceSearcher.kt
│ └── test
│ │ └── kotlin
│ │ └── com
│ │ └── vaadin
│ │ └── plugin
│ │ ├── hotswapagent
│ │ └── JdkUtilTest.kt
│ │ └── starter
│ │ ├── HelloWorldModelTest.kt
│ │ └── StarterProjectModelTest.kt
├── settings.gradle.kts
├── ampli.json
├── .gitignore
└── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle.kts
├── gradle.properties
├── .gitignore
├── README.md
├── .run
└── Run IDE with Plugin.run.xml
├── gradlew.bat
└── .github
└── workflows
└── validation.yml
/.idea/.name:
--------------------------------------------------------------------------------
1 | intellij-plugin-parent
--------------------------------------------------------------------------------
/plugin-api-test-client/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "plugin-api-test-client"
2 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/META-INF/vaadin-with-microservices-jvm.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vaadin/intellij-plugin/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/plugin/src/main/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vaadin/intellij-plugin/HEAD/plugin/src/main/resources/icon.png
--------------------------------------------------------------------------------
/plugin/src/main/resources/hotswap-agent.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vaadin/intellij-plugin/HEAD/plugin/src/main/resources/hotswap-agent.jar
--------------------------------------------------------------------------------
/plugin-api-test-client/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vaadin/intellij-plugin/HEAD/plugin-api-test-client/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/plugin-api-test-client/src/test/java/com/plugin/testviews/Role.java:
--------------------------------------------------------------------------------
1 | package com.plugin.testviews;
2 |
3 | public enum Role {
4 | USER, ADMIN;
5 | }
--------------------------------------------------------------------------------
/plugin-api-test-client/src/test/resources/samples/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vaadin/intellij-plugin/HEAD/plugin-api-test-client/src/test/resources/samples/image.png
--------------------------------------------------------------------------------
/plugin/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenCentral()
4 | gradlePluginPortal()
5 | }
6 | }
7 |
8 | rootProject.name = "intellij-plugin"
9 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/Handler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | interface Handler {
4 |
5 | fun run(): HandlerResponse
6 | }
7 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/CommandRequest.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot
2 |
3 | data class CommandRequest(val command: String, val projectBasePath: String?, val data: Map)
4 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenCentral()
4 | gradlePluginPortal()
5 | }
6 | }
7 |
8 | rootProject.name = "intellij-plugin-parent"
9 | include("plugin")
10 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/plugin-api-test-client/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/plugin-api-test-client/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/plugin-api-test-client/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/main/java/com/vaadin/plugin/TestApplication.java:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin;
2 |
3 | import org.springframework.boot.autoconfigure.SpringBootApplication;
4 |
5 | @SpringBootApplication
6 | public class TestApplication {
7 | }
8 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/META-INF/vaadin-injections.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/HandlerResponse.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import io.netty.handler.codec.http.HttpResponseStatus
4 |
5 | data class HandlerResponse(val status: HttpResponseStatus, val data: Map? = null)
6 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/Entity.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.psi.HierarchicalMethodSignature
4 |
5 | class Entity(val className: String, val visibleMethods: Collection, val path: String) {}
6 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/test/java/com/plugin/testviews/UserRepository.java:
--------------------------------------------------------------------------------
1 | package com.plugin.testviews;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 |
5 | public interface UserRepository extends JpaRepository {
6 | User findByUsername(String username);
7 | }
--------------------------------------------------------------------------------
/plugin/src/main/resources/META-INF/vaadin-with-ultimate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinRoute.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.psi.PsiAnchor
4 |
5 | class VaadinRoute(val urlMapping: String, val locationString: String, val anchor: PsiAnchor) {
6 | fun isValid(): Boolean = anchor.retrieve() != null
7 | }
8 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/starter/DownloadableModel.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.starter
2 |
3 | import com.intellij.openapi.project.Project
4 |
5 | interface DownloadableModel {
6 |
7 | fun getDownloadLink(project: Project): String
8 |
9 | fun getProjectType(): String
10 | }
11 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/starter/StarterSupportMatrixElement.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.starter
2 |
3 | data class StarterSupportMatrixElement(
4 | val framework: String,
5 | val languages: Collection,
6 | val architectures: Collection,
7 | val buildTools: Collection,
8 | )
9 |
--------------------------------------------------------------------------------
/plugin-api-test-client/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/test/java/com/plugin/testviews/CustomButton.java:
--------------------------------------------------------------------------------
1 | package com.plugin.testviews;
2 |
3 | import com.vaadin.flow.component.button.Button;
4 |
5 | /**
6 | * A simple Vaadin custom component.
7 | */
8 | public class CustomButton extends Button {
9 |
10 | public CustomButton() {
11 | setText("My Custom Button");
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinSecurity.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.psi.PsiAnchor
4 |
5 | class VaadinSecurity(
6 | val className: String,
7 | val origin: String,
8 | val source: String,
9 | val path: String,
10 | val anchor: PsiAnchor,
11 | val loginView: String? = null,
12 | ) {
13 | fun isValid(): Boolean = anchor.retrieve() != null
14 | }
15 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinUserDetails.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.psi.PsiAnchor
4 |
5 | class VaadinUserDetails(
6 | val className: String,
7 | val origin: String,
8 | val source: String,
9 | val path: String,
10 | val anchor: PsiAnchor,
11 | val entity: List? = null,
12 | ) {
13 | fun isValid(): Boolean = anchor.retrieve() != null
14 | }
15 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/utils/ArtifactIdUtil.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.utils
2 |
3 | fun toArtifactId(name: String): String {
4 | return name
5 | .trim()
6 | .replace(Regex("([a-z])([A-Z])"), "$1-$2") // camelCase to kebab-case
7 | .replace(Regex("[\\s_]+"), "-") // spaces/underscores to hyphen
8 | .replace(Regex("[^a-zA-Z0-9-]"), "") // remove invalid chars
9 | .lowercase()
10 | }
11 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
2 | kotlin.stdlib.default.dependency=false
3 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
4 | org.gradle.configuration-cache=true
5 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
6 | org.gradle.caching=true
7 |
8 | kotlin.code.style=official
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/service/CopilotDotfileService.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.service
2 |
3 | import java.io.IOException
4 | import java.nio.file.Path
5 |
6 | interface CopilotDotfileService {
7 |
8 | fun isActive(): Boolean
9 |
10 | @Throws(IOException::class) fun removeDotfile()
11 |
12 | @Throws(IOException::class) fun createDotfile(content: String)
13 |
14 | fun getDotfilePath(): Path?
15 | }
16 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/RefreshHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.project.Project
4 | import com.intellij.openapi.vfs.VirtualFileManager
5 |
6 | class RefreshHandler(project: Project) : AbstractHandler(project) {
7 |
8 | override fun run(): HandlerResponse {
9 | VirtualFileManager.getInstance().asyncRefresh {}
10 | return RESPONSE_OK
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/plugin-api-test-client/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/test/java/com/plugin/testviews/HelloWorldView.java:
--------------------------------------------------------------------------------
1 | package com.plugin.testviews;
2 |
3 | import com.vaadin.flow.component.orderedlayout.VerticalLayout;
4 | import com.vaadin.flow.router.Route;
5 |
6 | /**
7 | * A simple Vaadin view that can be used for testing get routes tool within this project.
8 | * This view is mapped to the route "HelloWorld".
9 | */
10 | @Route("HelloWorld")
11 | public class HelloWorldView extends VerticalLayout {
12 | }
13 |
--------------------------------------------------------------------------------
/plugin/ampli.json:
--------------------------------------------------------------------------------
1 | {
2 | "Zone": "us",
3 | "OrgId": "62972",
4 | "WorkspaceId": "418f2561-c6f7-4d6c-9e33-459672266311",
5 | "SourceId": "755ed219-73a5-4bf7-bd2f-48cf12072780",
6 | "Runtime": "jre:kotlin-ampli",
7 | "Platform": "JRE",
8 | "Language": "Kotlin",
9 | "SDK": "com.amplitude:java-sdk:1.+",
10 | "Path": "./src/main/kotlin/com/amplitude/ampli",
11 | "Branch": "main",
12 | "Version": "1.0.0",
13 | "VersionId": "ac62c3f9-e92b-4502-8a50-3fd8f13c4103"
14 | }
--------------------------------------------------------------------------------
/plugin-api-test-client/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinComponent.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.psi.HierarchicalMethodSignature
4 | import com.intellij.psi.PsiAnchor
5 |
6 | class VaadinComponent(
7 | val className: String,
8 | val origin: String,
9 | val source: String,
10 | val path: String,
11 | val anchor: PsiAnchor,
12 | val visibleMethods: Collection
13 | ) {
14 | fun isValid(): Boolean = anchor.retrieve() != null
15 | }
16 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/RestUtil.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot
2 |
3 | import java.util.UUID
4 | import org.jetbrains.ide.BuiltInServerManager
5 |
6 | class RestUtil {
7 |
8 | companion object {
9 |
10 | private val serviceName = "copilot-" + UUID.randomUUID()
11 |
12 | fun getServiceName(): String = serviceName
13 |
14 | fun getEndpoint(): String {
15 | val port = BuiltInServerManager.getInstance().port
16 | return "http://127.0.0.1:$port/api/" + getServiceName()
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/listeners/VaadinProjectListener.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.listeners
2 |
3 | import com.intellij.openapi.project.Project
4 | import com.intellij.util.messages.Topic
5 | import com.intellij.util.messages.Topic.ProjectLevel
6 | import java.util.*
7 |
8 | interface VaadinProjectListener : EventListener {
9 |
10 | companion object {
11 | @ProjectLevel
12 | val TOPIC: Topic =
13 | Topic.create("Vaadin project detected", VaadinProjectListener::class.java)
14 | }
15 |
16 | fun vaadinProjectDetected(project: Project) {}
17 | }
18 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/actions/VaadinOnSaveInfoProvider.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.actions
2 |
3 | import com.intellij.ide.actionsOnSave.ActionOnSaveContext
4 | import com.intellij.ide.actionsOnSave.ActionOnSaveInfo
5 | import com.intellij.ide.actionsOnSave.ActionOnSaveInfoProvider
6 |
7 | class VaadinOnSaveInfoProvider : ActionOnSaveInfoProvider() {
8 |
9 | override fun getActionOnSaveInfos(p0: ActionOnSaveContext): MutableCollection {
10 | val info: ArrayList = ArrayList()
11 | info.add(VaadinCompileOnSaveActionInfo(p0))
12 | return info
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/META-INF/vaadin-with-microservices.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/test/java/com/plugin/testviews/CustomView.java:
--------------------------------------------------------------------------------
1 | package com.plugin.testviews;
2 |
3 | import com.flowingcode.vaadin.addons.dayofweekselector.DayOfWeekSelector;
4 | import com.vaadin.flow.component.orderedlayout.VerticalLayout;
5 | import com.vaadin.flow.router.Route;
6 |
7 |
8 | /**
9 | * A simple Vaadin view that can be used for testing get routes tool within this project.
10 | * This view is mapped to the route "custom".
11 | */
12 | @Route(value = "custom", autoLayout = false)
13 | public class CustomView extends VerticalLayout {
14 | public CustomView() {
15 | add(new DayOfWeekSelector());
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/plugin-api-test-client/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/ui/VaadinStatusBarWidgetFactory.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.ui
2 |
3 | import com.intellij.openapi.project.Project
4 | import com.intellij.openapi.wm.StatusBarWidget
5 | import com.intellij.openapi.wm.StatusBarWidgetFactory
6 |
7 | class VaadinStatusBarWidgetFactory : StatusBarWidgetFactory {
8 |
9 | override fun getId(): String {
10 | return "VaadinStatusBarWidgetFactory"
11 | }
12 |
13 | override fun getDisplayName(): String {
14 | return "Vaadin"
15 | }
16 |
17 | override fun createWidget(project: Project): StatusBarWidget {
18 | return VaadinStatusBarWidget(project)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/META-INF/vaadin-with-javascript.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | !gradle/wrapper/gradle-wrapper.jar
4 | !**/src/main/**/build/
5 | !**/src/test/**/build/
6 |
7 | ### IntelliJ IDEA ###
8 | .idea/modules.xml
9 | .idea/jarRepositories.xml
10 | .idea/compiler.xml
11 | .idea/libraries/
12 | .intellijPlatform
13 | *.iws
14 | *.iml
15 | *.ipr
16 | out/
17 | !**/src/main/**/out/
18 | !**/src/test/**/out/
19 |
20 | ### Eclipse ###
21 | .apt_generated
22 | .classpath
23 | .factorypath
24 | .project
25 | .settings
26 | .springBeans
27 | .sts4-cache
28 | bin/
29 | !**/src/main/**/bin/
30 | !**/src/test/**/bin/
31 |
32 | ### NetBeans ###
33 | /nbproject/private/
34 | /nbbuild/
35 | /dist/
36 | /nbdist/
37 | /.nb-gradle/
38 |
39 | ### VS Code ###
40 | .vscode/
41 |
42 | ### Mac OS ###
43 | .DS_Store
44 |
45 |
46 | .idea/.copilot-plugin
--------------------------------------------------------------------------------
/plugin/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | !gradle/wrapper/gradle-wrapper.jar
4 | !**/src/main/**/build/
5 | !**/src/test/**/build/
6 |
7 | ### IntelliJ IDEA ###
8 | .idea/modules.xml
9 | .idea/jarRepositories.xml
10 | .idea/compiler.xml
11 | .idea/libraries/
12 | .intellijPlatform
13 | *.iws
14 | *.iml
15 | *.ipr
16 | out/
17 | !**/src/main/**/out/
18 | !**/src/test/**/out/
19 |
20 | ### Eclipse ###
21 | .apt_generated
22 | .classpath
23 | .factorypath
24 | .project
25 | .settings
26 | .springBeans
27 | .sts4-cache
28 | bin/
29 | !**/src/main/**/bin/
30 | !**/src/test/**/bin/
31 |
32 | ### NetBeans ###
33 | /nbproject/private/
34 | /nbbuild/
35 | /dist/
36 | /nbdist/
37 | /.nb-gradle/
38 |
39 | ### VS Code ###
40 | .vscode/
41 |
42 | ### Mac OS ###
43 | .DS_Store
44 |
45 |
46 | .idea/.copilot-plugin
--------------------------------------------------------------------------------
/plugin-api-test-client/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | !gradle/wrapper/gradle-wrapper.jar
4 | !**/src/main/**/build/
5 | !**/src/test/**/build/
6 |
7 | ### IntelliJ IDEA ###
8 | .idea/modules.xml
9 | .idea/jarRepositories.xml
10 | .idea/compiler.xml
11 | .idea/libraries/
12 | .intellijPlatform
13 | *.iws
14 | *.iml
15 | *.ipr
16 | out/
17 | !**/src/main/**/out/
18 | !**/src/test/**/out/
19 |
20 | ### Eclipse ###
21 | .apt_generated
22 | .classpath
23 | .factorypath
24 | .project
25 | .settings
26 | .springBeans
27 | .sts4-cache
28 | bin/
29 | !**/src/main/**/bin/
30 | !**/src/test/**/bin/
31 |
32 | ### NetBeans ###
33 | /nbproject/private/
34 | /nbbuild/
35 | /dist/
36 | /nbdist/
37 | /.nb-gradle/
38 |
39 | ### VS Code ###
40 | .vscode/
41 |
42 | ### Mac OS ###
43 | .DS_Store
44 |
45 |
46 | .idea/.copilot-plugin
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/GetModulePathsHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.project.Project
4 | import com.intellij.openapi.project.guessProjectDir
5 | import com.vaadin.plugin.copilot.CopilotPluginUtil
6 | import com.vaadin.plugin.copilot.CopilotPluginUtil.Companion.getModulesInfo
7 | import io.netty.handler.codec.http.HttpResponseStatus
8 |
9 | class GetModulePathsHandler(project: Project) : AbstractHandler(project) {
10 |
11 | override fun run(): HandlerResponse {
12 | val modules = getModulesInfo(project)
13 | val projectInfo = CopilotPluginUtil.ProjectInfo(project.guessProjectDir()?.path, modules)
14 | val data = mapOf("project" to projectInfo)
15 | return HandlerResponse(HttpResponseStatus.OK, data)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/utils/VaadinIcons.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.utils
2 |
3 | import com.intellij.openapi.util.IconLoader
4 |
5 | class VaadinIcons {
6 |
7 | companion object {
8 |
9 | private const val RESOURCE_PATH = "/vaadin/icons"
10 |
11 | val VAADIN_BLUE = IconLoader.getIcon("$RESOURCE_PATH/module.svg", VaadinIcons::class.java.classLoader)
12 |
13 | val VAADIN = IconLoader.getIcon("$RESOURCE_PATH/vaadin.svg", VaadinIcons::class.java.classLoader)
14 |
15 | val HILLA = IconLoader.getIcon("$RESOURCE_PATH/hilla.svg", VaadinIcons::class.java.classLoader)
16 |
17 | val DEBUG_HOTSWAP = IconLoader.getIcon("$RESOURCE_PATH/debug.svg", VaadinIcons::class.java.classLoader)
18 |
19 | val RERUN_HOTSWAP = IconLoader.getIcon("$RESOURCE_PATH/rerun.svg", VaadinIcons::class.java.classLoader)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/module/StarterProjectPanel.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.module
2 |
3 | import com.intellij.ui.dsl.builder.bindSelected
4 | import com.intellij.ui.dsl.builder.panel
5 | import com.vaadin.plugin.starter.StarterProjectModel
6 |
7 | class StarterProjectPanel(private val model: StarterProjectModel) {
8 |
9 | private val MAX_LINE_LENGTH = 60
10 |
11 | val root = panel {
12 | row("Vaadin Version") {
13 | segmentedButton(setOf(false, true)) { this.text = if (it) "Prerelease" else "Stable" }
14 | .bind(model.usePrereleaseProperty)
15 | }
16 | row { text("Include sample view").bold() }
17 | row { text("A sample view built fully in Java, front to back.", MAX_LINE_LENGTH) }
18 | row("Include sample view") { checkBox("").bindSelected(model.includeFlowProperty) }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/plugin/src/test/kotlin/com/vaadin/plugin/hotswapagent/JdkUtilTest.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.hotswapagent
2 |
3 | import kotlin.test.Test
4 | import kotlin.test.assertEquals
5 | import kotlin.test.assertNull
6 |
7 | class JdkUtilTest {
8 |
9 | @Test
10 | fun parseJavaVersionSupportsCommonFormats() {
11 | val cases =
12 | mapOf(
13 | "17" to 17,
14 | " 17 " to 17,
15 | "1.8" to 8,
16 | "1.8.0_292" to 8,
17 | "11.0.4" to 11,
18 | "21-ea" to 21,
19 | )
20 |
21 | cases.forEach { (input, expected) ->
22 | assertEquals(expected, JdkUtil.parseJavaVersion(input), "Failed for input '$input'")
23 | }
24 | }
25 |
26 | @Test
27 | fun parseJavaVersionReturnsNullWhenNotParsable() {
28 | assertNull(JdkUtil.parseJavaVersion("abc"))
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/module/VaadinProjectWizardStep.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.module
2 |
3 | import com.intellij.ide.util.projectWizard.WizardContext
4 | import com.intellij.ide.wizard.NewProjectWizardStep
5 | import com.intellij.openapi.observable.properties.PropertyGraph
6 | import com.intellij.openapi.util.UserDataHolder
7 | import com.intellij.openapi.util.UserDataHolderBase
8 | import com.intellij.ui.dsl.builder.Panel
9 |
10 | class VaadinProjectWizardStep(override val context: WizardContext, override val propertyGraph: PropertyGraph) :
11 | NewProjectWizardStep {
12 |
13 | override val data: UserDataHolder
14 | get() = UserDataHolderBase()
15 |
16 | override val keywords: NewProjectWizardStep.Keywords
17 | get() = NewProjectWizardStep.Keywords()
18 |
19 | override fun setupUI(builder: Panel) {
20 | VaadinPanel(propertyGraph, context, builder)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/listeners/CopilotVaadinProjectListener.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.listeners
2 |
3 | import com.intellij.openapi.components.service
4 | import com.intellij.openapi.project.DumbService
5 | import com.intellij.openapi.project.Project
6 | import com.vaadin.plugin.copilot.CopilotPluginUtil.Companion.saveDotFile
7 | import com.vaadin.plugin.copilot.service.CopilotDotfileService
8 | import com.vaadin.plugin.listeners.VaadinProjectListener
9 | import com.vaadin.plugin.ui.VaadinStatusBarWidget
10 |
11 | class CopilotVaadinProjectListener : VaadinProjectListener {
12 |
13 | override fun vaadinProjectDetected(project: Project) {
14 | if (!project.isDisposed && !project.service().isActive()) {
15 | saveDotFile(project)
16 | DumbService.getInstance(project).smartInvokeLater { VaadinStatusBarWidget.update(project) }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vaadin IntelliJ Plugin
2 |
3 | Official Vaadin plugin for JetBrains IntelliJ.
4 |
5 | Improves developer experience while working with [Vaadin](https://vaadin.com) framework.
6 | - Helps to create new Vaadin projects using configurable Hello World starters.
7 | - Boosts [Vaadin Copilot](https://vaadin.com/copilot) experience.
8 | - Helps to set up and run projects using HotSwap Agent.
9 | - Provides navigation to [Flow](https://vaadin.com/flow) @Route views in Endpoints tool window.
10 |
11 | Available at [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/23758-vaadin).
12 |
13 | Thanks to [HotSwapHelper](https://github.com/gejun123456/HotSwapHelper) for inspiration and ideas on the Hotswap Agent integration.
14 |
15 | ## API test application
16 |
17 | `plugin-api-test-client` is a simple Spring boot app with tests allowing to easily debug plugin endpoint API calls.
18 |
19 | Please remember to run development IDE (Run Plugin) before running the tests.
20 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/RedoHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.command.undo.UndoManager
4 | import com.intellij.openapi.fileEditor.FileEditor
5 | import com.intellij.openapi.project.Project
6 | import com.intellij.openapi.vfs.VirtualFile
7 |
8 | class RedoHandler(project: Project, data: Map) : UndoHandler(project, data) {
9 |
10 | override fun getOpsCount(vfsFile: VirtualFile): Int {
11 | return getCopilotUndoManager().getRedoCount(vfsFile)
12 | }
13 |
14 | override fun before(vfsFile: VirtualFile) {
15 | getCopilotUndoManager().redoStart(vfsFile)
16 | }
17 |
18 | override fun runManagerAction(undoManager: UndoManager, editor: FileEditor) {
19 | undoManager.redo(editor)
20 | }
21 |
22 | override fun after(vfsFile: VirtualFile) {
23 | getCopilotUndoManager().redoDone(vfsFile)
24 | LOG.info("$vfsFile redo performed")
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/actions/InstallOrUpdateHotSwapAction.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.actions
2 |
3 | import com.intellij.notification.NotificationType
4 | import com.intellij.openapi.actionSystem.AnAction
5 | import com.intellij.openapi.actionSystem.AnActionEvent
6 | import com.vaadin.plugin.copilot.CopilotPluginUtil
7 | import com.vaadin.plugin.utils.VaadinHomeUtil
8 |
9 | class InstallOrUpdateHotSwapAction : AnAction() {
10 |
11 | override fun actionPerformed(p0: AnActionEvent) {
12 | val version = VaadinHomeUtil.updateOrInstallHotSwapJar()
13 | if (version != null) {
14 | CopilotPluginUtil.notify("hotswap-agent.jar:$version installed", NotificationType.INFORMATION, p0.project)
15 | } else {
16 | CopilotPluginUtil.notify(
17 | "Installation of hotswap-agent.jar failed, see logs for details",
18 | NotificationType.ERROR,
19 | p0.project,
20 | )
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/HeartbeatHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.project.Project
4 | import com.vaadin.plugin.copilot.service.CompilationStatusManagerService
5 | import io.netty.handler.codec.http.HttpResponseStatus
6 |
7 | class HeartbeatHandler(project: Project) : AbstractHandler(project) {
8 | companion object {
9 | const val HAS_COMPILATION_ERROR = "hasCompilationError"
10 | const val FILES_CONTAIN_COMPILATION_ERROR = "filesContainCompilationError"
11 | }
12 |
13 | override fun run(): HandlerResponse {
14 | val data: MutableMap = mutableMapOf()
15 | val compilationStatusManagerService = project.getService(CompilationStatusManagerService::class.java)
16 | data[HAS_COMPILATION_ERROR] = compilationStatusManagerService.hasCompilationError()
17 | data[FILES_CONTAIN_COMPILATION_ERROR] = compilationStatusManagerService.getErrorFiles()
18 | return HandlerResponse(HttpResponseStatus.OK, data)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/GetVaadinRoutesHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.ApplicationManager
4 | import com.intellij.openapi.project.Project
5 | import com.intellij.psi.search.GlobalSearchScope
6 | import com.vaadin.plugin.endpoints.findFlowRoutes
7 | import io.netty.handler.codec.http.HttpResponseStatus
8 |
9 | class GetVaadinRoutesHandler(project: Project) : AbstractHandler(project) {
10 |
11 | override fun run(): HandlerResponse {
12 | return ApplicationManager.getApplication().runReadAction {
13 | val flowViews = findFlowRoutes(project, GlobalSearchScope.allScope(project))
14 |
15 | val mapFlowRoute =
16 | flowViews.map { route -> mapOf("route" to route.urlMapping, "classname" to route.locationString) }
17 |
18 | LOG.info("Flow Routes detected: $flowViews")
19 |
20 | HandlerResponse(status = HttpResponseStatus.OK, data = mapOf("routes" to mapFlowRoute))
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/actions/DebugUsingHotSwapAgentAction.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.actions
2 |
3 | import com.intellij.execution.Executor
4 | import com.intellij.execution.ExecutorRegistry
5 | import com.intellij.execution.dashboard.actions.ExecutorAction
6 | import com.intellij.openapi.actionSystem.AnActionEvent
7 | import com.vaadin.plugin.hotswapagent.HotswapAgentExecutor
8 | import com.vaadin.plugin.utils.VaadinIcons
9 |
10 | class DebugUsingHotSwapAgentAction : ExecutorAction() {
11 | override fun update(event: AnActionEvent, isRunning: Boolean) {
12 | if (isRunning) {
13 | event.presentation.text = "Rerun using HotSwapAgent"
14 | event.presentation.icon = VaadinIcons.RERUN_HOTSWAP
15 | } else {
16 | event.presentation.text = "Debug using HotSwapAgent"
17 | event.presentation.icon = VaadinIcons.DEBUG_HOTSWAP
18 | }
19 | }
20 |
21 | override fun getExecutor(): Executor {
22 | return ExecutorRegistry.getInstance().getExecutorById(HotswapAgentExecutor.ID)!!
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/DefaultProgramPatcher.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot
2 |
3 | import com.intellij.execution.Executor
4 | import com.intellij.execution.configurations.JavaParameters
5 | import com.intellij.execution.configurations.RunConfiguration
6 | import com.intellij.execution.configurations.RunProfile
7 | import com.intellij.execution.runners.JavaProgramPatcher
8 | import com.vaadin.plugin.copilot.service.CopilotDotfileService
9 |
10 | class DefaultProgramPatcher : JavaProgramPatcher() {
11 |
12 | override fun patchJavaParameters(executor: Executor?, runProfile: RunProfile?, javaParameters: JavaParameters?) {
13 |
14 | if (javaParameters == null) {
15 | return
16 | }
17 | val paramsList = javaParameters.vmParametersList
18 |
19 | if (runProfile is RunConfiguration) {
20 | runProfile.project.getService(CopilotDotfileService::class.java).getDotfilePath()?.let {
21 | paramsList.add("-Dvaadin.copilot.pluginDotFilePath=${it}")
22 | }
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/test/java/com/plugin/testviews/SecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.plugin.testviews;
2 |
3 | import com.vaadin.flow.spring.security.VaadinWebSecurity;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
8 | import org.springframework.security.crypto.password.NoOpPasswordEncoder;
9 | import org.springframework.security.crypto.password.PasswordEncoder;
10 |
11 | @Configuration
12 | @EnableWebSecurity
13 | public class SecurityConfig extends VaadinWebSecurity {
14 |
15 | @Override
16 | protected void configure(HttpSecurity http) throws Exception {
17 | super.configure(http);
18 | // Set the Login view to use
19 | setLoginView(http, HelloWorldView.class);
20 | }
21 |
22 | @Bean
23 | public PasswordEncoder passwordEncoder() {
24 | return NoOpPasswordEncoder.getInstance();
25 | }
26 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/symbols/HillaSymbolReferenceProvider.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.symbols
2 |
3 | import com.intellij.lang.javascript.psi.JSReferenceExpression
4 | import com.intellij.model.Symbol
5 | import com.intellij.model.psi.PsiExternalReferenceHost
6 | import com.intellij.model.psi.PsiSymbolReference
7 | import com.intellij.model.psi.PsiSymbolReferenceHints
8 | import com.intellij.model.psi.PsiSymbolReferenceProvider
9 | import com.intellij.model.search.SearchRequest
10 | import com.intellij.openapi.project.Project
11 |
12 | internal class HillaSymbolReferenceProvider : PsiSymbolReferenceProvider {
13 |
14 | override fun getReferences(
15 | host: PsiExternalReferenceHost,
16 | hints: PsiSymbolReferenceHints
17 | ): Collection {
18 | if (host !is JSReferenceExpression) {
19 | return emptyList()
20 | }
21 |
22 | return listOf(HillaSymbolReference(host))
23 | }
24 |
25 | override fun getSearchRequests(project: Project, symbol: Symbol): Collection {
26 | return emptyList()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettings.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.ui.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 org.jetbrains.annotations.NotNull
8 |
9 | @State(name = "com.vaadin.plugin.ui.settings.VaadinSettings", storages = [Storage("VaadinSettings.xml")])
10 | internal class VaadinSettings : PersistentStateComponent {
11 | internal class State {
12 | var userId: String? = null
13 | var sendUsageStatistics: Boolean = true
14 | }
15 |
16 | private var myState = State()
17 |
18 | override fun getState(): State {
19 | return myState
20 | }
21 |
22 | override fun loadState(@NotNull state: State) {
23 | myState = state
24 | }
25 |
26 | companion object {
27 | val instance: VaadinSettings
28 | get() = ApplicationManager.getApplication().getService(VaadinSettings::class.java)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/listeners/CopilotDynamicPluginListener.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.listeners
2 |
3 | import com.intellij.ide.plugins.DynamicPluginListener
4 | import com.intellij.ide.plugins.IdeaPluginDescriptor
5 | import com.intellij.openapi.diagnostic.Logger
6 | import com.intellij.openapi.project.Project
7 | import com.vaadin.plugin.copilot.CopilotPluginUtil
8 | import com.vaadin.plugin.copilot.service.CopilotDotfileService
9 |
10 | class CopilotDynamicPluginListener(private val project: Project) : DynamicPluginListener {
11 |
12 | private val LOG: Logger = Logger.getInstance(CopilotDynamicPluginListener::class.java)
13 |
14 | override fun beforePluginUnload(pluginDescriptor: IdeaPluginDescriptor, isUpdate: Boolean) {
15 | project.getService(CopilotDotfileService::class.java).removeDotfile()
16 | LOG.debug("Plugin is going to be unloaded, .copilot-plugin removed")
17 | }
18 |
19 | override fun pluginLoaded(pluginDescriptor: IdeaPluginDescriptor) {
20 | CopilotPluginUtil.saveDotFile(project)
21 | LOG.debug("Plugin loaded, .copilot-plugin created")
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/plugin-api-test-client/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.springframework.boot") version "3.4.0"
3 | id("io.spring.dependency-management") version "1.1.6"
4 | id("java")
5 | }
6 |
7 | group = "com.vaadin.plugin"
8 | version = "unspecified"
9 |
10 | repositories {
11 | mavenCentral()
12 | }
13 |
14 | dependencies {
15 | implementation("org.springframework.boot:spring-boot-starter-web:3.4.2")
16 | implementation("org.springframework.boot:spring-boot-starter-security:3.4.2")
17 | implementation("org.springframework.boot:spring-boot-starter-data-jpa:3.4.2")
18 | implementation("com.vaadin:vaadin-spring:24.6.5")
19 | implementation("com.fasterxml.jackson.core:jackson-databind:2.18.2")
20 | implementation("com.vaadin:vaadin-core:24.6.5")
21 | implementation("jakarta.persistence:jakarta.persistence-api:3.2.0")
22 | implementation("com.flowingcode.vaadin.addons:day-of-week-selector-addon:1.1.0")
23 | testImplementation("org.springframework.boot:spring-boot-starter-test:3.4.2")
24 | }
25 |
26 | tasks.test {
27 | useJUnitPlatform()
28 | testLogging {
29 | events("PASSED", "FAILED", "SKIPPED")
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/ReloadMavenModuleHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.runInEdt
4 | import com.intellij.openapi.project.Project
5 | import org.jetbrains.idea.maven.project.MavenProjectsManager
6 |
7 | class ReloadMavenModuleHandler(project: Project, moduleName: String) : AbstractHandler(project) {
8 |
9 | private val moduleName: String = moduleName as String
10 |
11 | override fun run(): HandlerResponse {
12 | runInEdt {
13 | val mavenProjectsManager = MavenProjectsManager.getInstance(project)
14 | mavenProjectsManager.projects
15 | .firstOrNull { it.displayName == moduleName }
16 | ?.let { mavenProject ->
17 | LOG.debug("Reloading ${mavenProject.displayName} (${project.name})")
18 | mavenProjectsManager.scheduleForceUpdateMavenProject(mavenProject)
19 | return@runInEdt
20 | }
21 | LOG.debug("Reloading of $moduleName failed - content not found")
22 | }
23 | return RESPONSE_OK
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/main/java/com/vaadin/plugin/Message.java:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin;
2 |
3 | import java.util.List;
4 |
5 | public class Message {
6 |
7 | record Command(String command, Object data) {
8 | }
9 |
10 | record CopilotRestRequest(String command, String projectBasePath, Object data) {
11 | }
12 |
13 | record WriteFileMessage(String file, String undoLabel, String content) {
14 | }
15 |
16 | record UndoRedoMessage(List files) {
17 | }
18 |
19 | record ShowInIdeMessage(String file, Integer line, Integer column) {
20 | }
21 |
22 | record RefreshMessage() {
23 | }
24 |
25 | record RestartApplicationMessage() {
26 | }
27 |
28 | record CompileMessage(List files) {
29 | }
30 |
31 | record DeleteMessage(String file) {
32 | }
33 |
34 | record GetVaadinRoutesMessage() {
35 | }
36 |
37 | record GetVaadinVersionMessage() {
38 | }
39 |
40 | record GetVaadinComponentsMessage(boolean includeMethods) {
41 | }
42 |
43 | record GetVaadinPersistenceMessage(boolean includeMethods) {
44 | }
45 |
46 | record GetVaadinSecurityMessage() {
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/.run/Run IDE with Plugin.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | true
20 | true
21 | false
22 |
23 |
24 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/symbols/HillaSymbol.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.symbols
2 |
3 | import com.intellij.model.Pointer
4 | import com.intellij.model.Symbol
5 | import com.intellij.navigation.NavigatableSymbol
6 | import com.intellij.navigation.SymbolNavigationService
7 | import com.intellij.openapi.project.Project
8 | import com.intellij.platform.backend.navigation.NavigationTarget
9 | import com.intellij.psi.PsiElement
10 |
11 | class HillaSymbol(private val target: PsiElement) : NavigatableSymbol {
12 |
13 | override fun createPointer(): Pointer {
14 | return Pointer.hardPointer(this)
15 | }
16 |
17 | override fun getNavigationTargets(project: Project): Collection {
18 | if (target.project != project) return emptyList()
19 | return listOf(SymbolNavigationService.getInstance().psiElementNavigationTarget(target))
20 | }
21 |
22 | override fun equals(other: Any?): Boolean {
23 | if (this === other) return true
24 | if (javaClass != other?.javaClass) return false
25 |
26 | other as HillaSymbol
27 |
28 | return target == other.target
29 | }
30 |
31 | override fun hashCode(): Int {
32 | return target.hashCode()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/WriteBase64FileHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.editor.Document
4 | import com.intellij.openapi.project.Project
5 | import com.intellij.openapi.vfs.VirtualFile
6 | import com.intellij.task.ProjectTaskManager
7 | import java.util.Base64
8 |
9 | class WriteBase64FileHandler(project: Project, data: Map) : WriteFileHandler(project, data) {
10 |
11 | override fun doWrite(vfsFile: VirtualFile?, doc: Document?, content: String) {
12 | vfsFile?.setBinaryContent(Base64.getDecoder().decode(content))
13 | }
14 |
15 | override fun postSave(vfsFile: VirtualFile) {
16 | LOG.info("File $vfsFile created")
17 | notifyUndoManager(vfsFile)
18 | // there is no Document associated with binary VirtualFile, call "compile" always to process
19 | // resource
20 | processResource(vfsFile)
21 | openFileInEditor(vfsFile)
22 | }
23 |
24 | private fun processResource(vfsFile: VirtualFile) {
25 | ProjectTaskManager.getInstance(project).compile(vfsFile).then {
26 | if (it.hasErrors()) {
27 | LOG.warn("Cannot process $vfsFile")
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/starter/StarterSupport.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.starter
2 |
3 | class StarterSupport {
4 |
5 | companion object {
6 |
7 | val languages = linkedMapOf("java" to "Java", "kotlin" to "Kotlin")
8 |
9 | val architectures =
10 | linkedMapOf(
11 | "springboot" to "Spring Boot",
12 | "quarkus" to "Quarkus",
13 | "jakartaee" to "Jakarta EE",
14 | "servlet" to "Servlet",
15 | )
16 |
17 | val buildTools = linkedMapOf("maven" to "Maven", "gradle" to "Gradle")
18 |
19 | fun isSupportedArchitecture(model: HelloWorldModel, architecture: String): Boolean {
20 | if (model.buildTool == "gradle") {
21 | return setOf("springboot", "servlet").contains(architecture)
22 | } else if (model.language == "kotlin") {
23 | return architecture == "springboot"
24 | }
25 | return true
26 | }
27 |
28 | fun isSupportedBuildTool(model: HelloWorldModel, buildTool: String): Boolean {
29 | if (model.language == "kotlin") {
30 | return buildTool == "maven"
31 | }
32 | return true
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/CompileFilesHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.project.Project
4 | import com.intellij.openapi.vfs.VfsUtil
5 | import com.intellij.openapi.vfs.findDocument
6 | import com.vaadin.plugin.actions.VaadinCompileOnSaveActionInfo
7 | import java.io.File
8 |
9 | class CompileFilesHandler(project: Project, data: Map) : AbstractHandler(project) {
10 |
11 | private val ioFile = data["files"] as Collection
12 |
13 | override fun run(): HandlerResponse {
14 |
15 | val documents =
16 | ioFile
17 | .map { File(it) }
18 | .filter { isFileInsideProject(project, it) }
19 | .mapNotNull { VfsUtil.findFileByIoFile(it, true) }
20 | .mapNotNull { it.findDocument() }
21 | .toTypedArray()
22 |
23 | if (documents.isNotEmpty()) {
24 | VaadinCompileOnSaveActionInfo.getAction().processDocuments(project, documents)
25 | LOG.debug("Files compilation initiated: $documents")
26 | return RESPONSE_OK
27 | } else {
28 | LOG.warn("No project files found in $ioFile")
29 | return RESPONSE_BAD_REQUEST
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/module/VaadinProjectWizard.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.module
2 |
3 | import com.intellij.ide.util.projectWizard.WizardContext
4 | import com.intellij.ide.wizard.GeneratorNewProjectWizard
5 | import com.intellij.ide.wizard.NewProjectWizardStep
6 | import com.intellij.openapi.observable.properties.PropertyGraph
7 | import com.vaadin.plugin.starter.DownloadableModel
8 | import com.vaadin.plugin.utils.VaadinIcons
9 | import com.vaadin.plugin.utils.VaadinProjectUtil.Companion.PROJECT_MODEL_PROP_KEY
10 | import javax.swing.Icon
11 |
12 | class VaadinProjectWizard : GeneratorNewProjectWizard {
13 |
14 | override val icon: Icon
15 | get() = VaadinIcons.VAADIN_BLUE
16 |
17 | override val id: String
18 | get() = "Vaadin"
19 |
20 | override val name: String
21 | get() = "Vaadin"
22 |
23 | private val propertyGraph: PropertyGraph
24 | get() = PropertyGraph("Vaadin project")
25 |
26 | private val projectModelProperty = propertyGraph.property(null)
27 |
28 | val projectModel: DownloadableModel? by projectModelProperty
29 |
30 | override fun createStep(context: WizardContext): NewProjectWizardStep {
31 | context.putUserData(PROJECT_MODEL_PROP_KEY, projectModelProperty)
32 | return VaadinProjectWizardStep(context, propertyGraph)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinHillaEndpointsProvider.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.microservices.endpoints.EndpointsFilter
4 | import com.intellij.microservices.endpoints.FrameworkPresentation
5 | import com.intellij.microservices.endpoints.ModuleEndpointsFilter
6 | import com.intellij.microservices.endpoints.presentation.HttpUrlPresentation
7 | import com.intellij.navigation.ItemPresentation
8 | import com.intellij.openapi.project.Project
9 | import com.vaadin.plugin.utils.VaadinIcons
10 | import com.vaadin.plugin.utils.hasVaadin
11 |
12 | internal class VaadinHillaEndpointsProvider : VaadinEndpointsProvider() {
13 |
14 | override val presentation: FrameworkPresentation =
15 | FrameworkPresentation("Vaadin-Hilla", "Vaadin Hilla", VaadinIcons.HILLA)
16 |
17 | override fun getEndpointGroups(project: Project, filter: EndpointsFilter): Iterable {
18 | if (filter !is ModuleEndpointsFilter) return emptyList()
19 | if (!hasVaadin(filter.module)) return emptyList()
20 |
21 | return findHillaEndpoints(project, filter.transitiveSearchScope)
22 | }
23 |
24 | override fun getEndpointPresentation(group: VaadinRoute, endpoint: VaadinRoute): ItemPresentation {
25 | return HttpUrlPresentation(group.urlMapping, group.locationString, VaadinIcons.HILLA)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/actions/VaadinCompileOnSaveActionInfo.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.actions
2 |
3 | import com.intellij.codeInsight.actions.onSave.ActionOnSaveInfoBase
4 | import com.intellij.ide.actionsOnSave.ActionOnSaveComment
5 | import com.intellij.ide.actionsOnSave.ActionOnSaveContext
6 | import com.intellij.ide.util.PropertiesComponent
7 | import com.intellij.openapi.actionSystem.ActionManager
8 | import com.intellij.openapi.project.Project
9 |
10 | class VaadinCompileOnSaveActionInfo(context: ActionOnSaveContext) :
11 | ActionOnSaveInfoBase(context, NAME, PROPERTY, DEFAULT) {
12 |
13 | companion object {
14 | const val ID = "VaadinCompileOnSaveAction"
15 | const val NAME = "Compile Java files"
16 | const val PROPERTY = "vaadin.compileOnSave"
17 | const val DEFAULT = true
18 | const val DESCRIPTION = "Compiles *.java while debugging"
19 |
20 | fun isEnabledForProject(project: Project): Boolean {
21 | return PropertiesComponent.getInstance(project).getBoolean(PROPERTY, DEFAULT)
22 | }
23 |
24 | fun getAction(): VaadinCompileOnSaveAction {
25 | return ActionManager.getInstance().getAction(ID) as VaadinCompileOnSaveAction
26 | }
27 | }
28 |
29 | override fun getComment(): ActionOnSaveComment? {
30 | return ActionOnSaveComment.info(DESCRIPTION)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinImplicitUsageProvider.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.codeInsight.AnnotationUtil
4 | import com.intellij.codeInsight.daemon.ImplicitUsageProvider
5 | import com.intellij.lang.jvm.JvmModifier
6 | import com.intellij.psi.PsiClass
7 | import com.intellij.psi.PsiElement
8 | import com.intellij.psi.PsiField
9 | import com.intellij.psi.util.InheritanceUtil
10 |
11 | internal class VaadinImplicitUsageProvider : ImplicitUsageProvider {
12 | override fun isImplicitUsage(element: PsiElement): Boolean {
13 | return element is PsiClass &&
14 | !element.isInterface &&
15 | !element.isEnum &&
16 | !element.hasModifier(JvmModifier.ABSTRACT) &&
17 | !element.isAnnotationType &&
18 | (AnnotationUtil.isAnnotated(element, VAADIN_ROUTE, 0) ||
19 | AnnotationUtil.isAnnotated(element, VAADIN_TAG, 0) ||
20 | AnnotationUtil.isAnnotated(element, HILLA_BROWSER_CALLABLE, 0) ||
21 | InheritanceUtil.isInheritor(element, VAADIN_APP_SHELL_CONFIGURATOR))
22 | }
23 |
24 | override fun isImplicitRead(element: PsiElement): Boolean {
25 | return false
26 | }
27 |
28 | override fun isImplicitWrite(element: PsiElement): Boolean {
29 | return element is PsiField && AnnotationUtil.isAnnotated(element, VAADIN_ID, 0)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/test/java/com/plugin/testviews/User.java:
--------------------------------------------------------------------------------
1 | package com.plugin.testviews;
2 |
3 | import jakarta.persistence.*;
4 |
5 | import java.util.HashSet;
6 | import java.util.Set;
7 |
8 | @Entity
9 | @Table(name = "app_user")
10 | public class User {
11 |
12 | @Id
13 | @GeneratedValue(strategy = GenerationType.IDENTITY)
14 | private Long id;
15 |
16 | @Column(nullable = false, unique = true)
17 | private String username;
18 |
19 | // Store the encoded password
20 | @Column(nullable = false)
21 | private String password;
22 |
23 | @ElementCollection(fetch = FetchType.EAGER)
24 | @CollectionTable(name = "app_user_roles", joinColumns = @JoinColumn(name = "user_id"))
25 | @Column(name = "role")
26 | private Set roles = new HashSet<>();
27 |
28 | public Long getId() {
29 | return id;
30 | }
31 |
32 | public String getUsername() {
33 | return username;
34 | }
35 |
36 | public void setUsername(String username) {
37 | this.username = username;
38 | }
39 |
40 | public String getPassword() {
41 | return password;
42 | }
43 |
44 | public void setPassword(String password) {
45 | this.password = password;
46 | }
47 |
48 | public Set getRoles() {
49 | return roles;
50 | }
51 |
52 | public void setRoles(Set roles) {
53 | this.roles = roles;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/vaadin/flow-language-injections.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Element.executeJs (com.vaadin.flow.dom)
4 |
5 |
6 |
7 |
8 | Html (com.vaadin.flow.component)
9 |
10 |
11 |
12 |
13 |
14 | TextField.setPattern (com.vaadin.flow.component.textfield)
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/META-INF/pluginIcon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/vaadin/icons/module.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/vaadin/icons/vaadin.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/vaadin/icons/vaadin_dark.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/vaadin/icons/rerun.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/vaadin/icons/rerun_dark.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/plugin/src/test/kotlin/com/vaadin/plugin/starter/HelloWorldModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.starter
2 |
3 | import com.intellij.openapi.observable.properties.PropertyGraph
4 | import com.intellij.openapi.project.Project
5 | import org.junit.jupiter.api.Assertions.*
6 | import org.junit.jupiter.api.Test
7 | import org.mockito.kotlin.mock
8 | import org.mockito.kotlin.whenever
9 |
10 | class HelloWorldModelTest {
11 | @Test
12 | fun defaultGroupIdIsComExampleApplication() {
13 | val groupIdProperty = PropertyGraph().property("com.example.application")
14 | val model = HelloWorldModel(groupIdProperty)
15 | assertEquals("com.example.application", model.groupIdProperty.get())
16 | }
17 |
18 | @Test
19 | fun groupIdCanBeChanged() {
20 | val groupIdProperty = PropertyGraph().property("com.example.application")
21 | val model = HelloWorldModel(groupIdProperty)
22 | groupIdProperty.set("org.example")
23 | assertEquals("org.example", model.groupIdProperty.get())
24 | }
25 |
26 | @Test
27 | fun groupIdIsIncludedInDownloadUrl() {
28 | val groupIdProperty = PropertyGraph().property("com.example.application")
29 | val model = HelloWorldModel(groupIdProperty)
30 | groupIdProperty.set("org.test")
31 | val project = mock()
32 | whenever(project.name).thenReturn("my-app")
33 | val url = model.getDownloadLink(project)
34 | assertTrue(url.contains("groupId=org.test"))
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/plugin/src/test/kotlin/com/vaadin/plugin/starter/StarterProjectModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.starter
2 |
3 | import com.intellij.openapi.observable.properties.PropertyGraph
4 | import com.intellij.openapi.project.Project
5 | import org.junit.jupiter.api.Assertions.*
6 | import org.junit.jupiter.api.Test
7 | import org.mockito.kotlin.mock
8 | import org.mockito.kotlin.whenever
9 |
10 | class StarterProjectModelTest {
11 | @Test
12 | fun defaultGroupIdIsComVaadinApplication() {
13 | val groupIdProperty = PropertyGraph().property("com.vaadin.application")
14 | val model = StarterProjectModel(groupIdProperty)
15 | assertEquals("com.vaadin.application", model.groupIdProperty.get())
16 | }
17 |
18 | @Test
19 | fun groupIdCanBeChanged() {
20 | val groupIdProperty = PropertyGraph().property("com.vaadin.application")
21 | val model = StarterProjectModel(groupIdProperty)
22 | groupIdProperty.set("org.example")
23 | assertEquals("org.example", model.groupIdProperty.get())
24 | }
25 |
26 | @Test
27 | fun groupIdIsIncludedInDownloadUrl() {
28 | val groupIdProperty = PropertyGraph().property("com.vaadin.application")
29 | val model = StarterProjectModel(groupIdProperty)
30 | groupIdProperty.set("org.test")
31 | val project = mock()
32 | whenever(project.name).thenReturn("demo")
33 | val url = model.getDownloadLink(project)
34 | assertTrue(url.contains("groupId=org.test"))
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/hotswapagent/BadJBRFoundDialog.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.hotswapagent
2 |
3 | import com.intellij.openapi.ui.DialogWrapper
4 | import javax.swing.Action
5 | import javax.swing.JComponent
6 | import javax.swing.JPanel
7 | import javax.swing.JTextArea
8 |
9 | class BadJBRFoundDialog() : DialogWrapper(true) {
10 |
11 | private val message: String
12 |
13 | override fun createActions(): Array {
14 | return arrayOf(myOKAction)
15 | }
16 |
17 | init {
18 | message = buildString {
19 | append(
20 | "HotswapAgent requires running with a JetBrains Runtime JDK which implements the low level hotswap functionality.\n\n")
21 | append(
22 | "Your IntelliJ IDEA installation includes a bundled JetBrains Runtime JDK which is known not work for this purpose.\n\n")
23 | append("You can resolve this by one of the following:\n")
24 | append("- Upgrade IntelliJ IDEA to 2024.2.4 or newer\n")
25 | append(
26 | "- Download a newer JetBrains runtime from https://github.com/JetBrains/JetBrainsRuntime/releases and set your run configuration to use it.")
27 | append("\n")
28 | }
29 | title = "Unable to Find a Suitable JDK"
30 | init()
31 | }
32 |
33 | override fun createCenterPanel(): JComponent {
34 | val textArea = JTextArea(message)
35 | textArea.isEditable = false
36 | return JPanel().apply { add(textArea) }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/GetVaadinEntitiesHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.ApplicationManager
4 | import com.intellij.openapi.project.Project
5 | import com.intellij.psi.search.GlobalSearchScope
6 | import com.vaadin.plugin.endpoints.findEntities
7 | import com.vaadin.plugin.endpoints.signatureToString
8 | import io.netty.handler.codec.http.HttpResponseStatus
9 |
10 | class GetVaadinEntitiesHandler(project: Project, includeMethods: Boolean) : AbstractHandler(project) {
11 |
12 | private val includeMethods: Boolean = includeMethods
13 |
14 | override fun run(): HandlerResponse {
15 | return ApplicationManager.getApplication().runReadAction {
16 | val entities = findEntities(project, GlobalSearchScope.allScope(project))
17 |
18 | val mapEntities =
19 | entities.map { entity ->
20 | if (includeMethods) {
21 | mapOf(
22 | "classname" to entity.className,
23 | "methods" to entity.visibleMethods.joinToString(",") { signatureToString(it) },
24 | "path" to entity.path)
25 | } else {
26 | mapOf("classname" to entity.className, "path" to entity.path)
27 | }
28 | }
29 |
30 | LOG.info("Entities detected: $entities")
31 |
32 | HandlerResponse(status = HttpResponseStatus.OK, data = mapOf("entities" to mapEntities))
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettingsConfigurable.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.ui.settings
2 |
3 | import com.intellij.openapi.options.Configurable
4 | import java.util.Objects
5 | import javax.swing.JComponent
6 |
7 | /** Provides controller functionality for application settings. */
8 | internal class VaadinSettingsConfigurable : Configurable {
9 |
10 | private var mySettingsComponent: VaadinSettingsComponent? = null
11 |
12 | override fun getPreferredFocusedComponent(): JComponent {
13 | return mySettingsComponent!!.preferredFocusedComponent
14 | }
15 |
16 | override fun createComponent(): JComponent {
17 | mySettingsComponent = VaadinSettingsComponent()
18 | return mySettingsComponent!!.panel
19 | }
20 |
21 | override fun isModified(): Boolean {
22 | val state: VaadinSettings.State = Objects.requireNonNull(VaadinSettings.instance.state)
23 | return mySettingsComponent!!.sendUsageStatisticsStatus != state.sendUsageStatistics
24 | }
25 |
26 | override fun apply() {
27 | val state: VaadinSettings.State = Objects.requireNonNull(VaadinSettings.instance.state)
28 | state.sendUsageStatistics = mySettingsComponent!!.sendUsageStatisticsStatus
29 | }
30 |
31 | override fun reset() {
32 | val state: VaadinSettings.State = Objects.requireNonNull(VaadinSettings.instance.state)
33 | mySettingsComponent!!.sendUsageStatisticsStatus = state.sendUsageStatistics
34 | }
35 |
36 | override fun disposeUIResources() {
37 | mySettingsComponent = null
38 | }
39 |
40 | override fun getDisplayName(): String {
41 | return "Vaadin"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/test/java/com/plugin/testviews/UserDetailsServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.plugin.testviews;
2 |
3 | import org.springframework.security.core.GrantedAuthority;
4 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
5 | import org.springframework.security.core.userdetails.UserDetails;
6 | import org.springframework.security.core.userdetails.UserDetailsService;
7 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
8 | import org.springframework.stereotype.Service;
9 |
10 | import java.util.List;
11 | import java.util.stream.Collectors;
12 |
13 | @Service
14 | public class UserDetailsServiceImpl implements UserDetailsService {
15 |
16 | private final UserRepository userRepository;
17 |
18 | public UserDetailsServiceImpl(UserRepository userRepository) {
19 | this.userRepository = userRepository;
20 | }
21 |
22 | @Override
23 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
24 | User user = userRepository.findByUsername(username);
25 | if (user == null) {
26 | throw new UsernameNotFoundException("No user present with username: " + username);
27 | } else {
28 | return new org.springframework.security.core.userdetails.User(
29 | user.getUsername(),
30 | user.getPassword(),
31 | getAuthorities(user)
32 | );
33 | }
34 | }
35 |
36 | private static List getAuthorities(User user) {
37 | return user.getRoles().stream()
38 | .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
39 | .collect(Collectors.toList());
40 | }
41 | }
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/starter/HelloWorldModel.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.starter
2 |
3 | import com.intellij.openapi.observable.properties.PropertyGraph
4 | import com.intellij.openapi.project.Project
5 | import com.vaadin.plugin.utils.toArtifactId
6 | import java.net.URLEncoder
7 | import java.nio.charset.StandardCharsets
8 |
9 | class HelloWorldModel(val groupIdProperty: com.intellij.openapi.observable.properties.GraphProperty) :
10 | DownloadableModel {
11 |
12 | private val graph = PropertyGraph()
13 | val languageProperty = graph.property(StarterSupport.languages.keys.first())
14 | val buildToolProperty = graph.property(StarterSupport.buildTools.keys.first())
15 | val architectureProperty = graph.property(StarterSupport.architectures.keys.first())
16 |
17 | val language by languageProperty
18 | val buildTool by buildToolProperty
19 | val architecture by architectureProperty
20 | val groupId by groupIdProperty
21 |
22 | override fun getDownloadLink(project: Project): String {
23 | val params =
24 | mapOf(
25 | "framework" to "flow",
26 | "language" to language,
27 | "buildtool" to buildTool,
28 | "stack" to architecture,
29 | "artifactId" to toArtifactId(project.name),
30 | "groupId" to groupId,
31 | "ref" to "intellij-plugin")
32 | val query =
33 | params.entries.joinToString("&") { (key, value) ->
34 | "${URLEncoder.encode(key, StandardCharsets.UTF_8)}=${URLEncoder.encode(value, StandardCharsets.UTF_8)}"
35 | }
36 |
37 | return "https://start.vaadin.com/helloworld?${query}"
38 | }
39 |
40 | override fun getProjectType(): String {
41 | return buildTool
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/utils/VaadinProjectUtil.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.utils
2 |
3 | import com.intellij.ide.plugins.IdeaPluginDescriptor
4 | import com.intellij.ide.plugins.PluginManager
5 | import com.intellij.ide.plugins.PluginManagerCore
6 | import com.intellij.java.library.JavaLibraryUtil.hasLibraryClass
7 | import com.intellij.openapi.application.ApplicationInfo
8 | import com.intellij.openapi.extensions.PluginId
9 | import com.intellij.openapi.observable.properties.GraphProperty
10 | import com.intellij.openapi.project.Project
11 | import com.intellij.openapi.util.Key
12 | import com.vaadin.plugin.listeners.VaadinProjectListener
13 | import com.vaadin.plugin.starter.DownloadableModel
14 |
15 | internal const val VAADIN_SERVICE = "com.vaadin.flow.server.VaadinService"
16 |
17 | internal const val ENDPOINTS_PLUGIN_ID = "com.intellij.microservices.ui"
18 |
19 | class VaadinProjectUtil {
20 |
21 | companion object {
22 |
23 | val PROJECT_MODEL_PROP_KEY = Key>("vaadin_project_model")
24 | }
25 | }
26 |
27 | internal fun doNotifyAboutVaadinProject(project: Project) {
28 | val publisher: VaadinProjectListener = project.messageBus.syncPublisher(VaadinProjectListener.TOPIC)
29 | publisher.vaadinProjectDetected(project)
30 | }
31 |
32 | internal fun hasVaadin(project: Project): Boolean = hasLibraryClass(project, VAADIN_SERVICE)
33 |
34 | internal fun hasVaadin(module: com.intellij.openapi.module.Module): Boolean = hasLibraryClass(module, VAADIN_SERVICE)
35 |
36 | internal fun hasEndpoints(): Boolean =
37 | PluginId.findId(ENDPOINTS_PLUGIN_ID)?.let { PluginManager.isPluginInstalled(it) } ?: false
38 |
39 | internal fun isUltimate(): Boolean = ApplicationInfo.getInstance().apiVersion.startsWith("IU-")
40 |
41 | internal fun getVaadinPluginDescriptor(): IdeaPluginDescriptor =
42 | PluginManagerCore.getPlugin(PluginId.getId("com.vaadin.intellij-plugin"))!!
43 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/GetVaadinComponentsHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.ApplicationManager
4 | import com.intellij.openapi.project.Project
5 | import com.intellij.psi.search.GlobalSearchScope
6 | import com.vaadin.plugin.endpoints.findComponents
7 | import com.vaadin.plugin.endpoints.signatureToString
8 | import io.netty.handler.codec.http.HttpResponseStatus
9 |
10 | class GetVaadinComponentsHandler(project: Project, includeMethods: Boolean) : AbstractHandler(project) {
11 |
12 | private val includeMethods: Boolean = includeMethods
13 |
14 | override fun run(): HandlerResponse {
15 | return ApplicationManager.getApplication().runReadAction {
16 | val components = findComponents(project, GlobalSearchScope.allScope(project))
17 |
18 | val mapComponents =
19 | components.map { component ->
20 | if (includeMethods) {
21 | mapOf(
22 | "class" to component.className,
23 | "origin" to component.origin,
24 | "source" to component.source,
25 | "path" to component.path,
26 | "methods" to component.visibleMethods.joinToString(",") { signatureToString(it) })
27 | } else {
28 | mapOf(
29 | "class" to component.className,
30 | "origin" to component.origin,
31 | "source" to component.source,
32 | "path" to component.path)
33 | }
34 | }
35 |
36 | LOG.info("Components detected: $components")
37 |
38 | HandlerResponse(status = HttpResponseStatus.OK, data = mapOf("components" to mapComponents))
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinEndpointsProvider.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.microservices.endpoints.EndpointType
4 | import com.intellij.microservices.endpoints.EndpointsFilter
5 | import com.intellij.microservices.endpoints.EndpointsProvider
6 | import com.intellij.microservices.endpoints.HTTP_SERVER_TYPE
7 | import com.intellij.microservices.endpoints.ModuleEndpointsFilter
8 | import com.intellij.openapi.project.Project
9 | import com.intellij.openapi.util.ModificationTracker
10 | import com.intellij.psi.PsiElement
11 | import com.intellij.uast.UastModificationTracker
12 | import com.vaadin.plugin.utils.hasVaadin
13 |
14 | abstract class VaadinEndpointsProvider : EndpointsProvider {
15 |
16 | override val endpointType: EndpointType = HTTP_SERVER_TYPE
17 |
18 | override fun getStatus(project: Project): EndpointsProvider.Status {
19 | if (hasVaadin(project)) return EndpointsProvider.Status.HAS_ENDPOINTS
20 | return EndpointsProvider.Status.UNAVAILABLE
21 | }
22 |
23 | override fun getModificationTracker(project: Project): ModificationTracker {
24 | return UastModificationTracker.getInstance(project)
25 | }
26 |
27 | override fun getEndpointGroups(project: Project, filter: EndpointsFilter): Iterable {
28 | if (filter !is ModuleEndpointsFilter) return emptyList()
29 | if (!hasVaadin(filter.module)) return emptyList()
30 |
31 | return findFlowRoutes(project, filter.transitiveSearchScope)
32 | }
33 |
34 | override fun getEndpoints(group: VaadinRoute): Iterable {
35 | return listOf(group)
36 | }
37 |
38 | override fun isValidEndpoint(group: VaadinRoute, endpoint: VaadinRoute): Boolean {
39 | return group.isValid()
40 | }
41 |
42 | override fun getDocumentationElement(group: VaadinRoute, endpoint: VaadinRoute): PsiElement? {
43 | return endpoint.anchor.retrieve()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/VaadinProjectDetector.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin
2 |
3 | import com.intellij.openapi.application.ApplicationManager
4 | import com.intellij.openapi.application.ReadAction
5 | import com.intellij.openapi.components.service
6 | import com.intellij.openapi.diagnostic.Logger
7 | import com.intellij.openapi.project.DumbService
8 | import com.intellij.openapi.project.Project
9 | import com.intellij.openapi.project.ProjectManager
10 | import com.intellij.openapi.roots.ModuleRootEvent
11 | import com.intellij.openapi.roots.ModuleRootListener
12 | import com.intellij.openapi.startup.ProjectActivity
13 | import com.vaadin.plugin.copilot.service.CopilotDotfileService
14 | import com.vaadin.plugin.utils.doNotifyAboutVaadinProject
15 | import com.vaadin.plugin.utils.hasVaadin
16 |
17 | class VaadinProjectDetector : ModuleRootListener, ProjectActivity, DumbService.DumbModeListener {
18 |
19 | private val LOG: Logger = Logger.getInstance(VaadinProjectDetector::class.java)
20 |
21 | override fun rootsChanged(event: ModuleRootEvent) {
22 | if (event.project.isOpen) {
23 | detectAndNotifyAboutVaadinProject { event.project }
24 | }
25 | }
26 |
27 | override suspend fun execute(project: Project) {
28 | detectAndNotifyAboutVaadinProject { project }
29 | }
30 |
31 | override fun exitDumbMode() {
32 | detectAndNotifyAboutVaadinProject { ProjectManager.getInstance().openProjects.first() }
33 | }
34 |
35 | private fun detectAndNotifyAboutVaadinProject(projectProvider: () -> Project) {
36 | ApplicationManager.getApplication().executeOnPooledThread {
37 | ReadAction.run {
38 | val project = projectProvider.invoke()
39 | if (!project.service().isActive() && hasVaadin(project)) {
40 | doNotifyAboutVaadinProject(project)
41 | LOG.info("Vaadin project detected " + project.name)
42 | }
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinFlowEndpointsProvider.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.microservices.endpoints.EndpointsFilter
4 | import com.intellij.microservices.endpoints.FrameworkPresentation
5 | import com.intellij.microservices.endpoints.ModuleEndpointsFilter
6 | import com.intellij.microservices.endpoints.presentation.HttpUrlPresentation
7 | import com.intellij.microservices.url.UrlPath
8 | import com.intellij.navigation.ItemPresentation
9 | import com.intellij.openapi.project.Project
10 | import com.vaadin.plugin.utils.VaadinIcons
11 | import com.vaadin.plugin.utils.hasVaadin
12 |
13 | internal class VaadinFlowEndpointsProvider : VaadinEndpointsProvider() {
14 |
15 | override val presentation: FrameworkPresentation =
16 | FrameworkPresentation("Vaadin-Flow", "Vaadin Flow", VaadinIcons.VAADIN_BLUE)
17 |
18 | override fun getEndpointGroups(project: Project, filter: EndpointsFilter): Iterable {
19 | if (filter !is ModuleEndpointsFilter) return emptyList()
20 | if (!hasVaadin(filter.module)) return emptyList()
21 |
22 | return findFlowRoutes(project, filter.transitiveSearchScope)
23 | }
24 |
25 | override fun getEndpointPresentation(group: VaadinRoute, endpoint: VaadinRoute): ItemPresentation {
26 | return HttpUrlPresentation(normalizeUrl(group.urlMapping), group.locationString, VaadinIcons.VAADIN_BLUE)
27 | }
28 |
29 | private fun normalizeUrl(urlMapping: String): String {
30 | val urlString = run {
31 | if (urlMapping.isBlank()) return@run "/"
32 | if (!urlMapping.startsWith("/")) return@run "/$urlMapping"
33 | return@run urlMapping
34 | }
35 |
36 | return parseVaadinUrlMapping(urlString).getPresentation(VaadinUrlRenderer)
37 | }
38 |
39 | private object VaadinUrlRenderer : UrlPath.PathSegmentRenderer {
40 | override fun visitVariable(variable: UrlPath.PathSegment.Variable): String {
41 | return "{${variable.variableName}}"
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/GetVaadinSecurityHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.ApplicationManager
4 | import com.intellij.openapi.project.Project
5 | import com.intellij.psi.search.GlobalSearchScope
6 | import com.vaadin.plugin.endpoints.findSecurityConfig
7 | import com.vaadin.plugin.endpoints.findUserDetails
8 | import io.netty.handler.codec.http.HttpResponseStatus
9 |
10 | class GetVaadinSecurityHandler(project: Project) : AbstractHandler(project) {
11 |
12 | override fun run(): HandlerResponse {
13 | return ApplicationManager.getApplication().runReadAction {
14 | val security = findSecurityConfig(project, GlobalSearchScope.allScope(project))
15 | val userDetails = findUserDetails(project, GlobalSearchScope.allScope(project))
16 |
17 | val mapSecurity =
18 | security.map { component ->
19 | mapOf(
20 | "class" to component.className,
21 | "origin" to component.origin,
22 | "source" to component.source,
23 | "path" to component.path,
24 | "loginView" to component.loginView)
25 | }
26 | val mapUserDetails =
27 | userDetails.filterNot({ component -> component.origin == "library" }).map { component ->
28 | mapOf(
29 | "class" to component.className,
30 | "origin" to component.origin,
31 | "source" to component.source,
32 | "path" to component.path,
33 | "entity" to component.entity?.joinToString(","))
34 | }
35 |
36 | LOG.info("Security detected: $security")
37 |
38 | HandlerResponse(
39 | status = HttpResponseStatus.OK,
40 | data = mapOf("security" to mapSecurity, "userDetails" to mapUserDetails))
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/RestartApplicationHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.execution.process.BaseOSProcessHandler
4 | import com.intellij.execution.runners.ExecutionUtil
5 | import com.intellij.execution.ui.RunContentDescriptor
6 | import com.intellij.execution.ui.RunContentManager
7 | import com.intellij.openapi.application.runInEdt
8 | import com.intellij.openapi.project.Project
9 |
10 | /**
11 | * Handler for restarting the application. If the main class is provided, the handler will restart the application with
12 | * the provided main class.
13 | */
14 | class RestartApplicationHandler(project: Project, data: Map?) : AbstractHandler(project) {
15 |
16 | private val expectedMainClass: String? = data?.get("mainClass") as String?
17 |
18 | override fun run(): HandlerResponse {
19 | val contentManager = RunContentManager.getInstance(project)
20 |
21 | val descriptor = findMatchingDescriptor(contentManager) ?: return RESPONSE_BAD_REQUEST
22 |
23 | runInEdt { ExecutionUtil.restart(descriptor) }
24 |
25 | return RESPONSE_OK
26 | }
27 |
28 | private fun findMatchingDescriptor(runContentManager: RunContentManager): RunContentDescriptor? {
29 | // If the main class is not provided, use default descriptor
30 | if (expectedMainClass == null) {
31 | return runContentManager.selectedContent
32 | }
33 |
34 | // Find descriptor with matching main class
35 | for (descriptor in runContentManager.allDescriptors) {
36 | val processHandler = descriptor.processHandler
37 |
38 | // Support only BaseOSProcessHandler
39 | if (processHandler !is BaseOSProcessHandler) {
40 | continue
41 | }
42 |
43 | val mainClassName = processHandler.commandLine.split(" ").last()
44 | if (mainClassName != expectedMainClass) {
45 | continue
46 | }
47 |
48 | return descriptor
49 | }
50 |
51 | return null
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/starter/StarterProjectModel.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.starter
2 |
3 | import com.intellij.openapi.components.BaseState
4 | import com.intellij.openapi.observable.properties.PropertyGraph
5 | import com.intellij.openapi.project.Project
6 | import com.vaadin.plugin.utils.toArtifactId
7 | import java.net.URLEncoder
8 | import java.nio.charset.StandardCharsets
9 |
10 | class StarterProjectModel(val groupIdProperty: com.intellij.openapi.observable.properties.GraphProperty) :
11 | BaseState(), DownloadableModel {
12 |
13 | private val graph: PropertyGraph = PropertyGraph()
14 | val usePrereleaseProperty = graph.property(false)
15 | val includeFlowProperty = graph.property(true)
16 | val includeHillaProperty = graph.property(false)
17 |
18 | private val usePrerelease by usePrereleaseProperty
19 | private val includeFlow by includeFlowProperty
20 | private val includeHilla by includeHillaProperty
21 | private val groupId by groupIdProperty
22 |
23 | override fun getDownloadLink(project: Project): String {
24 | val frameworks =
25 | when {
26 | includeFlow && includeHilla -> "flow,hilla"
27 | includeFlow -> "flow"
28 | includeHilla -> "hilla"
29 | else -> "empty"
30 | }
31 |
32 | val platformVersion = if (usePrerelease) "pre" else "latest"
33 |
34 | val params =
35 | mapOf(
36 | "frameworks" to frameworks,
37 | "platformVersion" to platformVersion,
38 | "artifactId" to toArtifactId(project.name),
39 | "groupId" to groupId,
40 | "ref" to "intellij-plugin")
41 | val query =
42 | params.entries.joinToString("&") { (key, value) ->
43 | "${URLEncoder.encode(key, StandardCharsets.UTF_8)}=${URLEncoder.encode(value, StandardCharsets.UTF_8)}"
44 | }
45 |
46 | return "https://start.vaadin.com/skeleton?${query}"
47 | }
48 |
49 | override fun getProjectType(): String {
50 | return "maven"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/plugin-api-test-client/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/DeleteFileHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.runInEdt
4 | import com.intellij.openapi.command.CommandProcessor
5 | import com.intellij.openapi.command.UndoConfirmationPolicy
6 | import com.intellij.openapi.command.WriteCommandAction
7 | import com.intellij.openapi.project.Project
8 | import com.intellij.openapi.vfs.ReadonlyStatusHandler
9 | import com.intellij.openapi.vfs.VfsUtil
10 | import java.io.File
11 |
12 | class DeleteFileHandler(project: Project, data: Map) : AbstractHandler(project) {
13 |
14 | private val ioFile: File = File(data["file"] as String)
15 |
16 | override fun run(): HandlerResponse {
17 | if (!isFileInsideProject(project, ioFile)) {
18 | LOG.warn("File $ioFile is not a part of a project")
19 | return RESPONSE_BAD_REQUEST
20 | }
21 |
22 | val vfsFile = VfsUtil.findFileByIoFile(ioFile, true) ?: return RESPONSE_ERROR
23 | if (!vfsFile.exists()) {
24 | LOG.warn("File $ioFile does not exist")
25 | return RESPONSE_ERROR
26 | }
27 |
28 | runInEdt {
29 | if (!ReadonlyStatusHandler.ensureFilesWritable(project, vfsFile)) {
30 | LOG.warn("File $ioFile is not writable, cannot delete")
31 | return@runInEdt
32 | }
33 |
34 | CommandProcessor.getInstance()
35 | .executeCommand(
36 | project,
37 | {
38 | WriteCommandAction.runWriteCommandAction(project) {
39 | try {
40 | vfsFile.delete(this)
41 | LOG.info("File $ioFile deleted")
42 | } catch (e: Exception) {
43 | LOG.error("Failed to delete file $ioFile", e)
44 | }
45 | }
46 | },
47 | "Vaadin Copilot Delete File",
48 | null,
49 | UndoConfirmationPolicy.DO_NOT_REQUEST_CONFIRMATION)
50 | }
51 |
52 | return RESPONSE_OK
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/CopilotErrorHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot
2 |
3 | import com.intellij.ide.BrowserUtil
4 | import com.intellij.openapi.application.ApplicationInfo
5 | import com.intellij.openapi.diagnostic.ErrorReportSubmitter
6 | import com.intellij.openapi.diagnostic.IdeaLoggingEvent
7 | import com.intellij.openapi.diagnostic.SubmittedReportInfo
8 | import com.intellij.util.Consumer
9 | import java.awt.Component
10 | import java.lang.management.ManagementFactory
11 |
12 | class CopilotErrorHandler : ErrorReportSubmitter() {
13 |
14 | private val ghMaxBodyLength = 5000
15 |
16 | private val url = "https://github.com/vaadin/intellij-plugin/issues/new"
17 |
18 | override fun getReportActionText(): String {
19 | return "Report on GitHub"
20 | }
21 |
22 | override fun submit(
23 | events: Array,
24 | additionalInfo: String?,
25 | parentComponent: Component,
26 | consumer: Consumer,
27 | ): Boolean {
28 | var throwableText = events.iterator().next().throwableText
29 | val firstLine = throwableText.split("\\n".toRegex(), 2)[0].trim()
30 | val appName = ApplicationInfo.getInstance().fullApplicationName
31 |
32 | if (throwableText.length > ghMaxBodyLength) {
33 | throwableText = throwableText.substring(0, ghMaxBodyLength)
34 | }
35 |
36 | val runtimeMXBean = ManagementFactory.getRuntimeMXBean()
37 | var body =
38 | "Plugin version: **${pluginDescriptor.version}**\n" +
39 | "IDE version: **$appName**\n" +
40 | "VM: **${runtimeMXBean.vmName + " " + runtimeMXBean.vmVersion + " " + runtimeMXBean.vmVendor}**\n" +
41 | "OS: **${System.getProperty("os.name") + " " + System.getProperty("os.version")}**\n\n"
42 |
43 | if (additionalInfo != null) {
44 | body += "Additional info:\n" + "> ${additionalInfo.replace("\\n+".toRegex(), "\n")}" + "\n\n"
45 | }
46 |
47 | body += "Stacktrace:\n```\n$throwableText\n```"
48 |
49 | val title = "[Error report] $firstLine"
50 | BrowserUtil.browse("$url?title=$title&body=$body")
51 | return true
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/ShowInIdeHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.runInEdt
4 | import com.intellij.openapi.editor.LogicalPosition
5 | import com.intellij.openapi.editor.ScrollType
6 | import com.intellij.openapi.fileEditor.FileEditorManager
7 | import com.intellij.openapi.fileEditor.OpenFileDescriptor
8 | import com.intellij.openapi.project.Project
9 | import com.intellij.openapi.vfs.VfsUtil
10 | import com.vaadin.plugin.utils.IdeUtil
11 | import java.io.File
12 |
13 | class ShowInIdeHandler(project: Project, data: Map) : AbstractHandler(project) {
14 |
15 | private val ioFile: File = File(data["file"] as String)
16 | private val line: Int = (data["line"] as Int?) ?: 0
17 | private val column: Int = (data["column"] as Int?) ?: 0
18 |
19 | override fun run(): HandlerResponse {
20 | if (line < 0 || column < 0) {
21 | LOG.warn("Invalid line or column number ($line:$column)")
22 | return RESPONSE_BAD_REQUEST
23 | }
24 | if (isFileInsideProject(project, ioFile)) {
25 | val result =
26 | VfsUtil.findFileByIoFile(ioFile, true)?.let { file ->
27 | val openFileDescriptor = OpenFileDescriptor(project, file)
28 | runInEdt {
29 | FileEditorManager.getInstance(project).openTextEditor(openFileDescriptor, true)?.let { editor ->
30 | editor.selectionModel.removeSelection()
31 | editor.caretModel.currentCaret.moveToLogicalPosition(LogicalPosition(line, column))
32 | editor.scrollingModel.scrollToCaret(ScrollType.CENTER)
33 | }
34 | IdeUtil.bringToFront(project)
35 | }
36 | LOG.info("File $ioFile opened at $line:$column")
37 | true
38 | }
39 | if (result != true) {
40 | LOG.warn("Cannot open $ioFile at $line:$column, file does not exist or is not readable")
41 | return RESPONSE_ERROR
42 | }
43 |
44 | return RESPONSE_OK
45 | } else {
46 | LOG.warn("File $ioFile is not a part of a project")
47 | return RESPONSE_BAD_REQUEST
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/symbols/HillaSymbolReference.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.symbols
2 |
3 | import com.intellij.model.Symbol
4 | import com.intellij.model.psi.PsiSymbolReference
5 | import com.intellij.openapi.util.TextRange
6 | import com.intellij.psi.JavaPsiFacade
7 | import com.intellij.psi.PsiElement
8 | import com.intellij.psi.search.GlobalSearchScope
9 | import com.intellij.psi.search.ProjectScope
10 | import com.intellij.psi.search.searches.AnnotatedElementsSearch
11 | import com.vaadin.plugin.endpoints.HILLA_BROWSER_CALLABLE
12 |
13 | class HillaSymbolReference(private val element: PsiElement) : PsiSymbolReference {
14 |
15 | override fun resolveReference(): Collection {
16 | val hillaBrowserCallableClass =
17 | JavaPsiFacade.getInstance(element.project)
18 | .findClass(HILLA_BROWSER_CALLABLE, ProjectScope.getLibrariesScope(element.project)) ?: return emptySet()
19 |
20 | val scope = GlobalSearchScope.allScope(element.project)
21 |
22 | var className = element.text
23 | var methodName: String? = null
24 |
25 | // both ClassName and ClassName.methodName are JSReferenceExpressions
26 | if (element.textContains('.')) {
27 | element.text.split('.').let {
28 | className = it[0]
29 | methodName = it[1]
30 | }
31 | }
32 |
33 | val psiClasses =
34 | AnnotatedElementsSearch.searchPsiClasses(hillaBrowserCallableClass, scope).filter {
35 | it.name?.endsWith(className) == true
36 | }
37 |
38 | if (psiClasses.isEmpty()) {
39 | return emptySet()
40 | }
41 |
42 | if (methodName != null) {
43 | return psiClasses
44 | .mapNotNull { it.findMethodsByName(methodName, true).firstOrNull() }
45 | .map { HillaSymbol(it) }
46 | }
47 |
48 | return psiClasses.map { HillaSymbol(it) }
49 | }
50 |
51 | override fun getElement(): PsiElement {
52 | return element
53 | }
54 |
55 | override fun getRangeInElement(): TextRange {
56 | val indexOfDot = element.text.indexOf('.')
57 | if (indexOfDot != -1) {
58 | return TextRange.from(indexOfDot + 1, element.text.length - indexOfDot)
59 | }
60 | return TextRange.allOf(element.text)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinReferenceContributor.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.codeInsight.highlighting.HighlightedReference
4 | import com.intellij.lang.properties.references.PropertyReference
5 | import com.intellij.microservices.jvm.url.uastUrlPathReferenceInjectorForScheme
6 | import com.intellij.microservices.jvm.url.uastUrlReferenceProvider
7 | import com.intellij.microservices.url.HTTP_SCHEMES
8 | import com.intellij.patterns.PsiJavaPatterns.psiMethod
9 | import com.intellij.patterns.uast.callExpression
10 | import com.intellij.patterns.uast.injectionHostUExpression
11 | import com.intellij.psi.*
12 | import com.intellij.util.ProcessingContext
13 | import org.jetbrains.uast.UElement
14 | import org.jetbrains.uast.expressions.UInjectionHost
15 |
16 | internal class VaadinReferenceContributor : PsiReferenceContributor() {
17 | override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) {
18 | registrar.registerUastReferenceProvider(
19 | injectionHostUExpression().annotationParam(VAADIN_ROUTE, "value"),
20 | uastUrlReferenceProvider(uastUrlPathReferenceInjectorForScheme(HTTP_SCHEMES, vaadinUrlPksParser)))
21 |
22 | registrar.registerUastReferenceProvider(
23 | injectionHostUExpression()
24 | .callParameter(
25 | 0,
26 | callExpression()
27 | .withMethodName("getTranslation")
28 | .withAnyResolvedMethod(
29 | psiMethod()
30 | .withName("getTranslation")
31 | .definedInClass("com.vaadin.flow.component.Component"))),
32 | object : UastReferenceProvider() {
33 | override fun getReferencesByElement(
34 | element: UElement,
35 | context: ProcessingContext
36 | ): Array {
37 | if (element !is UInjectionHost) return PsiReference.EMPTY_ARRAY
38 | val key = element.evaluateToString() ?: return PsiReference.EMPTY_ARRAY
39 | val sourcePsi = element.sourcePsi ?: return PsiReference.EMPTY_ARRAY
40 |
41 | return arrayOf(object : PropertyReference(key, sourcePsi, null, false), HighlightedReference {})
42 | }
43 | })
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/hotswapagent/HotswapAgentExecutor.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.hotswapagent
2 |
3 | import com.intellij.execution.JavaRunConfigurationBase
4 | import com.intellij.execution.RunManager
5 | import com.intellij.execution.configurations.RunConfiguration
6 | import com.intellij.execution.executors.DefaultDebugExecutor
7 | import com.intellij.openapi.project.Project
8 | import com.vaadin.plugin.utils.VaadinIcons
9 | import javax.swing.Icon
10 |
11 | class HotswapAgentExecutor : DefaultDebugExecutor() {
12 |
13 | companion object {
14 | const val ID = "Vaadin.HotswapAgentExecutor"
15 | }
16 |
17 | override fun getDescription(): String {
18 | return "Debug using HotswapAgent"
19 | }
20 |
21 | override fun getId(): String {
22 | return ID
23 | }
24 |
25 | override fun getToolWindowId(): String {
26 | return id
27 | }
28 |
29 | override fun getActionName(): String {
30 | return description
31 | }
32 |
33 | override fun getStartActionText(): String {
34 | return description
35 | }
36 |
37 | override fun getStartActionText(configurationName: String): String {
38 | return description
39 | }
40 |
41 | override fun getContextActionId(): String {
42 | return "$id-action"
43 | }
44 |
45 | override fun getIcon(): Icon {
46 | return VaadinIcons.DEBUG_HOTSWAP
47 | }
48 |
49 | override fun getToolWindowIcon(): Icon {
50 | return VaadinIcons.DEBUG_HOTSWAP
51 | }
52 |
53 | override fun getDisabledIcon(): Icon {
54 | return super.getDisabledIcon()
55 | }
56 |
57 | override fun isApplicable(project: Project): Boolean {
58 | val selectedConfiguration = RunManager.getInstance(project).selectedConfiguration?.configuration
59 | return isCurrentFile(selectedConfiguration) ||
60 | (selectedConfiguration is JavaRunConfigurationBase &&
61 | (!isMaven(selectedConfiguration) || !isGradle(selectedConfiguration)))
62 | }
63 |
64 | private fun isCurrentFile(configuration: RunConfiguration?): Boolean {
65 | return configuration == null
66 | }
67 |
68 | private fun isMaven(configuration: RunConfiguration): Boolean {
69 | return configuration.javaClass.name.contains("MavenRunConfiguration")
70 | }
71 |
72 | private fun isGradle(configuration: RunConfiguration): Boolean {
73 | return configuration.javaClass.name.contains("GradleRunConfiguration")
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/GetVaadinVersionHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.ApplicationManager
4 | import com.intellij.openapi.project.Project
5 | import com.intellij.openapi.vfs.JarFileSystem
6 | import com.intellij.openapi.vfs.VirtualFile
7 | import com.intellij.psi.JavaPsiFacade
8 | import com.intellij.psi.PsiClass
9 | import com.intellij.psi.search.GlobalSearchScope
10 | import com.vaadin.plugin.utils.VAADIN_SERVICE
11 | import io.netty.handler.codec.http.HttpResponseStatus
12 |
13 | class GetVaadinVersionHandler(project: Project) : AbstractHandler(project) {
14 |
15 | override fun run(): HandlerResponse {
16 | return ApplicationManager.getApplication().runReadAction {
17 | val psiClass: PsiClass =
18 | JavaPsiFacade.getInstance(project).findClass(VAADIN_SERVICE, GlobalSearchScope.allScope(project))
19 | ?: return@runReadAction HandlerResponse(
20 | status = HttpResponseStatus.NOT_FOUND, data = mapOf("error" to "VaadinService class not found"))
21 |
22 | LOG.info("Vaadin Service psiClass: ${psiClass.qualifiedName}")
23 |
24 | val classFile: VirtualFile =
25 | psiClass.containingFile.virtualFile
26 | ?: return@runReadAction HandlerResponse(
27 | status = HttpResponseStatus.NOT_FOUND,
28 | data = mapOf("error" to "VaadinService class file not found"))
29 |
30 | LOG.info("Vaadin Service class file: ${classFile.path}")
31 |
32 | val jarFile = JarFileSystem.getInstance().getVirtualFileForJar(classFile)
33 | val version =
34 | if (jarFile != null) {
35 | extractArtifactVersionFromJarPath(jarFile.path)
36 | } else {
37 | "N/A"
38 | }
39 |
40 | LOG.info("Vaadin Version detected: $version")
41 |
42 | HandlerResponse(status = HttpResponseStatus.OK, data = mapOf("version" to version) as Map?)
43 | }
44 | }
45 |
46 | fun extractArtifactVersionFromJarPath(jarPath: String): String? {
47 | val jarName = jarPath.substringAfterLast('/')
48 | if (!jarName.endsWith(".jar")) return null
49 |
50 | val baseName = jarName.removeSuffix(".jar")
51 |
52 | // Heuristic: version starts at the last dash, assuming format like artifactId-version.jar
53 | val dashIndex = baseName.lastIndexOf('-')
54 | if (dashIndex == -1 || dashIndex == baseName.length - 1) return null
55 |
56 | return baseName.substring(dashIndex + 1)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/vaadin/icons/hilla.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/psi/HillaReferenceSearcher.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.psi
2 |
3 | import com.intellij.lang.javascript.JSElementTypes
4 | import com.intellij.openapi.application.QueryExecutorBase
5 | import com.intellij.openapi.application.ReadAction
6 | import com.intellij.openapi.fileTypes.FileTypeManager
7 | import com.intellij.openapi.util.TextRange
8 | import com.intellij.psi.PsiElement
9 | import com.intellij.psi.PsiReference
10 | import com.intellij.psi.PsiReferenceBase
11 | import com.intellij.psi.search.GlobalSearchScope
12 | import com.intellij.psi.search.PsiSearchHelper
13 | import com.intellij.psi.search.UsageSearchContext
14 | import com.intellij.psi.search.searches.MethodReferencesSearch
15 | import com.intellij.psi.util.elementType
16 | import com.intellij.util.Processor
17 | import com.vaadin.plugin.endpoints.HILLA_BROWSER_CALLABLE
18 |
19 | class HillaReferenceSearcher : QueryExecutorBase() {
20 |
21 | private val tsFileType = FileTypeManager.getInstance().getFileTypeByExtension("ts")
22 | private val tsxFileType = FileTypeManager.getInstance().getFileTypeByExtension("tsx")
23 | private val jsFileType = FileTypeManager.getInstance().getFileTypeByExtension("js")
24 | private val jsxFileType = FileTypeManager.getInstance().getFileTypeByExtension("jsx")
25 |
26 | override fun processQuery(
27 | queryParameters: MethodReferencesSearch.SearchParameters,
28 | consumer: Processor
29 | ) {
30 | val psiMethod = queryParameters.method
31 |
32 | ReadAction.run {
33 | if (psiMethod.containingClass?.hasAnnotation(HILLA_BROWSER_CALLABLE) != true) {
34 | return@run
35 | }
36 |
37 | val searchHelper = PsiSearchHelper.getInstance(queryParameters.project)
38 | val scope = GlobalSearchScope.projectScope(queryParameters.project)
39 |
40 | val filteredScope =
41 | GlobalSearchScope.getScopeRestrictedByFileTypes(scope, tsFileType, tsxFileType, jsFileType, jsxFileType)
42 | searchHelper.processElementsWithWord(
43 | { psiElement, offset ->
44 | if (psiElement.elementType == JSElementTypes.REFERENCE_EXPRESSION) {
45 | val range = TextRange(offset, offset + psiMethod.name.length)
46 | val reference =
47 | object : PsiReferenceBase(psiElement, range) {
48 | override fun resolve(): PsiElement = psiElement
49 | }
50 | consumer.process(reference)
51 | }
52 | true // return false to stop the search early
53 | },
54 | filteredScope,
55 | psiMethod.name,
56 | UsageSearchContext.IN_CODE,
57 | true)
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/service/CopilotRestService.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.service
2 |
3 | import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
4 | import com.fasterxml.jackson.module.kotlin.readValue
5 | import com.google.gson.Gson
6 | import com.intellij.openapi.diagnostic.Logger
7 | import com.intellij.openapi.project.ProjectManager
8 | import com.vaadin.plugin.copilot.CommandRequest
9 | import com.vaadin.plugin.copilot.CopilotPluginUtil
10 | import com.vaadin.plugin.copilot.RestUtil
11 | import io.netty.buffer.ByteBuf
12 | import io.netty.buffer.Unpooled
13 | import io.netty.channel.ChannelHandlerContext
14 | import io.netty.handler.codec.http.*
15 | import java.nio.charset.Charset
16 | import java.nio.file.Path
17 | import org.jetbrains.ide.RestService
18 | import org.jetbrains.io.response
19 |
20 | class CopilotRestService : RestService() {
21 |
22 | private val LOG: Logger = Logger.getInstance(CopilotRestService::class.java)
23 |
24 | override fun getServiceName(): String {
25 | return RestUtil.getServiceName()
26 | }
27 |
28 | override fun execute(
29 | urlDecoder: QueryStringDecoder,
30 | request: FullHttpRequest,
31 | context: ChannelHandlerContext,
32 | ): String? {
33 | val charset = HttpUtil.getCharset(request, Charset.defaultCharset())
34 | val copilotRequest: CommandRequest = jacksonObjectMapper().readValue(request.content().toString(charset))
35 |
36 | if (copilotRequest.projectBasePath == null) {
37 | sendStatus(HttpResponseStatus.BAD_REQUEST, false, context.channel())
38 | return null
39 | }
40 |
41 | val projectBasePath = Path.of(copilotRequest.projectBasePath).toRealPath()
42 | val project =
43 | ProjectManager.getInstance().openProjects.find {
44 | Path.of(it.basePath!!).toRealPath().equals(projectBasePath)
45 | }
46 |
47 | if (project == null) {
48 | LOG.error(
49 | "Requested project location $projectBasePath does not match any of opened projects: " +
50 | ProjectManager.getInstance().openProjects.mapNotNull { it.basePath }.joinToString { it })
51 | sendStatus(HttpResponseStatus.BAD_REQUEST, false, context.channel())
52 | return null
53 | }
54 |
55 | val handlerResponse =
56 | CopilotPluginUtil.createCommandHandler(copilotRequest.command, project, copilotRequest.data).run()
57 | if (handlerResponse.data == null) {
58 | sendStatus(handlerResponse.status, HttpUtil.isKeepAlive(request), context.channel())
59 | } else {
60 | val json = Gson().toJson(handlerResponse.data)
61 | val buff: ByteBuf = Unpooled.copiedBuffer(json, Charset.forName("UTF-8"))
62 | sendResponse(request, context, response("application/json", buff))
63 | }
64 | return null
65 | }
66 |
67 | override fun isMethodSupported(method: HttpMethod): Boolean {
68 | return method === HttpMethod.POST
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/vaadin/icons/debug.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/plugin/src/main/resources/vaadin/icons/debug_dark.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/service/CompilationStatusManagerService.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.service
2 |
3 | import com.intellij.openapi.compiler.CompilationStatusListener
4 | import com.intellij.openapi.compiler.CompileContext
5 | import com.intellij.openapi.compiler.CompilerMessageCategory
6 | import com.intellij.openapi.compiler.CompilerTopics
7 | import com.intellij.openapi.components.Service
8 | import com.intellij.openapi.project.Project
9 |
10 | @Service(Service.Level.PROJECT)
11 | class CompilationStatusManagerService(private val project: Project) : CompilationStatusListener {
12 |
13 | /**
14 | * Holds compilation error state for a given project, including:
15 | * - [hasCompilationError]: whether the last compilation attempt had errors
16 | * - [errorFiles]: a set of file paths (as strings) that caused the compilation errors
17 | */
18 | data class CompilationErrorInfo(
19 | var hasCompilationError: Boolean = false,
20 | val errorFiles: MutableSet = mutableSetOf()
21 | )
22 |
23 | private var compilationErrorInfo: CompilationErrorInfo? = null
24 |
25 | /** Subscribes to project message bus to listen compilation results */
26 | fun subscribeToCompilationStatus() {
27 | project.messageBus.connect().subscribe(CompilerTopics.COMPILATION_STATUS, this)
28 | }
29 |
30 | override fun compilationFinished(aborted: Boolean, errors: Int, warnings: Int, compileContext: CompileContext) {
31 | val filePaths = mutableSetOf()
32 | if (errors > 0) {
33 | val messages = compileContext.getMessages(CompilerMessageCategory.ERROR)
34 | for (message in messages) {
35 | message.virtualFile?.let { virtualFile -> filePaths.add(virtualFile.path) }
36 | }
37 | }
38 | setHasCompilationError(errors > 0, filePaths)
39 | super.compilationFinished(aborted, errors, warnings, compileContext)
40 | }
41 |
42 | /**
43 | * Checks if the given [project] currently has compilation errors.
44 | *
45 | * @return `true` if the project has errors, otherwise `false`.
46 | */
47 | fun hasCompilationError(): Boolean {
48 | return compilationErrorInfo?.hasCompilationError ?: false
49 | }
50 |
51 | /**
52 | * Retrieves the set of file names that caused compilation errors for the given [project].
53 | *
54 | * @return A [Set] of file paths that had errors, or an empty set if none are recorded.
55 | */
56 | fun getErrorFiles(): Set {
57 | return compilationErrorInfo?.errorFiles ?: emptySet()
58 | }
59 |
60 | /**
61 | * Updates the compilation error status for the given [project].
62 | *
63 | * @param hasError Whether the project has compilation errors.
64 | * @param files The set of file names (or paths) that caused compilation errors.
65 | */
66 | private fun setHasCompilationError(hasError: Boolean, files: Set = emptySet()) {
67 | val info = compilationErrorInfo ?: CompilationErrorInfo()
68 | info.hasCompilationError = hasError
69 | info.errorFiles.clear()
70 | info.errorFiles.addAll(files)
71 | this.compilationErrorInfo = info
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/plugin-api-test-client/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/ui/settings/VaadinSettingsComponent.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.ui.settings
2 |
3 | import com.intellij.ide.BrowserUtil
4 | import com.intellij.ui.ColorUtil
5 | import com.intellij.ui.HyperlinkAdapter
6 | import com.intellij.ui.JBColor
7 | import com.intellij.ui.components.JBCheckBox
8 | import com.intellij.ui.scale.JBUIScale.scale
9 | import com.intellij.util.ui.FormBuilder
10 | import com.intellij.util.ui.JBUI
11 | import com.intellij.util.ui.SwingHelper
12 | import com.intellij.util.ui.UIUtil
13 | import java.awt.GridBagConstraints
14 | import java.awt.GridBagLayout
15 | import javax.swing.JComponent
16 | import javax.swing.JPanel
17 | import javax.swing.event.HyperlinkEvent
18 | import javax.swing.text.html.HTMLDocument
19 |
20 | /** Supports creating and managing a [JPanel] for the Settings Dialog. */
21 | class VaadinSettingsComponent {
22 | val panel: JPanel
23 | private val sendUsageStatistics = JBCheckBox("Send usage statistics")
24 |
25 | init {
26 | val pane = JPanel(GridBagLayout())
27 | val viewer = SwingHelper.createHtmlViewer(true, null, JBColor.WHITE, JBColor.BLACK)
28 | viewer.isOpaque = false
29 | viewer.isFocusable = false
30 | UIUtil.doNotScrollToCaret(viewer)
31 | viewer.addHyperlinkListener(
32 | object : HyperlinkAdapter() {
33 | override fun hyperlinkActivated(e: HyperlinkEvent) {
34 | e.url?.let { BrowserUtil.browse(it) }
35 | }
36 | })
37 | viewer.text =
38 | "" +
39 | "Help us improve Vaadin plugin by sending anonymous usage statistics.
" +
40 | "Please note that this will not include personal data or any sensitive information,
" +
41 | "such as source code, file names, etc. The data sent complies with the Vaadin Privacy Policy." +
42 | ""
43 | val styleSheet = (viewer.document as HTMLDocument).styleSheet
44 | styleSheet.addRule(
45 | "body {font-size: " +
46 | UIUtil.getFontSize(UIUtil.FontSize.SMALL) +
47 | "; color: #" +
48 | ColorUtil.toHex(UIUtil.getContextHelpForeground()) +
49 | ";}")
50 | pane.add(
51 | viewer,
52 | GridBagConstraints(
53 | 0,
54 | GridBagConstraints.RELATIVE,
55 | 1,
56 | 1,
57 | 1.0,
58 | 0.0,
59 | GridBagConstraints.NORTHWEST,
60 | GridBagConstraints.BOTH,
61 | JBUI.insets(0, scale(24), 0, 0),
62 | 0,
63 | 0))
64 |
65 | panel =
66 | FormBuilder.createFormBuilder()
67 | .addComponent(sendUsageStatistics, 1)
68 | .addComponent(pane)
69 | .addComponentFillVertically(JPanel(), 0)
70 | .panel
71 | }
72 |
73 | val preferredFocusedComponent: JComponent
74 | get() = sendUsageStatistics
75 |
76 | var sendUsageStatisticsStatus: Boolean
77 | get() = sendUsageStatistics.isSelected
78 | set(newStatus) {
79 | sendUsageStatistics.isSelected = newStatus
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/utils/AmpliUtil.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.utils
2 |
3 | import ai.grazie.utils.mpp.UUID
4 | import com.amplitude.ampli.Ampli
5 | import com.amplitude.ampli.DebugWithHotswap
6 | import com.amplitude.ampli.EventOptions
7 | import com.amplitude.ampli.LoadOptions
8 | import com.amplitude.ampli.ManualCopilotRestart
9 | import com.amplitude.ampli.PluginInitialized
10 | import com.amplitude.ampli.ProjectCreated
11 | import com.amplitude.ampli.ampli
12 | import com.intellij.openapi.application.ApplicationInfo
13 | import com.intellij.util.io.DigestUtil
14 | import com.vaadin.plugin.copilot.CopilotPluginUtil
15 | import com.vaadin.plugin.ui.settings.VaadinSettings
16 | import com.vaadin.pro.licensechecker.LocalProKey
17 | import elemental.json.Json
18 | import java.io.IOException
19 | import java.nio.charset.Charset
20 |
21 | private val eventOptions =
22 | EventOptions(
23 | platform = ApplicationInfo.getInstance().fullApplicationName,
24 | deviceModel = if (isUltimate()) "ultimate" else "community",
25 | language = System.getProperty("user.language"),
26 | country = System.getProperty("user.country"),
27 | region = System.getProperty("user.region"),
28 | osName = System.getProperty("os.name"),
29 | osVersion = System.getProperty("os.version"),
30 | appVersion = CopilotPluginUtil.getPluginVersion())
31 |
32 | private var userId: String? = null
33 |
34 | private var vaadiner: Boolean? = null
35 |
36 | private fun getUserId(): String {
37 | if (userId == null) {
38 | userId =
39 | try {
40 | VaadinHomeUtil.getUserKey()
41 | } catch (e: IOException) {
42 | "user-" + UUID.random().text
43 | }
44 | ampli.load(LoadOptions(Ampli.Environment.IDEPLUGINS))
45 | ampli.identify(userId, eventOptions)
46 | }
47 | return userId!!
48 | }
49 |
50 | private fun isVaadiner(): Boolean {
51 | if (vaadiner == null) {
52 | val proKey = LocalProKey.get()
53 | if (proKey != null) {
54 | val json = Json.parse(proKey.toJson())
55 | vaadiner = if (json.hasKey("username")) json.getString("username").endsWith("@vaadin.com") else false
56 | } else {
57 | vaadiner = false
58 | }
59 | }
60 | return vaadiner!!
61 | }
62 |
63 | private fun getProKeyDigest(): String? {
64 | val proKey = LocalProKey.get()
65 | return if (proKey != null) {
66 | DigestUtil.sha256Hex(proKey.proKey.toByteArray(Charset.defaultCharset()))
67 | } else {
68 | null
69 | }
70 | }
71 |
72 | private val enabled: Boolean
73 | get() = VaadinSettings.instance.state.sendUsageStatistics
74 |
75 | internal fun trackPluginInitialized() {
76 | if (enabled) {
77 | ampli.pluginInitialized(getUserId(), PluginInitialized(isVaadiner(), getProKeyDigest()))
78 | }
79 | }
80 |
81 | internal fun trackProjectCreated(downloadUrl: String) {
82 | if (enabled) {
83 | ampli.projectCreated(getUserId(), ProjectCreated(isVaadiner(), downloadUrl))
84 | }
85 | }
86 |
87 | internal fun trackManualCopilotRestart() {
88 | if (enabled) {
89 | ampli.manualCopilotRestart(getUserId(), ManualCopilotRestart(isVaadiner()))
90 | }
91 | }
92 |
93 | internal fun trackDebugWithHotswap() {
94 | if (enabled) {
95 | ampli.debugWithHotswap(getUserId(), DebugWithHotswap(isVaadiner()))
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/module/VaadinProjectBuilderAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.module
2 |
3 | import com.intellij.ide.actions.NewProjectAction
4 | import com.intellij.ide.util.projectWizard.WizardContext
5 | import com.intellij.ide.wizard.GeneratorNewProjectWizardBuilderAdapter
6 | import com.intellij.ide.wizard.NewProjectWizardStep
7 | import com.intellij.openapi.fileEditor.FileEditorManager
8 | import com.intellij.openapi.fileEditor.OpenFileDescriptor
9 | import com.intellij.openapi.project.Project
10 | import com.intellij.openapi.util.io.FileUtil
11 | import com.intellij.openapi.vfs.VfsUtil
12 | import com.intellij.util.io.ZipUtil
13 | import com.vaadin.plugin.utils.DownloadUtil
14 | import com.vaadin.plugin.utils.IdeUtil
15 | import com.vaadin.plugin.utils.IdeUtil.getIdeaDirectoryPath
16 | import com.vaadin.plugin.utils.getVaadinPluginDescriptor
17 | import com.vaadin.plugin.utils.trackProjectCreated
18 | import java.io.File
19 | import java.nio.file.Files
20 | import java.nio.file.Path
21 |
22 | class VaadinProjectBuilderAdapter(private val vaadinWizard: VaadinProjectWizard = VaadinProjectWizard()) :
23 | GeneratorNewProjectWizardBuilderAdapter(vaadinWizard) {
24 |
25 | override fun createStep(context: WizardContext): NewProjectWizardStep {
26 | return vaadinWizard.createStep(context)
27 | }
28 |
29 | override fun createProject(name: String?, path: String?): Project? {
30 | return super.createProject(name, path)?.let { project ->
31 | val outputPath = Path.of(path!!)
32 | val downloadUrl = vaadinWizard.projectModel!!.getDownloadLink(project)
33 | val tempFile = FileUtil.generateRandomTemporaryPath("project", ".zip")
34 | DownloadUtil.download(project, downloadUrl, tempFile.toPath(), "Vaadin Project")
35 | .thenRun {
36 | // project zip contains single root directory, move contents to parent after
37 | // extracting
38 | val zipRoot = DownloadUtil.getZipRootFolder(tempFile)!!
39 | ZipUtil.extract(tempFile.toPath(), outputPath, null, true)
40 | IdeUtil.moveDirectoryContentsRobustly(outputPath.resolve(zipRoot).toFile(), outputPath.toFile())
41 | FileUtil.delete(tempFile)
42 | copyVaadinIcon(project)
43 | }
44 | .thenRun {
45 | afterProjectCreated(project)
46 | trackProjectCreated(downloadUrl)
47 | }
48 | project
49 | }
50 | }
51 |
52 | // Show wizard only for new projects, not modules
53 | override fun isAvailable(): Boolean {
54 | return Thread.currentThread().stackTrace.any { element ->
55 | element.className.contains(NewProjectAction::class.java.name)
56 | }
57 | }
58 |
59 | private fun afterProjectCreated(project: Project) {
60 | VfsUtil.findFileByIoFile(File(project.basePath, "README.md"), true)?.let {
61 | val descriptor = OpenFileDescriptor(project, it)
62 | descriptor.isUsePreviewTab = true
63 | FileEditorManager.getInstance(project).openEditor(descriptor, true)
64 | }
65 | }
66 |
67 | private fun copyVaadinIcon(project: Project) {
68 | val ideaDir = getIdeaDirectoryPath(project)
69 | if (ideaDir != null) {
70 | FileUtil.createIfDoesntExist(ideaDir.toFile())
71 | val classLoader = getVaadinPluginDescriptor().classLoader
72 | classLoader.getResourceAsStream("icon.png")?.use { inputStream ->
73 | Files.copy(inputStream, ideaDir.resolve("icon.png"))
74 | }
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/UndoHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.WriteAction
4 | import com.intellij.openapi.application.runInEdt
5 | import com.intellij.openapi.command.impl.UndoManagerImpl
6 | import com.intellij.openapi.command.undo.UndoManager
7 | import com.intellij.openapi.fileEditor.FileEditor
8 | import com.intellij.openapi.project.Project
9 | import com.intellij.openapi.vfs.VfsUtil
10 | import com.intellij.openapi.vfs.VirtualFile
11 | import com.intellij.openapi.vfs.findDocument
12 | import io.netty.handler.codec.http.HttpResponseStatus
13 | import java.io.File
14 |
15 | open class UndoHandler(project: Project, data: Map) : AbstractHandler(project) {
16 |
17 | private val vfsFiles: ArrayList = ArrayList()
18 |
19 | init {
20 | val paths = data["files"] as Collection
21 | for (path in paths) {
22 | val file = File(path)
23 | if (isFileInsideProject(project, file)) {
24 | var vfsFile = VfsUtil.findFileByIoFile(file, true)
25 | if (vfsFile != null) {
26 | vfsFiles.add(vfsFile)
27 | } else {
28 | // if we want to undo file removal we need to create empty virtual file to write
29 | // content
30 | runInEdt {
31 | WriteAction.run {
32 | val parent = VfsUtil.createDirectories(file.parent)
33 | vfsFile = parent.createChildData(this, file.name)
34 | vfsFiles.add(vfsFile!!)
35 | }
36 | }
37 | }
38 | } else {
39 | LOG.warn("File $path is not a part of a project")
40 | }
41 | }
42 | }
43 |
44 | override fun run(): HandlerResponse {
45 | var performed = false
46 | for (vfsFile in vfsFiles) {
47 | val count = getOpsCount(vfsFile)
48 | if (count == 0) {
49 | continue
50 | }
51 |
52 | performed = true
53 |
54 | runInEdt {
55 | getEditorWrapper(vfsFile).use { wrapper ->
56 | val undoManager = UndoManagerImpl.getInstance(project)
57 | val editor = wrapper.getFileEditor()
58 | WriteAction.run {
59 | try {
60 | before(vfsFile)
61 | var i = 0
62 | while (i++ < count) {
63 | runManagerAction(undoManager, editor)
64 | }
65 | commitAndFlush(vfsFile.findDocument())
66 | } finally {
67 | after(vfsFile)
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
74 | val data = mapOf("performed" to performed)
75 | return HandlerResponse(HttpResponseStatus.OK, data)
76 | }
77 |
78 | open fun getOpsCount(vfsFile: VirtualFile): Int {
79 | return getCopilotUndoManager().getUndoCount(vfsFile)
80 | }
81 |
82 | open fun before(vfsFile: VirtualFile) {
83 | getCopilotUndoManager().undoStart(vfsFile)
84 | }
85 |
86 | open fun runManagerAction(undoManager: UndoManager, editor: FileEditor) {
87 | undoManager.undo(editor)
88 | }
89 |
90 | open fun after(vfsFile: VirtualFile) {
91 | getCopilotUndoManager().undoDone(vfsFile)
92 | LOG.info("$vfsFile undo performed")
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/AbstractHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.diagnostic.Logger
4 | import com.intellij.openapi.editor.Document
5 | import com.intellij.openapi.fileEditor.FileDocumentManager
6 | import com.intellij.openapi.fileEditor.FileEditor
7 | import com.intellij.openapi.fileEditor.FileEditorManager
8 | import com.intellij.openapi.fileEditor.OpenFileDescriptor
9 | import com.intellij.openapi.project.Project
10 | import com.intellij.openapi.vfs.VirtualFile
11 | import com.intellij.openapi.vfs.findDocument
12 | import com.intellij.psi.PsiDocumentManager
13 | import com.vaadin.plugin.copilot.CopilotPluginUtil
14 | import com.vaadin.plugin.copilot.CopilotPluginUtil.Companion.getBaseDirectoriesForProject
15 | import com.vaadin.plugin.copilot.service.CopilotUndoManager
16 | import io.netty.handler.codec.http.HttpResponseStatus
17 | import java.io.File
18 |
19 | abstract class AbstractHandler(val project: Project) : Handler {
20 |
21 | val LOG: Logger = Logger.getInstance(CopilotPluginUtil::class.java)
22 |
23 | val RESPONSE_OK = HandlerResponse(HttpResponseStatus.OK)
24 |
25 | val RESPONSE_BAD_REQUEST = HandlerResponse(HttpResponseStatus.BAD_REQUEST)
26 |
27 | val RESPONSE_ERROR = HandlerResponse(HttpResponseStatus.INTERNAL_SERVER_ERROR)
28 |
29 | class FileEditorWrapper(
30 | private val fileEditor: FileEditor,
31 | private val project: Project,
32 | private val closable: Boolean,
33 | ) : AutoCloseable {
34 |
35 | fun getFileEditor(): FileEditor {
36 | return fileEditor
37 | }
38 |
39 | override fun close() {
40 | if (closable) {
41 | FileEditorManager.getInstance(project).closeFile(fileEditor.file)
42 | }
43 | }
44 | }
45 |
46 | fun isFileInsideProject(project: Project, file: File): Boolean {
47 | if (file.exists()) {
48 | val path = file.toPath()
49 | return getBaseDirectoriesForProject(project).values.flatten().any { baseDirectory ->
50 | path.startsWith(baseDirectory)
51 | }
52 | } else {
53 | // New file
54 | return isFileInsideProject(project, file.parentFile)
55 | }
56 | }
57 |
58 | fun getEditorWrapper(vfsFile: VirtualFile): FileEditorWrapper {
59 | val manager = FileEditorManager.getInstance(project)
60 | val editors = manager.getEditors(vfsFile)
61 | if (editors.isEmpty()) {
62 | return FileEditorWrapper(manager.openFile(vfsFile, false).first(), project, true)
63 | }
64 | return FileEditorWrapper(editors.first(), project, false)
65 | }
66 |
67 | open fun postSave(vfsFile: VirtualFile) {
68 | LOG.info("File $vfsFile contents saved")
69 | notifyUndoManager(vfsFile)
70 | commitAndFlush(vfsFile.findDocument())
71 | openFileInEditor(vfsFile)
72 | }
73 |
74 | fun openFileInEditor(vfsFile: VirtualFile) {
75 | val openFileDescriptor = OpenFileDescriptor(project, vfsFile)
76 | FileEditorManager.getInstance(project).openTextEditor(openFileDescriptor, false)
77 | }
78 |
79 | fun notifyUndoManager(vfsFile: VirtualFile) {
80 | getCopilotUndoManager().fileWritten(vfsFile)
81 | }
82 |
83 | fun commitAndFlush(vfsDoc: Document?) {
84 | if (vfsDoc != null) {
85 | PsiDocumentManager.getInstance(project).commitDocument(vfsDoc)
86 | FileDocumentManager.getInstance().saveDocuments(vfsDoc::equals)
87 | }
88 | }
89 |
90 | fun getCopilotUndoManager(): CopilotUndoManager {
91 | return project.getService(CopilotUndoManager::class.java)
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/service/CopilotDotfileServiceImpl.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.service
2 |
3 | import com.intellij.openapi.application.WriteAction
4 | import com.intellij.openapi.application.runInEdt
5 | import com.intellij.openapi.project.Project
6 | import com.intellij.openapi.project.ProjectManager
7 | import com.intellij.openapi.project.ProjectManagerListener
8 | import com.intellij.openapi.vfs.VfsUtil
9 | import com.intellij.openapi.vfs.VirtualFileManager
10 | import com.intellij.openapi.vfs.newvfs.BulkFileListener
11 | import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
12 | import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent
13 | import com.intellij.openapi.vfs.newvfs.events.VFileEvent
14 | import com.vaadin.plugin.utils.IdeUtil.getIdeaDirectoryPath
15 | import java.io.IOException
16 | import java.nio.file.Files
17 | import java.nio.file.Path
18 | import kotlinx.coroutines.flow.MutableStateFlow
19 | import kotlinx.coroutines.flow.StateFlow
20 | import kotlinx.coroutines.flow.asStateFlow
21 |
22 | class CopilotDotfileServiceImpl(private val project: Project) : CopilotDotfileService {
23 |
24 | private val DOTFILE = ".copilot-plugin"
25 |
26 | private val _fileExists = MutableStateFlow(false)
27 | private val fileExists: StateFlow = _fileExists.asStateFlow()
28 |
29 | init {
30 | val connection = project.messageBus.connect()
31 | connection.subscribe(
32 | VirtualFileManager.VFS_CHANGES,
33 | object : BulkFileListener {
34 | override fun after(events: List) {
35 | for (event in events) {
36 | when (event) {
37 | is VFileCreateEvent ->
38 | if (Path.of(event.path).equals(getDotfilePath())) _fileExists.value = true
39 | is VFileDeleteEvent ->
40 | if (Path.of(event.path).equals(getDotfilePath())) _fileExists.value = false
41 | }
42 | }
43 | }
44 | })
45 |
46 | // Initial state
47 | _fileExists.value = getDotfilePath()?.let { Files.exists(it) } ?: false
48 |
49 | ProjectManager.getInstance()
50 | .addProjectManagerListener(
51 | project,
52 | object : ProjectManagerListener {
53 | override fun projectClosing(project: Project) {
54 | removeDotfile()
55 | }
56 | },
57 | )
58 | }
59 |
60 | override fun isActive(): Boolean {
61 | return fileExists.value
62 | }
63 |
64 | override fun getDotfilePath(): Path? {
65 | return getIdeaDirectoryPath(project)?.resolve(DOTFILE)
66 | }
67 |
68 | @Throws(IOException::class)
69 | override fun removeDotfile() {
70 | runInEdt { WriteAction.run { getDotfilePath()?.let { VfsUtil.findFile(it, false)?.delete(this) } } }
71 | }
72 |
73 | @Throws(IOException::class)
74 | override fun createDotfile(content: String) {
75 | runInEdt {
76 | WriteAction.run {
77 | val dotfileDirectory =
78 | getIdeaDirectoryPath(project) ?: throw IOException("Could not find the .idea directory")
79 | val vfsDotfileDirectory =
80 | VfsUtil.createDirectoryIfMissing(dotfileDirectory.toString())
81 | ?: throw IOException("Could not create .idea directory")
82 | val dotfile = vfsDotfileDirectory.createChildData(this, DOTFILE)
83 | VfsUtil.saveText(dotfile, content)
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/service/CopilotUndoManager.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.service
2 |
3 | import com.intellij.openapi.components.Service
4 | import com.intellij.openapi.project.Project
5 | import com.intellij.openapi.vfs.VirtualFile
6 | import com.intellij.openapi.vfs.VirtualFileManager
7 | import com.intellij.openapi.vfs.newvfs.BulkFileListener
8 | import com.intellij.openapi.vfs.newvfs.events.VFileEvent
9 | import java.util.Stack
10 | import java.util.concurrent.locks.ReentrantLock
11 |
12 | @Service(Service.Level.PROJECT)
13 | class CopilotUndoManager(val project: Project) : BulkFileListener {
14 |
15 | companion object {
16 | const val ACTIONS_ON_SAVE_WINDOW = 1000 // ms window for actions on save
17 | }
18 |
19 | class Batch() {
20 | private val time = System.currentTimeMillis()
21 | private var count = 0
22 |
23 | fun getCount(): Int {
24 | return count
25 | }
26 |
27 | fun increment() {
28 | count += 1
29 | }
30 |
31 | fun isInProgress(): Boolean {
32 | return System.currentTimeMillis() - time <= ACTIONS_ON_SAVE_WINDOW
33 | }
34 | }
35 |
36 | private val undoStack: MutableMap> = mutableMapOf()
37 | private val redoStack: MutableMap> = mutableMapOf()
38 | private val locks: MutableMap = mutableMapOf()
39 |
40 | fun subscribeToVfsChanges() {
41 | project.messageBus.connect().subscribe(VirtualFileManager.VFS_CHANGES, this)
42 | }
43 |
44 | // increments latest batch for file if is current batch
45 | // locking prevents modifying stack during undo / redo
46 | override fun after(events: MutableList) {
47 | events
48 | .filter { ev -> ev.isFromSave }
49 | .filter { ev -> locks[ev.path] == null || !locks[ev.path]!!.isLocked }
50 | .forEach {
51 | val stack = undoStack[it.path]
52 | if (stack != null) {
53 | if (stack.peek().isInProgress()) {
54 | stack.peek().increment()
55 | } else {
56 | undoStack.remove(it.path)
57 | }
58 | }
59 | }
60 | }
61 |
62 | fun fileWritten(file: VirtualFile) {
63 | undoStack.getOrPut(file.path) { Stack() }.push(Batch())
64 | }
65 |
66 | fun getUndoCount(file: VirtualFile): Int {
67 | return undoStack[file.path]?.peek()?.getCount() ?: 0
68 | }
69 |
70 | fun getRedoCount(file: VirtualFile): Int {
71 | return redoStack[file.path]?.peek()?.getCount() ?: 0
72 | }
73 |
74 | fun undoStart(file: VirtualFile) {
75 | locks.getOrPut(file.path) { ReentrantLock() }.lock()
76 | }
77 |
78 | fun redoStart(file: VirtualFile) {
79 | locks.getOrPut(file.path) { ReentrantLock() }.lock()
80 | }
81 |
82 | fun undoDone(file: VirtualFile) {
83 | popAndPush(file, undoStack, redoStack)
84 | locks[file.path]?.unlock()
85 | }
86 |
87 | fun redoDone(file: VirtualFile) {
88 | popAndPush(file, redoStack, undoStack)
89 | locks[file.path]?.unlock()
90 | }
91 |
92 | private fun popAndPush(
93 | file: VirtualFile,
94 | fromStacksMap: MutableMap>,
95 | targetStacksMap: MutableMap>
96 | ) {
97 | val batch = fromStacksMap[file.path]?.pop()
98 | targetStacksMap.getOrPut(file.path) { Stack() }.push(batch)
99 | if (fromStacksMap[file.path]?.isEmpty() == true) {
100 | fromStacksMap.remove(file.path)
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/endpoints/VaadinUrlResolver.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.endpoints
2 |
3 | import com.intellij.microservices.jvm.cache.ModuleCacheValueHolder
4 | import com.intellij.microservices.jvm.cache.SourceLibSearchProvider
5 | import com.intellij.microservices.jvm.cache.UastCachedSearchUtils.sequenceWithCache
6 | import com.intellij.microservices.url.*
7 | import com.intellij.microservices.url.UrlPath.PathSegment
8 | import com.intellij.microservices.url.references.UrlPksParser
9 | import com.intellij.openapi.module.Module
10 | import com.intellij.openapi.module.ModuleManager
11 | import com.intellij.openapi.project.Project
12 | import com.intellij.psi.PsiAnchor
13 | import com.intellij.psi.PsiElement
14 | import com.intellij.psi.util.PartiallyKnownString
15 | import com.intellij.psi.util.SplitEscaper
16 | import com.vaadin.plugin.utils.VaadinIcons
17 | import com.vaadin.plugin.utils.hasVaadin
18 | import javax.swing.Icon
19 |
20 | internal class VaadinUrlResolverFactory : UrlResolverFactory {
21 | override fun forProject(project: Project): UrlResolver? {
22 | return if (hasVaadin(project)) VaadinUrlResolver(project) else null
23 | }
24 | }
25 |
26 | internal class VaadinUrlResolver(private val project: Project) : UrlResolver {
27 | override val supportedSchemes: List
28 | get() = HTTP_SCHEMES
29 |
30 | override fun getVariants(): Iterable {
31 | return getAllModuleVariants(project).asIterable()
32 | }
33 |
34 | override fun resolve(request: UrlResolveRequest): Iterable {
35 | if (request.method != HttpMethods.GET) return emptyList()
36 |
37 | val allModuleVariants = getAllModuleVariants(project).toList()
38 |
39 | return UrlPath.combinations(request.path)
40 | .flatMap { path -> allModuleVariants.asSequence().filter { it.path.isCompatibleWith(path) } }
41 | .asIterable()
42 | }
43 | }
44 |
45 | internal val VAADIN_ROUTES_SEARCH: SourceLibSearchProvider, Module> =
46 | SourceLibSearchProvider("VAADIN_ROUTES") { p, _, scope -> findFlowRoutes(p, scope).toList() }
47 |
48 | private fun getAllModuleVariants(project: Project): Sequence {
49 | val modules = ModuleManager.getInstance(project).modules
50 |
51 | return modules.asSequence().flatMap(::getVariants).map(::VaadinUrlTargetInfo)
52 | }
53 |
54 | private fun getVariants(module: Module): Sequence {
55 | if (!hasVaadin(module)) return emptySequence()
56 |
57 | return sequenceWithCache(ModuleCacheValueHolder(module), VAADIN_ROUTES_SEARCH)
58 | }
59 |
60 | private class VaadinUrlTargetInfo(route: VaadinRoute) : UrlTargetInfo {
61 | private val anchor: PsiAnchor = route.anchor
62 |
63 | override val authorities: List
64 | get() = emptyList()
65 |
66 | override val path: UrlPath = parseVaadinUrlMapping(route.urlMapping)
67 |
68 | override val icon: Icon
69 | get() = VaadinIcons.VAADIN_BLUE
70 |
71 | override val schemes: List
72 | get() = HTTP_SCHEMES
73 |
74 | override fun resolveToPsiElement(): PsiElement? = anchor.retrieve()
75 | }
76 |
77 | internal val vaadinUrlPksParser: UrlPksParser =
78 | UrlPksParser(
79 | splitEscaper = { _, _ -> SplitEscaper.AcceptAll },
80 | customPathSegmentExtractor = { part ->
81 | if (part.startsWith(":")) {
82 | val varName = part.removePrefix(":").substringBefore("?").substringBefore("(")
83 | PathSegment.Variable(varName)
84 | } else {
85 | PathSegment.Exact(part)
86 | }
87 | })
88 |
89 | internal fun parseVaadinUrlMapping(urlMapping: String): UrlPath {
90 | return vaadinUrlPksParser.parseUrlPath(PartiallyKnownString(urlMapping)).urlPath
91 | }
92 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/ui/VaadinStatusBarWidget.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.ui
2 |
3 | import com.intellij.openapi.project.DumbService
4 | import com.intellij.openapi.project.Project
5 | import com.intellij.openapi.ui.popup.JBPopupFactory
6 | import com.intellij.openapi.util.Disposer
7 | import com.intellij.openapi.wm.StatusBarWidget
8 | import com.intellij.openapi.wm.WindowManager
9 | import com.intellij.ui.IconManager
10 | import com.intellij.ui.awt.RelativePoint
11 | import com.intellij.util.Consumer
12 | import com.intellij.util.ui.JBUI.CurrentTheme.IconBadge
13 | import com.intellij.util.ui.UIUtil
14 | import com.vaadin.plugin.copilot.service.CopilotDotfileService
15 | import com.vaadin.plugin.utils.VaadinIcons
16 | import com.vaadin.plugin.utils.hasEndpoints
17 | import java.awt.Point
18 | import java.awt.event.MouseEvent
19 | import javax.swing.Icon
20 |
21 | class VaadinStatusBarWidget(private val project: Project) : StatusBarWidget, StatusBarWidget.IconPresentation {
22 |
23 | companion object {
24 | const val ID = "VaadinStatusBarPanel"
25 |
26 | fun update(project: Project) {
27 | WindowManager.getInstance().getStatusBar(project)?.updateWidget(ID)
28 | }
29 | }
30 |
31 | private var clicked: Boolean = false
32 |
33 | init {
34 | project.messageBus
35 | .connect()
36 | .subscribe(
37 | DumbService.DUMB_MODE,
38 | object : DumbService.DumbModeListener {
39 | override fun enteredDumbMode() {
40 | update(project)
41 | }
42 |
43 | override fun exitDumbMode() {
44 | update(project)
45 | }
46 | })
47 | }
48 |
49 | override fun ID(): String {
50 | return ID
51 | }
52 |
53 | override fun getPresentation(): StatusBarWidget.WidgetPresentation {
54 | return this
55 | }
56 |
57 | override fun getClickConsumer(): Consumer {
58 | return Consumer {
59 | clicked = true
60 | showPopup(it)
61 | update(project)
62 | }
63 | }
64 |
65 | private fun showPopup(e: MouseEvent) {
66 | val panel = VaadinStatusBarInfoPopupPanel(project)
67 | val popup = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, null).createPopup()
68 | panel.closePopup = { popup.cancel() }
69 | val dimension = popup.content.preferredSize
70 | val at = Point(0, -dimension.height)
71 | popup.show(RelativePoint(e.component, at))
72 | // destroy popup on unexpected project close
73 | Disposer.register(this, popup)
74 | }
75 |
76 | override fun getTooltipText(): String {
77 | if (isActive() && hasEndpoints()) {
78 | return "Vaadin plugin is active, all features are available"
79 | }
80 |
81 | if (DumbService.isDumb(project)) {
82 | return "Indexing is in progress, please wait"
83 | }
84 |
85 | return "Not all features are available, click to see details"
86 | }
87 |
88 | override fun getIcon(): Icon {
89 | if (clicked) {
90 | return VaadinIcons.VAADIN
91 | }
92 |
93 | if (isActive() && hasEndpoints()) {
94 | return VaadinIcons.VAADIN
95 | }
96 |
97 | if (DumbService.isDumb(project)) {
98 | return IconManager.getInstance().withIconBadge(VaadinIcons.VAADIN, UIUtil.getLabelForeground())
99 | }
100 |
101 | return IconManager.getInstance().withIconBadge(VaadinIcons.VAADIN, IconBadge.WARNING)
102 | }
103 |
104 | private fun isActive(): Boolean {
105 | return project.getService(CopilotDotfileService::class.java).isActive()
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/plugin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
2 | import org.jetbrains.intellij.platform.gradle.TestFrameworkType
3 | import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask
4 |
5 | plugins {
6 | id("java")
7 | id("org.jetbrains.kotlin.jvm") version "1.9.21"
8 | id("org.jetbrains.intellij.platform") version "2.10.4"
9 | id("com.diffplug.spotless") version "7.2.1"
10 |
11 | id("com.adarshr.test-logger") version "4.0.0"
12 | }
13 |
14 | // version for building plugin
15 | val buildVersion = "2024.2"
16 |
17 | // compatibility range
18 | val sinceBuildProperty = "242"
19 | val untilBuildProperty = "253.*"
20 |
21 | // version for verifying plugin, check validation.yml
22 | val verifyVersion =
23 | if (hasProperty("verifyVersion")) {
24 | property("verifyVersion") as String
25 | } else {
26 | buildVersion
27 | }
28 |
29 | group = "com.vaadin"
30 |
31 | val publishChannel =
32 | if (hasProperty("publishChannel")) {
33 | property("publishChannel") as String
34 | } else {
35 | "Stable"
36 | }
37 |
38 | java {
39 | sourceCompatibility = JavaVersion.VERSION_21
40 | targetCompatibility = JavaVersion.VERSION_21
41 | }
42 |
43 | kotlin { jvmToolchain(21) }
44 |
45 | repositories {
46 | mavenCentral()
47 | intellijPlatform { defaultRepositories() }
48 | }
49 |
50 | dependencies {
51 | implementation("com.amplitude:java-sdk:[1.8.0,2.0)")
52 | implementation("org.json:json:20201115")
53 | implementation("com.vaadin:license-checker:1.13.3") {
54 | exclude(group = "net.java.dev.jna", module = "jna")
55 | exclude(group = "net.java.dev.jna", module = "jna-platform")
56 | }
57 |
58 | intellijPlatform {
59 | intellijIdeaUltimate(buildVersion)
60 | bundledPlugin("com.intellij.java")
61 | bundledPlugin("org.jetbrains.idea.maven")
62 | bundledPlugin("com.intellij.properties")
63 | bundledPlugin("com.intellij.microservices.jvm")
64 | bundledPlugin("JavaScript")
65 |
66 | pluginVerifier()
67 | zipSigner()
68 |
69 | testFramework(TestFrameworkType.Platform)
70 | }
71 |
72 | testImplementation(kotlin("test"))
73 | testImplementation("junit:junit:4.13.2")
74 | testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
75 | }
76 |
77 | intellijPlatform {
78 | pluginConfiguration {
79 | version =
80 | if (hasProperty("projVersion")) {
81 | property("projVersion") as String
82 | } else {
83 | "1.0-SNAPSHOT"
84 | }
85 | }
86 | pluginVerification {
87 | ides { create(IntelliJPlatformType.IntellijIdea, verifyVersion) }
88 | verificationReportsFormats = listOf(VerifyPluginTask.VerificationReportsFormats.MARKDOWN)
89 | }
90 | }
91 |
92 | configure {
93 | kotlin {
94 | // by default the target is every '.kt' and '.kts` file in the java sourcesets
95 | ktfmt("0.51").googleStyle().configure {
96 | it.setMaxWidth(120)
97 | it.setBlockIndent(4)
98 | it.setContinuationIndent(4)
99 | it.setRemoveUnusedImports(true)
100 | it.setManageTrailingCommas(false)
101 | }
102 | }
103 | kotlinGradle {
104 | target("*.gradle.kts") // default target for kotlinGradle
105 | ktfmt()
106 | }
107 | }
108 |
109 | tasks {
110 | patchPluginXml {
111 | sinceBuild.set(sinceBuildProperty)
112 | untilBuild.set(untilBuildProperty)
113 | }
114 |
115 | signPlugin {
116 | certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
117 | privateKey.set(System.getenv("PRIVATE_KEY"))
118 | password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
119 | }
120 |
121 | publishPlugin {
122 | channels.set(listOf(publishChannel))
123 | token.set(System.getenv("PUBLISH_TOKEN"))
124 | }
125 |
126 | test { useJUnitPlatform() }
127 | }
128 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/hotswapagent/HotswapAgentProgramPatcher.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.hotswapagent
2 |
3 | import com.intellij.execution.Executor
4 | import com.intellij.execution.JavaRunConfigurationBase
5 | import com.intellij.execution.configurations.JavaParameters
6 | import com.intellij.execution.configurations.RunProfile
7 | import com.intellij.execution.runners.JavaProgramPatcher
8 | import com.intellij.openapi.diagnostic.Logger
9 | import com.vaadin.plugin.utils.VaadinHomeUtil
10 |
11 | class HotswapAgentProgramPatcher : JavaProgramPatcher() {
12 |
13 | private val LOG: Logger = Logger.getInstance(HotswapAgentProgramPatcher::class.java)
14 |
15 | override fun patchJavaParameters(executor: Executor?, runProfile: RunProfile?, javaParameters: JavaParameters?) {
16 | if (executor !is HotswapAgentExecutor) {
17 | return
18 | }
19 | if (runProfile !is JavaRunConfigurationBase) {
20 | return
21 | }
22 | if (javaParameters == null) {
23 | return
24 | }
25 | val module = runProfile.configurationModule?.module ?: return
26 |
27 | if (runProfile.javaClass.simpleName == "SpringBootApplicationRunConfiguration") {
28 | turnOffFrameDeactivationPolicy(runProfile)
29 | }
30 | if (!JdkUtil.isJetbrainsRuntime(javaParameters.jdk)) {
31 | // Use the bundled Jetbrains Runtime
32 | try {
33 | javaParameters.jdk =
34 | JdkUtil.getCompatibleJetbrainsJdk(module)
35 | ?: throw IllegalArgumentException("The bundled JBR is not compatible with the project JDK")
36 | } catch (e: BrokenJbrException) {
37 | throw IllegalArgumentException("The bundled JBR is known to be broken")
38 | }
39 | }
40 | val agentInHome = VaadinHomeUtil.getHotSwapAgentJar()
41 |
42 | val paramsList = javaParameters.vmParametersList
43 |
44 | val addOpens = "--add-opens"
45 | paramsList.add(addOpens)
46 | paramsList.add("java.base/sun.nio.ch=ALL-UNNAMED")
47 | paramsList.add(addOpens)
48 | paramsList.add("java.base/java.lang=ALL-UNNAMED")
49 | paramsList.add(addOpens)
50 | paramsList.add("java.base/java.lang.reflect=ALL-UNNAMED")
51 | paramsList.add(addOpens)
52 | paramsList.add("java.base/java.io=ALL-UNNAMED")
53 | paramsList.add(addOpens)
54 | paramsList.add("java.base/sun.security.action=ALL-UNNAMED")
55 | paramsList.add(addOpens)
56 | paramsList.add("java.desktop/java.beans=ALL-UNNAMED")
57 | paramsList.add(addOpens)
58 | paramsList.add("java.desktop/com.sun.beans=ALL-UNNAMED")
59 | paramsList.add(addOpens)
60 | paramsList.add("java.desktop/com.sun.beans.introspect=ALL-UNNAMED")
61 | paramsList.add(addOpens)
62 | paramsList.add("java.desktop/com.sun.beans.util=ALL-UNNAMED")
63 | paramsList.add(addOpens)
64 | paramsList.add("java.base/jdk.internal.loader=ALL-UNNAMED")
65 |
66 | paramsList.add("-XX:+AllowEnhancedClassRedefinition")
67 | paramsList.add("-XX:+ClassUnloading")
68 |
69 | paramsList.add("-javaagent:$agentInHome")
70 | }
71 |
72 | private fun turnOffFrameDeactivationPolicy(runProfile: JavaRunConfigurationBase) {
73 | try {
74 | val getOptions = runProfile.javaClass.getDeclaredMethod("getOptions")
75 | getOptions.trySetAccessible()
76 | val options = getOptions.invoke(runProfile)
77 | val policy = options.javaClass.getDeclaredField("frameDeactivationUpdatePolicy\$delegate")
78 | policy.trySetAccessible()
79 |
80 | val prop = policy.get(options)
81 |
82 | prop.javaClass.getMethod("parseAndSetValue", String::class.java).invoke(prop, null)
83 | } catch (e: Exception) {
84 | LOG.debug("Failed to turn off frame deactivation policy", e)
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/hotswapagent/HotswapAgentRunner.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.hotswapagent
2 |
3 | import com.intellij.debugger.impl.GenericDebuggerRunner
4 | import com.intellij.execution.JavaRunConfigurationBase
5 | import com.intellij.execution.configurations.JavaCommandLine
6 | import com.intellij.execution.configurations.RunProfile
7 | import com.intellij.execution.runners.ExecutionEnvironment
8 | import com.intellij.notification.Notification
9 | import com.intellij.notification.NotificationAction
10 | import com.intellij.notification.NotificationType
11 | import com.intellij.notification.Notifications
12 | import com.intellij.openapi.application.ApplicationManager
13 | import com.intellij.openapi.application.invokeLater
14 | import com.vaadin.plugin.copilot.CopilotPluginUtil.Companion.NOTIFICATION_GROUP
15 | import com.vaadin.plugin.utils.trackDebugWithHotswap
16 |
17 | class HotswapAgentRunner : GenericDebuggerRunner() {
18 |
19 | override fun getRunnerId(): String {
20 | return "vaadin-hotswapagent-runner"
21 | }
22 |
23 | override fun canRun(executorId: String, profile: RunProfile): Boolean {
24 | return executorId.equals(HotswapAgentExecutor.ID)
25 | }
26 |
27 | override fun execute(environment: ExecutionEnvironment) {
28 | ApplicationManager.getApplication().executeOnPooledThread {
29 | if (environment.runProfile !is JavaRunConfigurationBase) {
30 | Notification(
31 | NOTIFICATION_GROUP,
32 | "To launch, open the Spring Boot application class and press \"Debug using Hotswap Agent\". " +
33 | "Do not launch through Maven or Gradle.",
34 | NotificationType.WARNING)
35 | .setTitle("Only Spring Boot applications are supported")
36 | .notify(environment.project)
37 | return@executeOnPooledThread
38 | }
39 |
40 | val runProfile = environment.runProfile as JavaRunConfigurationBase
41 | val javaCommandLine =
42 | environment.state as? JavaCommandLine
43 | ?: throw IllegalStateException("$runnerId needs a JavaCommandLine")
44 | val module =
45 | runProfile.configurationModule?.module ?: throw IllegalStateException("$runnerId needs a module")
46 |
47 | val javaParameters = javaCommandLine.javaParameters
48 | try {
49 | val jdkOk =
50 | JdkUtil.isJetbrainsRuntime(javaParameters.jdk) || JdkUtil.getCompatibleJetbrainsJdk(module) != null
51 |
52 | if (jdkOk) {
53 | trackDebugWithHotswap()
54 | invokeLater { super.execute(environment) }
55 | } else {
56 | invokeLater {
57 | val action =
58 | NotificationAction.create("Setup JetBrains Runtime...") { event, notification ->
59 | JdkUtil.createSdkPopupBuilder(environment.project)
60 | .onSdkSelected({ _ -> notification.hideBalloon() })
61 | .buildPopup()
62 | .showPopup(event)
63 | }
64 | Notifications.Bus.notify(
65 | Notification(
66 | NOTIFICATION_GROUP,
67 | "Current SDK is not a JetBrains Runtime. Set up JetBrains Runtime to enable Debug with HotSwap.",
68 | NotificationType.WARNING)
69 | .addAction(action),
70 | environment.project,
71 | )
72 | }
73 | }
74 | } catch (_: BrokenJbrException) {
75 | invokeLater { BadJBRFoundDialog().show() }
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/.github/workflows/validation.yml:
--------------------------------------------------------------------------------
1 | name: Validate
2 |
3 | on:
4 | pull_request:
5 |
6 | concurrency:
7 | group: "${{ github.workflow }}-${{ github.ref }}"
8 | cancel-in-progress: true
9 |
10 | jobs:
11 |
12 | codeChecks:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 | - name: Set up JDK 21
17 | uses: actions/setup-java@v4
18 | with:
19 | java-version: '21'
20 | distribution: 'temurin'
21 | cache: gradle
22 | - name: Check Spotless
23 | run: ./gradlew spotlessCheck
24 | - name: Run Unit Tests
25 | run: ./gradlew test
26 |
27 | buildPlugin:
28 | runs-on: ubuntu-latest
29 | needs: codeChecks
30 | steps:
31 | - uses: actions/checkout@v4
32 | - name: Set up JDK 21
33 | uses: actions/setup-java@v4
34 | with:
35 | java-version: '21'
36 | distribution: 'temurin'
37 | cache: gradle
38 | - name: Build plugin
39 | run: ./gradlew --no-configuration-cache buildPlugin
40 | - uses: actions/upload-artifact@v4.3.3
41 | id: artifact
42 | with:
43 | name: distributions
44 | path: plugin/build/distributions
45 | - uses: mshick/add-pr-comment@v2
46 | with:
47 | message-id: download-artifact-comment
48 | refresh-message-position: true
49 | message: |
50 | Artifact build on last commit: [distributions.zip](${{ steps.artifact.outputs.artifact-url }}).
51 | For MacOS users: there is a zip inside this zip and Finder unzips them both at once. Use `unzip distributions.zip` from Terminal or [check solution for Archive Manager](https://apple.stackexchange.com/questions/443607/is-there-a-way-to-prevent-macoss-archive-utility-from-unarchiving-inner-zip-fil).
52 |
53 | runPluginVerifier:
54 | runs-on: macos-latest
55 | needs: codeChecks
56 | strategy:
57 | fail-fast: false
58 | matrix:
59 | version: [ "253.28294.169" ] # Use EAP for 2025.3
60 | steps:
61 | - uses: actions/checkout@v4
62 | - name: Set up JDK 21
63 | uses: actions/setup-java@v4
64 | with:
65 | java-version: '21'
66 | distribution: 'temurin'
67 | cache: gradle
68 | - name: Verify plugin
69 | run: ./gradlew --no-configuration-cache -PverifyVersion=${{ matrix.version }} verifyPlugin
70 | - uses: actions/upload-artifact@v4
71 | with:
72 | name: pluginVerifierReports-${{ matrix.version }}
73 | path: plugin/build/reports/pluginVerifier
74 |
75 | waitForResults:
76 | runs-on: ubuntu-latest
77 | needs: runPluginVerifier
78 | steps:
79 | - uses: actions/download-artifact@v4
80 | - name: Get result directories
81 | run: |
82 | DIRECTORIES=$(find . -name 'I*' | jq -R -s -c 'split("\n")[:-1]')
83 | echo "DIRECTORIES=$DIRECTORIES" >> $GITHUB_ENV
84 | outputs:
85 | directories: ${{ env.DIRECTORIES }}
86 |
87 | addComments:
88 | runs-on: ubuntu-latest
89 | needs: waitForResults
90 | strategy:
91 | fail-fast: false
92 | matrix:
93 | dir: ${{fromJson(needs.waitForResults.outputs.directories)}}
94 | steps:
95 | - uses: actions/download-artifact@v4
96 | - run: |
97 | cat ${{ matrix.dir }}/report.md | head -n 3 | sed s/#/###/ > ${{ matrix.dir }}/report.short.md
98 | - name: Publish report
99 | uses: mshick/add-pr-comment@v2
100 | with:
101 | message-id: verify-plugin-comment-${{ matrix.dir }}
102 | refresh-message-position: true
103 | message-path: |
104 | ${{ matrix.dir }}/report.short.md
105 | - name: Fail on not compatible
106 | run: |
107 | if grep -q "Compatible." ${{ matrix.dir }}/report.short.md; then
108 | echo "✅ Compatible."
109 | else
110 | echo "❌ Not compatible!"
111 | exit 1
112 | fi
113 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/copilot/handler/WriteFileHandler.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.copilot.handler
2 |
3 | import com.intellij.openapi.application.runInEdt
4 | import com.intellij.openapi.command.CommandProcessor
5 | import com.intellij.openapi.command.UndoConfirmationPolicy
6 | import com.intellij.openapi.command.WriteCommandAction
7 | import com.intellij.openapi.editor.Document
8 | import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
9 | import com.intellij.openapi.project.Project
10 | import com.intellij.openapi.util.text.Strings
11 | import com.intellij.openapi.vfs.ReadonlyStatusHandler
12 | import com.intellij.openapi.vfs.VfsUtil
13 | import com.intellij.openapi.vfs.VirtualFile
14 | import com.intellij.openapi.vfs.findDocument
15 | import com.vaadin.plugin.utils.IdeUtil
16 | import io.netty.handler.codec.http.HttpResponseStatus
17 | import java.io.File
18 |
19 | open class WriteFileHandler(project: Project, data: Map) : AbstractHandler(project) {
20 |
21 | private val content: String = data["content"] as String
22 | private val undoLabel: String? = data["undoLabel"] as String?
23 | private val ioFile: File = File(data["file"] as String)
24 |
25 | override fun run(): HandlerResponse {
26 | var response = RESPONSE_OK
27 | if (isFileInsideProject(project, ioFile)) {
28 | // file exists, write content
29 | val vfsFile = VfsUtil.findFileByIoFile(ioFile, true)
30 | if (vfsFile?.exists() == true) {
31 | runInEdt {
32 | if (ReadonlyStatusHandler.ensureFilesWritable(project, vfsFile)) {
33 | writeAndFlush(vfsFile)
34 | } else {
35 | LOG.warn("File $ioFile is not writable")
36 | }
37 | }
38 | } else {
39 | // file does not exist, create new one
40 | LOG.info("File $ioFile does not exist, creating new file")
41 | create()
42 | if (IdeUtil.willVcsPopupBeShown(project)) {
43 | IdeUtil.bringToFront(project)
44 | response = HandlerResponse(HttpResponseStatus.OK, mapOf("blockingPopup" to "true"))
45 | }
46 | }
47 |
48 | return response
49 | } else {
50 | LOG.warn("File $ioFile is not a part of a project")
51 | return RESPONSE_BAD_REQUEST
52 | }
53 | }
54 |
55 | private fun writeAndFlush(vfsFile: VirtualFile) {
56 | vfsFile.findDocument()?.let {
57 | CommandProcessor.getInstance()
58 | .executeCommand(
59 | project,
60 | {
61 | WriteCommandAction.runWriteCommandAction(project) {
62 | doWrite(vfsFile, it, content)
63 | postSave(vfsFile)
64 | }
65 | },
66 | undoLabel ?: "Vaadin Copilot Write File",
67 | DocCommandGroupId.noneGroupId(it),
68 | UndoConfirmationPolicy.DO_NOT_REQUEST_CONFIRMATION,
69 | )
70 | }
71 | }
72 |
73 | private fun create() {
74 | runInEdt {
75 | CommandProcessor.getInstance()
76 | .executeCommand(
77 | project,
78 | {
79 | WriteCommandAction.runWriteCommandAction(project) {
80 | val vfsFile = doCreate(ioFile, content)
81 | postSave(vfsFile)
82 | }
83 | },
84 | undoLabel ?: "Vaadin Copilot Write File",
85 | null,
86 | UndoConfirmationPolicy.DO_NOT_REQUEST_CONFIRMATION,
87 | )
88 | }
89 | }
90 |
91 | open fun doCreate(file: File, content: String): VirtualFile {
92 | val parent = VfsUtil.createDirectories(file.parent)
93 | val vfsFile = parent.createChildData(this, file.name)
94 | doWrite(vfsFile, vfsFile.findDocument(), content)
95 | return vfsFile
96 | }
97 |
98 | open fun doWrite(vfsFile: VirtualFile?, doc: Document?, content: String) {
99 | doc?.setText(Strings.convertLineSeparators(content))
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/module/HelloWorldPanel.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.module
2 |
3 | import com.intellij.openapi.observable.properties.GraphProperty
4 | import com.intellij.openapi.observable.properties.PropertyGraph
5 | import com.intellij.openapi.ui.DialogPanel
6 | import com.intellij.ui.dsl.builder.Row
7 | import com.intellij.ui.dsl.builder.SegmentedButton
8 | import com.intellij.ui.dsl.builder.bindText
9 | import com.intellij.ui.dsl.builder.panel
10 | import com.vaadin.plugin.starter.HelloWorldModel
11 | import com.vaadin.plugin.starter.StarterSupport
12 |
13 | class HelloWorldPanel(private val model: HelloWorldModel) {
14 |
15 | private class SegmentModel(
16 | val values: LinkedHashMap,
17 | val property: GraphProperty,
18 | val supported: ((HelloWorldModel, String) -> Boolean)
19 | ) {
20 | var component: SegmentedButton? = null
21 |
22 | fun reset() {
23 | property.set(values.keys.first())
24 | }
25 |
26 | fun value(): String {
27 | return property.get()
28 | }
29 |
30 | fun label(): String {
31 | return values[value()]!!
32 | }
33 |
34 | fun label(key: String): String {
35 | return values[key]!!
36 | }
37 |
38 | fun update() {
39 | component?.let { values.forEach { v -> it.update(v.key) } }
40 | }
41 | }
42 |
43 | private class ViewModel(val model: HelloWorldModel) {
44 | val language = SegmentModel(StarterSupport.languages, model.languageProperty, { _, _ -> true })
45 | val buildTool =
46 | SegmentModel(StarterSupport.buildTools, model.buildToolProperty, StarterSupport::isSupportedBuildTool)
47 | val architecture =
48 | SegmentModel(
49 | StarterSupport.architectures, model.architectureProperty, StarterSupport::isSupportedArchitecture)
50 |
51 | fun all(): List {
52 | return listOf(language, buildTool, architecture)
53 | }
54 |
55 | fun isSupported(segmentModel: SegmentModel, value: String): Boolean {
56 | return segmentModel.supported.let { it(model, value) } ?: true
57 | }
58 | }
59 |
60 | private val viewModel: ViewModel = ViewModel(model)
61 |
62 | private val graph: PropertyGraph = PropertyGraph()
63 | private val kotlinInfoVisibleProperty = graph.property(false)
64 | private val notAllArchitecturesVisibleProperty = graph.property(false)
65 | private val notAllArchitecturesSupportedMessage = graph.property("")
66 |
67 | init {
68 | viewModel.language.property.afterChange { refreshSegments("language") }
69 | viewModel.buildTool.property.afterChange { refreshSegments("buildTool") }
70 | viewModel.architecture.property.afterChange { refreshSegments("architecture") }
71 | notAllArchitecturesSupportedMessage.afterChange { notAllArchitecturesVisibleProperty.set(it != "") }
72 | }
73 |
74 | private fun buildSegment(row: Row, segmentModel: SegmentModel) {
75 | segmentModel.component =
76 | row.segmentedButton(segmentModel.values.keys) {
77 | this.text = segmentModel.label(it)
78 | this.enabled = viewModel.isSupported(segmentModel, it)
79 | }
80 | .bind(segmentModel.property)
81 | }
82 |
83 | val root: DialogPanel = panel {
84 | buildSegment(row("Language") {}, viewModel.language)
85 | row("") { text("Kotlin support uses a community add-on.") }.visibleIf(kotlinInfoVisibleProperty)
86 | buildSegment(row("Build tool") {}, viewModel.buildTool)
87 | buildSegment(row("Architecture") {}, viewModel.architecture)
88 | row("") { text("").bindText(notAllArchitecturesSupportedMessage) }.visibleIf(notAllArchitecturesVisibleProperty)
89 | }
90 |
91 | private fun refreshSegments(source: String) {
92 | if (source != "language") {
93 | viewModel.language.update()
94 | }
95 | if (source != "buildTool") {
96 | viewModel.buildTool.update()
97 | }
98 | if (source != "architecture") {
99 | viewModel.architecture.update()
100 | }
101 | refreshKotlinInfo()
102 | fallbackToFirstEnabled()
103 | }
104 |
105 | private fun refreshKotlinInfo() {
106 | kotlinInfoVisibleProperty.set(viewModel.language.value() == "kotlin")
107 | }
108 |
109 | private fun fallbackToFirstEnabled() {
110 | viewModel.all().filter { !viewModel.isSupported(it, it.value()) }.forEach { it.reset() }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/actions/VaadinCompileOnSaveAction.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.actions
2 |
3 | import com.intellij.debugger.DebuggerManagerEx
4 | import com.intellij.debugger.ui.HotSwapUI
5 | import com.intellij.ide.actionsOnSave.impl.ActionsOnSaveFileDocumentManagerListener
6 | import com.intellij.openapi.application.ReadAction
7 | import com.intellij.openapi.diagnostic.Logger
8 | import com.intellij.openapi.editor.Document
9 | import com.intellij.openapi.fileEditor.FileDocumentManager
10 | import com.intellij.openapi.progress.ProgressIndicator
11 | import com.intellij.openapi.progress.ProgressManager
12 | import com.intellij.openapi.progress.Task
13 | import com.intellij.openapi.project.Project
14 | import com.intellij.openapi.roots.ProjectFileIndex
15 | import com.intellij.openapi.vfs.VirtualFile
16 | import com.intellij.task.ProjectTaskListener
17 | import com.intellij.task.ProjectTaskManager
18 | import com.vaadin.plugin.copilot.CopilotPluginUtil
19 | import java.util.concurrent.Executors
20 | import java.util.concurrent.Semaphore
21 |
22 | /** Action run after Document has been saved. Is not run for binary files. */
23 | class VaadinCompileOnSaveAction : ActionsOnSaveFileDocumentManagerListener.ActionOnSave() {
24 |
25 | private val LOG: Logger = Logger.getInstance(CopilotPluginUtil::class.java)
26 | private var compileLock = Semaphore(1)
27 |
28 | override fun isEnabledForProject(project: Project): Boolean {
29 | return VaadinCompileOnSaveActionInfo.isEnabledForProject(project)
30 | }
31 |
32 | override fun processDocuments(project: Project, documents: Array) {
33 | val task =
34 | object : Task.Backgroundable(project, "Vaadin: compiling...") {
35 | override fun run(indicator: ProgressIndicator) {
36 |
37 | LOG.info("Processing ${documents.size} document(s)")
38 |
39 | val fileIndex = ProjectFileIndex.getInstance(project)
40 | val vfsFiles =
41 | ReadAction.compute, Exception> {
42 | documents
43 | .mapNotNull { FileDocumentManager.getInstance().getFile(it) }
44 | .filter { fileIndex.isInSourceContent(it) }
45 | }
46 |
47 | if (vfsFiles.isEmpty()) {
48 | return
49 | }
50 |
51 | val javaFiles = vfsFiles.filter { it.extension == "java" }
52 | if (javaFiles.isNotEmpty()) {
53 | val session = DebuggerManagerEx.getInstanceEx(project).context.debuggerSession
54 | if (session != null) {
55 | val executorService = Executors.newSingleThreadExecutor()
56 | executorService.submit {
57 | // Wait for compile lock so only one compile task is running at a
58 | // time
59 | val timeout: Long = 30
60 | if (!compileLock.tryAcquire(timeout, java.util.concurrent.TimeUnit.SECONDS)) {
61 | // We did not get the compile lock. Assume something is wrong
62 | // and create a new lock so we won't block all further compiles
63 | LOG.warn(
64 | "Unable to acquire the compile lock in $timeout seconds. Creating a new lock and proceeding.")
65 | compileLock = Semaphore(1)
66 | compileLock.acquire()
67 | }
68 |
69 | val thisLock = compileLock
70 |
71 | LOG.debug("Compile starting for $javaFiles")
72 |
73 | val messageBusConnection = getProject().messageBus.connect()
74 | val listener =
75 | object : ProjectTaskListener {
76 | override fun finished(result: ProjectTaskManager.Result) {
77 | LOG.debug("Compile completed for $javaFiles")
78 | thisLock.release()
79 | messageBusConnection.disconnect()
80 | }
81 | }
82 |
83 | messageBusConnection.subscribe(ProjectTaskListener.TOPIC, listener)
84 |
85 | ReadAction.run {
86 | HotSwapUI.getInstance(project).compileAndReload(session, *javaFiles.toTypedArray())
87 | }
88 | }
89 | executorService.shutdown()
90 | }
91 | return
92 | }
93 |
94 | val nonJavaFiles = vfsFiles.filter { it.extension != "java" }
95 | if (nonJavaFiles.isNotEmpty()) {
96 | ProjectTaskManager.getInstance(project).compile(*nonJavaFiles.toTypedArray())
97 | }
98 | }
99 | }
100 | ProgressManager.getInstance().run(task)
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/plugin-api-test-client/src/main/java/com/vaadin/plugin/Client.java:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.JsonNode;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import org.springframework.http.MediaType;
7 | import org.springframework.web.client.RestClient;
8 |
9 | import java.io.IOException;
10 | import java.net.URI;
11 | import java.net.http.HttpClient;
12 | import java.net.http.HttpRequest;
13 | import java.net.http.HttpResponse;
14 | import java.nio.file.Path;
15 | import java.util.Collections;
16 | import java.util.Optional;
17 |
18 | public class Client {
19 |
20 | private final String endpoint;
21 |
22 | private final String projectBasePath;
23 |
24 | private final ObjectMapper objectMapper = new ObjectMapper();
25 |
26 | public Client(String endpoint, String projectBasePath) {
27 | this.endpoint = endpoint;
28 | this.projectBasePath = projectBasePath;
29 | }
30 |
31 | public RestClient.ResponseSpec undo(Path path) throws IOException {
32 | return send("undo", new Message.UndoRedoMessage(Collections.singletonList(path.toString())));
33 | }
34 |
35 | public RestClient.ResponseSpec redo(Path path) throws IOException {
36 | return send("redo", new Message.UndoRedoMessage(Collections.singletonList(path.toString())));
37 | }
38 |
39 | public RestClient.ResponseSpec write(Path path, String content) throws IOException {
40 | return send("write", new Message.WriteFileMessage(path.toString(), null, content));
41 | }
42 |
43 | public RestClient.ResponseSpec restartApplication() throws IOException {
44 | return send("restartApplication", new Message.RestartApplicationMessage());
45 | }
46 |
47 | public RestClient.ResponseSpec writeBinary(Path path, String content) throws IOException {
48 | return send("writeBase64", new Message.WriteFileMessage(path.toString(), null, content));
49 | }
50 |
51 | public RestClient.ResponseSpec showInIde(Path path) throws IOException {
52 | return send("showInIde", new Message.ShowInIdeMessage(path.toString(), -10, -2));
53 | }
54 |
55 | public RestClient.ResponseSpec refresh() throws IOException {
56 | return send("refresh", new Message.RefreshMessage());
57 | }
58 |
59 | public RestClient.ResponseSpec delete(Path path) throws IOException {
60 | return send("delete", new Message.DeleteMessage(path.toString()));
61 | }
62 |
63 | public Optional getVaadinRoutes() throws IOException {
64 | return sendRestSync("getVaadinRoutes", new Message.GetVaadinRoutesMessage());
65 | }
66 |
67 | public Optional getVaadinVersion() throws IOException {
68 | return sendRestSync("getVaadinVersion", new Message.GetVaadinVersionMessage());
69 | }
70 |
71 | public Optional getVaadinComponents(boolean includeMethods) throws IOException {
72 | return sendRestSync("getVaadinComponents", new Message.GetVaadinComponentsMessage(includeMethods));
73 | }
74 |
75 | public Optional getVaadinEntities(boolean includeMethods) throws IOException {
76 | return sendRestSync("getVaadinEntities", new Message.GetVaadinPersistenceMessage(includeMethods));
77 | }
78 |
79 | public Optional getVaadinSecurity() throws IOException {
80 | return sendRestSync("getVaadinSecurity", new Message.GetVaadinSecurityMessage());
81 | }
82 |
83 | private RestClient.ResponseSpec send(String command, Object data) throws JsonProcessingException {
84 | Message.CopilotRestRequest message = new Message.CopilotRestRequest(command, projectBasePath, data);
85 | String body = new ObjectMapper().writeValueAsString(message);
86 | org.springframework.web.client.RestClient.ResponseSpec response = org.springframework.web.client.RestClient.create().post()
87 | .uri(endpoint).contentType(MediaType.APPLICATION_JSON)
88 | .body(body).retrieve();
89 | return response;
90 | }
91 |
92 | // rest client
93 | private Optional sendRestSync(String command, Object dataCommand) {
94 | try {
95 | Message.CopilotRestRequest message = new Message.CopilotRestRequest(command, projectBasePath, dataCommand);
96 | byte[] data = objectMapper.writeValueAsBytes(message);
97 | HttpRequest request = HttpRequest.newBuilder().uri(URI.create(endpoint))
98 | .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofByteArray(data))
99 | .build();
100 | HttpResponse response = HttpClient.newHttpClient().send(request,
101 | HttpResponse.BodyHandlers.ofString());
102 | if (response.statusCode() != 200) {
103 | throw new IOException("Unexpected response (" + response.statusCode()
104 | + ") communicating with the IDE plugin: " + response.body());
105 | }
106 | if (response.body() != null && !response.body().isEmpty()) {
107 | JsonNode responseJson = objectMapper.readTree(response.body());
108 | return Optional.of(responseJson);
109 | }
110 | } catch (IOException e) {
111 | e.printStackTrace();
112 | } catch (InterruptedException e) {
113 | Thread.currentThread().interrupt();
114 | }
115 |
116 | return Optional.empty();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/plugin/src/main/kotlin/com/vaadin/plugin/utils/DownloadUtil.kt:
--------------------------------------------------------------------------------
1 | package com.vaadin.plugin.utils
2 |
3 | import com.intellij.openapi.diagnostic.Logger
4 | import com.intellij.openapi.project.Project
5 | import com.intellij.openapi.util.Pair
6 | import com.intellij.openapi.util.io.FileUtil
7 | import com.intellij.openapi.vfs.VirtualFile
8 | import com.intellij.util.download.DownloadableFileDescription
9 | import com.intellij.util.download.DownloadableFileService
10 | import com.intellij.util.net.HttpConfigurable
11 | import java.io.File
12 | import java.io.FileInputStream
13 | import java.io.FileOutputStream
14 | import java.io.IOException
15 | import java.net.InetSocketAddress
16 | import java.net.PasswordAuthentication
17 | import java.net.Proxy
18 | import java.net.URL
19 | import java.nio.file.Path
20 | import java.util.concurrent.CompletableFuture
21 | import java.util.zip.ZipFile
22 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream
23 | import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream
24 |
25 | object DownloadUtil {
26 |
27 | private val LOG: Logger = Logger.getInstance(DownloadUtil::class.java)
28 |
29 | /**
30 | * Open connection to given url using IDE proxy if configured
31 | *
32 | * @param urlStr resource URL
33 | * @return content as String
34 | */
35 | fun openUrlWithProxy(urlStr: String): String {
36 | val config = HttpConfigurable.getInstance()
37 | val url = URL(urlStr)
38 |
39 | val connection =
40 | if (config.USE_HTTP_PROXY && !config.isProxyException(url.toURI())) {
41 | val proxy = Proxy(Proxy.Type.HTTP, InetSocketAddress(config.PROXY_HOST, config.PROXY_PORT))
42 |
43 | if (config.PROXY_AUTHENTICATION) {
44 | java.net.Authenticator.setDefault(
45 | object : java.net.Authenticator() {
46 | override fun getPasswordAuthentication(): PasswordAuthentication {
47 | return PasswordAuthentication(
48 | config.proxyLogin, config.plainProxyPassword?.toCharArray())
49 | }
50 | })
51 | }
52 |
53 | url.openConnection(proxy)
54 | } else {
55 | url.openConnection()
56 | }
57 |
58 | return connection.getInputStream().bufferedReader().use { it.readText() }
59 | }
60 |
61 | /**
62 | * Downloads resource from given url in background with progress indicator
63 | *
64 | * @param project current project
65 | * @param url resource url
66 | * @param targetFile download target file
67 | * @param downloaderLabel label for download indicator progress widget
68 | * @return list of virtual files and descriptors as completable future
69 | */
70 | fun download(
71 | project: Project,
72 | url: String,
73 | targetFile: Path,
74 | downloaderLabel: String
75 | ): CompletableFuture?>?> {
76 | LOG.info("Downloading $url")
77 | val description =
78 | DownloadableFileService.getInstance().createFileDescription(url, targetFile.fileName.toString())
79 | val downloader = DownloadableFileService.getInstance().createDownloader(listOf(description), downloaderLabel)
80 | return downloader.downloadWithBackgroundProgress(targetFile.parent.toString(), project)
81 | }
82 |
83 | /**
84 | * Extracts tar gz archive
85 | *
86 | * @param tarGzPath archive file
87 | * @param outputDirPath output dir
88 | */
89 | fun extractTarGz(tarGzPath: File, outputDirPath: File) {
90 | val buffer = ByteArray(4096)
91 | val input = TarArchiveInputStream(GzipCompressorInputStream(FileInputStream(tarGzPath)))
92 |
93 | var entry = input.nextEntry
94 | while (entry != null) {
95 | val outFile = outputDirPath.resolve(entry.name)
96 | if (entry.isDirectory) {
97 | FileUtil.createDirectory(outFile)
98 | } else {
99 | FileUtil.createDirectory(outFile.parentFile)
100 | FileOutputStream(outFile).use { out ->
101 | var len: Int
102 | while (input.read(buffer).also { len = it } != -1) {
103 | out.write(buffer, 0, len)
104 | }
105 | }
106 | }
107 | entry = input.nextEntry
108 | }
109 | input.close()
110 | }
111 |
112 | /**
113 | * Finds root directory in zip archive if present
114 | *
115 | * @param zip input file
116 | * @return root directory name if present
117 | */
118 | @Throws(IOException::class)
119 | fun getZipRootFolder(zip: File): String? {
120 | ZipFile(zip).use { zipFile ->
121 | val en = zipFile.entries()
122 | while (en.hasMoreElements()) {
123 | val zipEntry = en.nextElement()
124 | // we do not necessarily get a separate entry for the subdirectory when the file
125 | // in the ZIP archive is placed in a subdirectory, so we need to check if the
126 | // slash
127 | // is found anywhere in the path
128 | val indexOf = zipEntry.name.indexOf('/')
129 | if (indexOf >= 0) {
130 | return zipEntry.name.substring(0, indexOf)
131 | }
132 | }
133 | return null
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------