From 420e84b40b2db801ee451aaeda211e3de7131cae Mon Sep 17 00:00:00 2001 From: Linh Pham Date: Wed, 20 Mar 2024 08:52:22 -0700 Subject: [PATCH 1/6] wip --- .../circuitfeature/CreateCircuitFeature.kt | 44 +++++++++++++++++ .../src/main/resources/META-INF/plugin.xml | 3 ++ ...Circuit Presenter (no UI).kt.child.0.kt.ft | 42 +++++++++++++++++ ...Circuit Presenter (no UI).kt.child.2.kt.ft | 47 +++++++++++++++++++ .../j2ee/Circuit Presenter (no UI).kt.ft | 23 +++++++++ ... (Presenter + Compose UI).kt.child.0.kt.ft | 42 +++++++++++++++++ ... (Presenter + Compose UI).kt.child.1.kt.ft | 44 +++++++++++++++++ ... (Presenter + Compose UI).kt.child.2.kt.ft | 45 ++++++++++++++++++ ... (Presenter + Compose UI).kt.child.3.kt.ft | 19 ++++++++ ...uit feature (Presenter + Compose UI).kt.ft | 23 +++++++++ .../UdfViewModel conversion.kt.child.0.kt.ft | 36 ++++++++++++++ .../j2ee/UdfViewModel conversion.kt.ft | 22 +++++++++ 12 files changed, 390 insertions(+) create mode 100644 skate-plugin/src/main/kotlin/com/slack/sgp/intellij/circuitfeature/CreateCircuitFeature.kt create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.0.kt.ft create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.2.kt.ft create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.ft create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.0.kt.ft create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.1.kt.ft create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.2.kt.ft create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.3.kt.ft create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.ft create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.child.0.kt.ft create mode 100644 skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.ft diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/circuitfeature/CreateCircuitFeature.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/circuitfeature/CreateCircuitFeature.kt new file mode 100644 index 000000000..bcf11fb25 --- /dev/null +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/circuitfeature/CreateCircuitFeature.kt @@ -0,0 +1,44 @@ +package com.slack.sgp.intellij.circuitfeature + +import com.intellij.codeInsight.template.TemplateManager +import com.intellij.ide.actions.CreateFileFromTemplateAction +import com.intellij.ide.actions.CreateFileFromTemplateDialog +import com.intellij.ide.fileTemplates.FileTemplate +import com.intellij.ide.fileTemplates.FileTemplateManager +import com.intellij.ide.fileTemplates.actions.AttributesDefaults +import com.intellij.ide.fileTemplates.ui.CreateFromTemplateDialog +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.gradleTooling.get +import java.util.Properties + +class CreateCircuitFeature : CreateFileFromTemplateAction( "Circuit Feature", "Creates new Circuit Feature", KotlinFileType.INSTANCE.icon), DumbAware { + override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) { + builder.setTitle("New Circuit Feature") + .addKind("Presenter + Compose UI", KotlinFileType.INSTANCE.icon, "Circuit feature (Presenter + Compose UI)") + .addKind("Presenter only", KotlinFileType.INSTANCE.icon, "Circuit feature (no UI)") + .addKind("UdfViewModel conversion", KotlinFileType.INSTANCE.icon, "UdfViewModel conversion") + } + + override fun getActionName(directory: PsiDirectory, newName: String, templateName: String) = "Circuit feature" + + override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? { + val allTemplate = FileTemplateManager.getInstance(dir.project).allJ2eeTemplates + allTemplate.forEach { child -> + logger().info("INSIDE") + logger().info(template.children.size.toString()) + logger().info(child.name) + logger().info(template.name) + logger().info(child.name.contains(template.name).toString()) + if (child.name.contains(template.name)) { + super.createFileFromTemplate(child.name, child, dir) + } + } + return null + } +} \ No newline at end of file diff --git a/skate-plugin/src/main/resources/META-INF/plugin.xml b/skate-plugin/src/main/resources/META-INF/plugin.xml index da4cf4c1c..af3d7adaf 100644 --- a/skate-plugin/src/main/resources/META-INF/plugin.xml +++ b/skate-plugin/src/main/resources/META-INF/plugin.xml @@ -55,6 +55,9 @@ description="Create new Slack subproject through Desktop Slack Project Generator app"> + + + \ No newline at end of file diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.0.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.0.kt.ft new file mode 100644 index 000000000..5310ff82f --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.0.kt.ft @@ -0,0 +1,42 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import com.slack.circuit.test.FakeNavigator +import com.slack.circuit.test.test +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import slack.foundation.coroutines.test.CoroutinesRule +import slack.test.SlackJvmTest + +class ${NAME}PresenterTest : SlackJvmTest() { + @get:Rule + val coroutineRule = CoroutinesRule() + + private val screen = ${NAME}Screen() + private val navigator = FakeNavigator() + + private val presenter = ${NAME}Presenter( + screen = screen, + navigator = navigator + ) + + @Test + fun `User taps text - tap counter is incremented`() = runTest { + presenter.test { + // Initial state + var state = awaitItem() + + assertEquals(state.message, "0") + + state.eventSink(${NAME}Screen.Event.UserTappedText) + state = awaitItem() + + assertEquals(state.message, "1") + + cancelAndIgnoreRemainingEvents() + } + } +} diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.2.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.2.kt.ft new file mode 100644 index 000000000..ded0d2d27 --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.2.kt.ft @@ -0,0 +1,47 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import com.slack.circuit.runtime.Navigator +import com.slack.circuit.runtime.presenter.Presenter +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import slack.di.UserScope +import slack.libraries.foundation.compose.rememberStableCoroutineScope + +/** + * TODO (remove): This Circuit [Presenter] was generated without UI or [CircuitInject], and can be + * used with legacy views via `by circuitState()` in your Fragment or Activity. + */ +class ${NAME}Presenter +@AssistedInject +constructor( + @Assisted private val screen: ${NAME}Screen, + @Assisted private val navigator: Navigator, +) : Presenter<${NAME}Screen.State> { + + @Composable + override fun present(): ${NAME}Screen.State { + val scope = rememberStableCoroutineScope() + var tapCounter by rememberSaveable { mutableIntStateOf(0) } + + return ${NAME}Screen.State( + message = tapCounter.toString() + ) { event -> + when (event) { + is ${NAME}Screen.Event.UserTappedText -> tapCounter++ + } + } + } + + @AssistedFactory + interface Factory { + fun create(screen: ${NAME}Screen, navigator: Navigator): ${NAME}Presenter + } +} \ No newline at end of file diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.ft new file mode 100644 index 000000000..1fb9e8996 --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.ft @@ -0,0 +1,23 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import androidx.compose.runtime.Immutable +import com.slack.circuit.runtime.CircuitUiEvent +import com.slack.circuit.runtime.CircuitUiState +import com.slack.circuit.runtime.screen.Screen +import kotlinx.parcelize.Parcelize + +@Parcelize +class ${NAME}Screen : Screen { + + data class State( + val message: String = "", + val eventSink: (Event) -> Unit = {} + ) : CircuitUiState + + @Immutable + sealed interface Event : CircuitUiEvent { + data object UserTappedText : Event + } +} diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.0.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.0.kt.ft new file mode 100644 index 000000000..bec9af5cb --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.0.kt.ft @@ -0,0 +1,42 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import com.slack.circuit.test.FakeNavigator +import com.slack.circuit.test.test +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test +import slack.foundation.coroutines.test.CoroutinesRule +import slack.test.SlackJvmTest + +class ${NAME}PresenterTest : SlackJvmTest() { + @get:Rule + val coroutineRule = CoroutinesRule() + + private val screen = ${NAME}Screen() + private val navigator = FakeNavigator(screen) + + private val presenter = ${NAME}Presenter( + screen = screen, + navigator = navigator + ) + + @Test + fun `User taps text - tap counter is incremented`() = runTest { + presenter.test { + // Initial state + var state = awaitItem() + + assertEquals(state.message, "0") + + state.eventSink(${NAME}Screen.Event.UserTappedText) + state = awaitItem() + + assertEquals(state.message, "1") + + cancelAndIgnoreRemainingEvents() + } + } +} diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.1.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.1.kt.ft new file mode 100644 index 000000000..d0a340952 --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.1.kt.ft @@ -0,0 +1,44 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasClickAction +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import kotlin.test.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import slack.test.android.SlackAndroidJvmTest + +class ${NAME}UiTest : SlackAndroidJvmTest() { + + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun `Clicking text emits tap event`() { + with(composeTestRule) { + var lastEvent: ${NAME}Screen.Event? = null + val uiState = ${NAME}Screen.State(message = "Test message") { + lastEvent = it + } + + setContent { + ${NAME}(state = uiState) + } + + onNodeWithText("Test message").assertIsDisplayed() + onNode( + hasText("Test message") + .and(hasClickAction()) + ) + .performClick() + + assertEquals(${NAME}Screen.Event.UserTappedText, lastEvent) + } + } +} diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.2.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.2.kt.ft new file mode 100644 index 000000000..eaeb61843 --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.2.kt.ft @@ -0,0 +1,45 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import com.slack.circuit.codegen.annotations.CircuitInject +import com.slack.circuit.runtime.Navigator +import com.slack.circuit.runtime.presenter.Presenter +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import slack.di.UserScope +import slack.libraries.foundation.compose.rememberStableCoroutineScope + +class ${NAME}Presenter +@AssistedInject +constructor( + @Assisted private val screen: ${NAME}Screen, + @Assisted private val navigator: Navigator, +) : Presenter<${NAME}Screen.State> { + + @Composable + override fun present(): ${NAME}Screen.State { + val scope = rememberStableCoroutineScope() + var tapCounter by rememberSaveable { mutableIntStateOf(0) } + + return ${NAME}Screen.State( + message = tapCounter.toString() + ) { event -> + when (event) { + is ${NAME}Screen.Event.UserTappedText -> tapCounter++ + } + } + } + + @CircuitInject(${NAME}Screen::class, UserScope::class) + @AssistedFactory + interface Factory { + fun create(screen: ${NAME}Screen, navigator: Navigator): ${NAME}Presenter + } +} \ No newline at end of file diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.3.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.3.kt.ft new file mode 100644 index 000000000..3d1dab06a --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.3.kt.ft @@ -0,0 +1,19 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import androidx.compose.foundation.clickable +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.slack.circuit.codegen.annotations.CircuitInject +import slack.di.UserScope + +@CircuitInject(${NAME}Screen::class, UserScope::class) +@Composable +fun ${NAME}(state: ${NAME}Screen.State, modifier: Modifier = Modifier) { + Text( + text = state.message, + modifier = modifier.clickable { state.eventSink(${NAME}Screen.Event.UserTappedText) } + ) +} \ No newline at end of file diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.ft new file mode 100644 index 000000000..1fb9e8996 --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.ft @@ -0,0 +1,23 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import androidx.compose.runtime.Immutable +import com.slack.circuit.runtime.CircuitUiEvent +import com.slack.circuit.runtime.CircuitUiState +import com.slack.circuit.runtime.screen.Screen +import kotlinx.parcelize.Parcelize + +@Parcelize +class ${NAME}Screen : Screen { + + data class State( + val message: String = "", + val eventSink: (Event) -> Unit = {} + ) : CircuitUiState + + @Immutable + sealed interface Event : CircuitUiEvent { + data object UserTappedText : Event + } +} diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.child.0.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.child.0.kt.ft new file mode 100644 index 000000000..2d89651f2 --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.child.0.kt.ft @@ -0,0 +1,36 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import androidx.lifecycle.ViewModel +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import slack.coreui.di.presenter.ViewModelKey +import slack.coreui.viewmodel.UdfViewModel +import slack.di.UserScope +import slack.foundation.coroutines.CloseableCoroutineScope +import slack.foundation.coroutines.SlackDispatchers + +@ContributesMultibinding(UserScope::class, boundType = ViewModel::class) +@ViewModelKey(${NAME}ViewModel::class) +class ${NAME}ViewModel +@Inject +constructor( + slackDispatchers: SlackDispatchers +) : + UdfViewModel<${NAME}Screen.State>(CloseableCoroutineScope.newMainScope(slackDispatchers)), + ${NAME}Screen.Events { + + private val state = MutableStateFlow(${NAME}Screen.State(events = this)) + private var tapCounter = 0 + + override fun state(): StateFlow<${NAME}Screen.State> = state + + override fun userTappedText() { + tapCounter++ + state.update { it.copy(message = tapCounter.toString()) } + } +} diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.ft new file mode 100644 index 000000000..ff9931bba --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.ft @@ -0,0 +1,22 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME} + +#end +#parse("File Header.java") +import androidx.compose.runtime.Immutable +import com.slack.circuit.runtime.CircuitUiState +import com.slack.circuit.runtime.screen.Screen +import kotlinx.parcelize.Parcelize + +@Parcelize +class ${NAME}Screen : Screen { + + data class State( + val message: String = "", + val events: Events + ) : CircuitUiState + + @Immutable + interface Events { + fun userTappedText() + } +} From 09805686d41a02029c3c09fbb2065deefd6a3c42 Mon Sep 17 00:00:00 2001 From: Linh Pham Date: Fri, 22 Mar 2024 00:10:29 -0700 Subject: [PATCH 2/6] create file from circuit template --- .../circuitfeature/CreateCircuitFeature.kt | 44 ------------ .../filetemplate/CreateCircuitFeature.kt | 68 +++++++++++++++++++ .../model/FileTemplateSettings.kt | 9 +++ .../filetemplate/model/TemplateSetting.kt | 7 ++ .../src/main/resources/META-INF/plugin.xml | 2 +- .../main/resources/fileTemplateSettings.yaml | 30 ++++++++ ...cuit Presenter (without UI).child.0.kt.ft} | 0 ...cuit Presenter (without UI).child.2.kt.ft} | 0 ...t => Circuit Presenter (without UI).kt.ft} | 0 ...cuit Presenter + Compose UI.child.0.kt.ft} | 0 ...cuit Presenter + Compose UI.child.1.kt.ft} | 0 ...cuit Presenter + Compose UI.child.2.kt.ft} | 0 ...cuit Presenter + Compose UI.child.3.kt.ft} | 0 ...t => Circuit Presenter + Compose UI.kt.ft} | 0 ....ft => UdfViewModel convert.child.0.kt.ft} | 0 ...rsion.kt.ft => UdfViewModel convert.kt.ft} | 0 16 files changed, 115 insertions(+), 45 deletions(-) delete mode 100644 skate-plugin/src/main/kotlin/com/slack/sgp/intellij/circuitfeature/CreateCircuitFeature.kt create mode 100644 skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt create mode 100644 skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateSettings.kt create mode 100644 skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/TemplateSetting.kt create mode 100644 skate-plugin/src/main/resources/fileTemplateSettings.yaml rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit Presenter (no UI).kt.child.0.kt.ft => Circuit Presenter (without UI).child.0.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit Presenter (no UI).kt.child.2.kt.ft => Circuit Presenter (without UI).child.2.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit Presenter (no UI).kt.ft => Circuit Presenter (without UI).kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit feature (Presenter + Compose UI).kt.child.0.kt.ft => Circuit Presenter + Compose UI.child.0.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit feature (Presenter + Compose UI).kt.child.1.kt.ft => Circuit Presenter + Compose UI.child.1.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit feature (Presenter + Compose UI).kt.child.2.kt.ft => Circuit Presenter + Compose UI.child.2.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit feature (Presenter + Compose UI).kt.child.3.kt.ft => Circuit Presenter + Compose UI.child.3.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit feature (Presenter + Compose UI).kt.ft => Circuit Presenter + Compose UI.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{UdfViewModel conversion.kt.child.0.kt.ft => UdfViewModel convert.child.0.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{UdfViewModel conversion.kt.ft => UdfViewModel convert.kt.ft} (100%) diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/circuitfeature/CreateCircuitFeature.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/circuitfeature/CreateCircuitFeature.kt deleted file mode 100644 index bcf11fb25..000000000 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/circuitfeature/CreateCircuitFeature.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.slack.sgp.intellij.circuitfeature - -import com.intellij.codeInsight.template.TemplateManager -import com.intellij.ide.actions.CreateFileFromTemplateAction -import com.intellij.ide.actions.CreateFileFromTemplateDialog -import com.intellij.ide.fileTemplates.FileTemplate -import com.intellij.ide.fileTemplates.FileTemplateManager -import com.intellij.ide.fileTemplates.actions.AttributesDefaults -import com.intellij.ide.fileTemplates.ui.CreateFromTemplateDialog -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.Project -import com.intellij.psi.PsiDirectory -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import org.jetbrains.kotlin.idea.KotlinFileType -import org.jetbrains.kotlin.idea.gradleTooling.get -import java.util.Properties - -class CreateCircuitFeature : CreateFileFromTemplateAction( "Circuit Feature", "Creates new Circuit Feature", KotlinFileType.INSTANCE.icon), DumbAware { - override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) { - builder.setTitle("New Circuit Feature") - .addKind("Presenter + Compose UI", KotlinFileType.INSTANCE.icon, "Circuit feature (Presenter + Compose UI)") - .addKind("Presenter only", KotlinFileType.INSTANCE.icon, "Circuit feature (no UI)") - .addKind("UdfViewModel conversion", KotlinFileType.INSTANCE.icon, "UdfViewModel conversion") - } - - override fun getActionName(directory: PsiDirectory, newName: String, templateName: String) = "Circuit feature" - - override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? { - val allTemplate = FileTemplateManager.getInstance(dir.project).allJ2eeTemplates - allTemplate.forEach { child -> - logger().info("INSIDE") - logger().info(template.children.size.toString()) - logger().info(child.name) - logger().info(template.name) - logger().info(child.name.contains(template.name).toString()) - if (child.name.contains(template.name)) { - super.createFileFromTemplate(child.name, child, dir) - } - } - return null - } -} \ No newline at end of file diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt new file mode 100644 index 000000000..435d3ff92 --- /dev/null +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt @@ -0,0 +1,68 @@ +package com.slack.sgp.intellij.filetemplate + +import com.charleskorn.kaml.Yaml +import com.charleskorn.kaml.YamlConfiguration +import com.charleskorn.kaml.decodeFromStream +import com.intellij.ide.actions.CreateFileFromTemplateAction +import com.intellij.ide.actions.CreateFileFromTemplateDialog +import com.intellij.ide.fileTemplates.FileTemplate +import com.intellij.ide.fileTemplates.FileTemplateManager +import com.intellij.ide.fileTemplates.FileTemplateUtil +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiManager +import com.slack.sgp.intellij.codeowners.model.CodeOwnersFile +import com.slack.sgp.intellij.filetemplate.model.FileTemplateSettings +import com.slack.sgp.intellij.filetemplate.model.TemplateSetting +import org.jetbrains.kotlin.idea.KotlinFileType +import java.io.File +import java.io.FileNotFoundException +import javax.xml.parsers.DocumentBuilderFactory + +class CreateCircuitFeature : CreateFileFromTemplateAction( "Circuit Feature", "Creates new Circuit Feature", KotlinFileType.INSTANCE.icon), DumbAware { + override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) { + builder.setTitle("New Circuit Feature Name") + .addKind("Presenter + Compose UI", KotlinFileType.INSTANCE.icon, "Circuit Presenter + Compose UI") + .addKind("Presenter only", KotlinFileType.INSTANCE.icon, "Circuit Presenter (without UI)") + .addKind("UdfViewModel conversion", KotlinFileType.INSTANCE.icon, "UdfViewModel convert") + } + + override fun getActionName(directory: PsiDirectory, newName: String, templateName: String) = "Circuit feature" + + override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? { + val templateSettings = fileTemplateSettingParser() + val allTemplate = FileTemplateManager.getInstance(dir.project).allJ2eeTemplates + + allTemplate.forEach { child -> + if (child.name.contains(template.name)) { + val properties = FileTemplateManager.getInstance(dir.project).defaultProperties + properties.setProperty("NAME", name) + val suffix = templateSettings[child.name]!!.fileNameSuffix + + // Create test directory + if (suffix.contains("Test") && !dir.virtualFile.path.contains("test")) { + val testPath = File(dir.virtualFile.path.replace("src/main", "src/test")) + testPath.mkdirs() + val testFolder = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(testPath) + val psiDir = testFolder?.let { PsiManager.getInstance(dir.project).findDirectory(it) } as PsiDirectory + FileTemplateUtil.createFromTemplate(child, name + suffix, properties, psiDir) + } else { + FileTemplateUtil.createFromTemplate(child, name + suffix, properties, dir) + } + } + } + return null + } + + private fun fileTemplateSettingParser() : Map { + Thread.currentThread().contextClassLoader = this.javaClass.classLoader + val template = Thread.currentThread().contextClassLoader.getResourceAsStream("fileTemplateSettings.yaml") + ?: throw FileNotFoundException("File template settings file not found") + val table = Yaml(configuration = YamlConfiguration(strictMode = false)).decodeFromStream(template) + return table.templates.associateBy { it.name } + } +} \ No newline at end of file diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateSettings.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateSettings.kt new file mode 100644 index 000000000..b2e62f6de --- /dev/null +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateSettings.kt @@ -0,0 +1,9 @@ +package com.slack.sgp.intellij.filetemplate.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class FileTemplateSettings( + @SerialName("templates") val templates: List, +) diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/TemplateSetting.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/TemplateSetting.kt new file mode 100644 index 000000000..306b62624 --- /dev/null +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/TemplateSetting.kt @@ -0,0 +1,7 @@ +package com.slack.sgp.intellij.filetemplate.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class TemplateSetting(val name: String, @SerialName("file_name_suffix") val fileNameSuffix: String) diff --git a/skate-plugin/src/main/resources/META-INF/plugin.xml b/skate-plugin/src/main/resources/META-INF/plugin.xml index af3d7adaf..0c8c321fe 100644 --- a/skate-plugin/src/main/resources/META-INF/plugin.xml +++ b/skate-plugin/src/main/resources/META-INF/plugin.xml @@ -55,7 +55,7 @@ description="Create new Slack subproject through Desktop Slack Project Generator app"> - + diff --git a/skate-plugin/src/main/resources/fileTemplateSettings.yaml b/skate-plugin/src/main/resources/fileTemplateSettings.yaml new file mode 100644 index 000000000..b93806787 --- /dev/null +++ b/skate-plugin/src/main/resources/fileTemplateSettings.yaml @@ -0,0 +1,30 @@ +templates: + - name: Circuit Presenter + Compose UI + file_name_suffix: Screen + + - name: Circuit Presenter + Compose UI.child.0 + file_name_suffix: PresenterTest + + - name: Circuit Presenter + Compose UI.child.1 + file_name_suffix: UiTest + + - name: Circuit Presenter + Compose UI.child.2 + file_name_suffix: Presenter + + - name: Circuit Presenter + Compose UI.child.3 + file_name_suffix: Ui + + - name: Circuit Presenter (without UI) + file_name_suffix: Screen + + - name: Circuit Presenter (without UI).child.0 + file_name_suffix: PresenterTest + + - name: Circuit Presenter (without UI).child.2 + file_name_suffix: Presenter + + - name: UdfViewModel convert + file_name_suffix: Screen + + - name: UdfViewModel convert.child.0 + file_name_suffix: ViewModel \ No newline at end of file diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.0.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (without UI).child.0.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.0.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (without UI).child.0.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.2.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (without UI).child.2.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.child.2.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (without UI).child.2.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (without UI).kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (no UI).kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter (without UI).kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.0.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.0.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.0.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.0.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.1.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.1.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.1.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.1.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.2.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.2.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.2.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.2.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.3.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.3.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.child.3.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.3.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit feature (Presenter + Compose UI).kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.child.0.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel convert.child.0.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.child.0.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel convert.child.0.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel convert.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel conversion.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/UdfViewModel convert.kt.ft From b7e05c76be13e4a34d159490a09c408944b7cfb4 Mon Sep 17 00:00:00 2001 From: Linh Pham Date: Fri, 22 Mar 2024 16:11:41 -0700 Subject: [PATCH 3/6] reorder the option --- .../filetemplate/CreateCircuitFeature.kt | 5 +---- .../src/main/resources/META-INF/plugin.xml | 22 +++++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt index 435d3ff92..1eb4573d1 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt @@ -8,20 +8,17 @@ import com.intellij.ide.actions.CreateFileFromTemplateDialog import com.intellij.ide.fileTemplates.FileTemplate import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.ide.fileTemplates.FileTemplateUtil -import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager -import com.slack.sgp.intellij.codeowners.model.CodeOwnersFile import com.slack.sgp.intellij.filetemplate.model.FileTemplateSettings import com.slack.sgp.intellij.filetemplate.model.TemplateSetting import org.jetbrains.kotlin.idea.KotlinFileType import java.io.File import java.io.FileNotFoundException -import javax.xml.parsers.DocumentBuilderFactory class CreateCircuitFeature : CreateFileFromTemplateAction( "Circuit Feature", "Creates new Circuit Feature", KotlinFileType.INSTANCE.icon), DumbAware { override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) { @@ -60,7 +57,7 @@ class CreateCircuitFeature : CreateFileFromTemplateAction( "Circuit Feature", "C private fun fileTemplateSettingParser() : Map { Thread.currentThread().contextClassLoader = this.javaClass.classLoader - val template = Thread.currentThread().contextClassLoader.getResourceAsStream("fileTemplateSettings.yaml") + val template = Thread.currentThread().contextClassLoader.getResourceAsStream("fileTemplateSettings") ?: throw FileNotFoundException("File template settings file not found") val table = Yaml(configuration = YamlConfiguration(strictMode = false)).decodeFromStream(template) return table.templates.associateBy { it.name } diff --git a/skate-plugin/src/main/resources/META-INF/plugin.xml b/skate-plugin/src/main/resources/META-INF/plugin.xml index 0c8c321fe..6071171e0 100644 --- a/skate-plugin/src/main/resources/META-INF/plugin.xml +++ b/skate-plugin/src/main/resources/META-INF/plugin.xml @@ -49,15 +49,19 @@ - - - - - - + + + + + + + + + + \ No newline at end of file From 3eb1e4e494c5e2d2728064f7c7bc9254a79aab97 Mon Sep 17 00:00:00 2001 From: Linh Pham Date: Thu, 28 Mar 2024 23:45:34 -0700 Subject: [PATCH 4/6] add ability to generate circuit feature template from project gen --- skate-plugin/build.gradle.kts | 5 ++ .../filetemplate/CreateCircuitFeature.kt | 53 ++----------------- .../filetemplate/CustomCreateFileAction.kt | 45 ++++++++++++++++ .../filetemplate/UdfViewModelMigrate.kt | 17 ++++++ .../filetemplate/model/FileTemplateFactory.kt | 23 ++++++++ .../projectgen/ProjectGenPresenter.kt | 37 ++++++++++++- .../sgp/intellij/projectgen/ProjectGenUi.kt | 50 +++++++++++++---- .../sgp/intellij/projectgen/UiElement.kt | 16 ++++++ .../slack/sgp/intellij/projectgen/models.kt | 26 ++++++++- .../src/main/resources/META-INF/plugin.xml | 3 ++ .../main/resources/fileTemplateSettings.yaml | 10 ++-- ...it Presenter and Compose UI.child.0.kt.ft} | 0 ...it Presenter and Compose UI.child.1.kt.ft} | 0 ...it Presenter and Compose UI.child.2.kt.ft} | 0 ...it Presenter and Compose UI.child.3.kt.ft} | 0 ...=> Circuit Presenter and Compose UI.kt.ft} | 0 .../FileTemplateSettingsParserTest.kt | 27 ++++++++++ .../test/resources/test_file_templates.yaml | 7 +++ ...test_malformed_file_templates_setting.yaml | 5 ++ 19 files changed, 258 insertions(+), 66 deletions(-) create mode 100644 skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt create mode 100644 skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/UdfViewModelMigrate.kt create mode 100644 skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateFactory.kt rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit Presenter + Compose UI.child.0.kt.ft => Circuit Presenter and Compose UI.child.0.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit Presenter + Compose UI.child.1.kt.ft => Circuit Presenter and Compose UI.child.1.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit Presenter + Compose UI.child.2.kt.ft => Circuit Presenter and Compose UI.child.2.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit Presenter + Compose UI.child.3.kt.ft => Circuit Presenter and Compose UI.child.3.kt.ft} (100%) rename skate-plugin/src/main/resources/fileTemplates/j2ee/{Circuit Presenter + Compose UI.kt.ft => Circuit Presenter and Compose UI.kt.ft} (100%) create mode 100644 skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/FileTemplateSettingsParserTest.kt create mode 100644 skate-plugin/src/test/resources/test_file_templates.yaml create mode 100644 skate-plugin/src/test/resources/test_malformed_file_templates_setting.yaml diff --git a/skate-plugin/build.gradle.kts b/skate-plugin/build.gradle.kts index 17932a2b9..236c66981 100644 --- a/skate-plugin/build.gradle.kts +++ b/skate-plugin/build.gradle.kts @@ -98,6 +98,11 @@ buildConfig { } } +tasks.withType().configureEach { + kotlinOptions { + freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" + } +} configure { val kotlinVersion = libs.versions.kotlin.get() // Flag to disable Compose's kotlin version check because they're often behind diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt index 1eb4573d1..666f0bbd0 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CreateCircuitFeature.kt @@ -1,65 +1,18 @@ package com.slack.sgp.intellij.filetemplate -import com.charleskorn.kaml.Yaml -import com.charleskorn.kaml.YamlConfiguration -import com.charleskorn.kaml.decodeFromStream -import com.intellij.ide.actions.CreateFileFromTemplateAction import com.intellij.ide.actions.CreateFileFromTemplateDialog -import com.intellij.ide.fileTemplates.FileTemplate -import com.intellij.ide.fileTemplates.FileTemplateManager -import com.intellij.ide.fileTemplates.FileTemplateUtil import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.psi.PsiDirectory -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiManager -import com.slack.sgp.intellij.filetemplate.model.FileTemplateSettings -import com.slack.sgp.intellij.filetemplate.model.TemplateSetting import org.jetbrains.kotlin.idea.KotlinFileType -import java.io.File -import java.io.FileNotFoundException -class CreateCircuitFeature : CreateFileFromTemplateAction( "Circuit Feature", "Creates new Circuit Feature", KotlinFileType.INSTANCE.icon), DumbAware { +class CreateCircuitFeature : CustomCreateFileAction( "New Circuit Feature", "Creates new Circuit Feature", KotlinFileType.INSTANCE.icon), DumbAware { override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) { builder.setTitle("New Circuit Feature Name") - .addKind("Presenter + Compose UI", KotlinFileType.INSTANCE.icon, "Circuit Presenter + Compose UI") + .addKind("Presenter + Compose UI", KotlinFileType.INSTANCE.icon, "Circuit Presenter and Compose UI") .addKind("Presenter only", KotlinFileType.INSTANCE.icon, "Circuit Presenter (without UI)") - .addKind("UdfViewModel conversion", KotlinFileType.INSTANCE.icon, "UdfViewModel convert") } override fun getActionName(directory: PsiDirectory, newName: String, templateName: String) = "Circuit feature" +} - override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? { - val templateSettings = fileTemplateSettingParser() - val allTemplate = FileTemplateManager.getInstance(dir.project).allJ2eeTemplates - - allTemplate.forEach { child -> - if (child.name.contains(template.name)) { - val properties = FileTemplateManager.getInstance(dir.project).defaultProperties - properties.setProperty("NAME", name) - val suffix = templateSettings[child.name]!!.fileNameSuffix - - // Create test directory - if (suffix.contains("Test") && !dir.virtualFile.path.contains("test")) { - val testPath = File(dir.virtualFile.path.replace("src/main", "src/test")) - testPath.mkdirs() - val testFolder = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(testPath) - val psiDir = testFolder?.let { PsiManager.getInstance(dir.project).findDirectory(it) } as PsiDirectory - FileTemplateUtil.createFromTemplate(child, name + suffix, properties, psiDir) - } else { - FileTemplateUtil.createFromTemplate(child, name + suffix, properties, dir) - } - } - } - return null - } - - private fun fileTemplateSettingParser() : Map { - Thread.currentThread().contextClassLoader = this.javaClass.classLoader - val template = Thread.currentThread().contextClassLoader.getResourceAsStream("fileTemplateSettings") - ?: throw FileNotFoundException("File template settings file not found") - val table = Yaml(configuration = YamlConfiguration(strictMode = false)).decodeFromStream(template) - return table.templates.associateBy { it.name } - } -} \ No newline at end of file diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt new file mode 100644 index 000000000..5c2c2a8ee --- /dev/null +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt @@ -0,0 +1,45 @@ +package com.slack.sgp.intellij.filetemplate + +import com.intellij.ide.actions.CreateFileFromTemplateAction +import com.intellij.ide.fileTemplates.FileTemplate +import com.intellij.ide.fileTemplates.FileTemplateManager +import com.intellij.ide.fileTemplates.FileTemplateUtil +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiManager +import com.slack.sgp.intellij.filetemplate.model.FileTemplateFactory +import java.io.File +import java.io.FileNotFoundException +import javax.swing.Icon + +abstract class CustomCreateFileAction(title: String, desc: String, icon: Icon) : CreateFileFromTemplateAction(title, desc, icon) { + + public override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? { + Thread.currentThread().contextClassLoader = this.javaClass.classLoader + val templateStream = Thread.currentThread().contextClassLoader.getResourceAsStream("fileTemplateSettings.yaml") + ?: throw FileNotFoundException("File template settings file not found") + val templateSettings = FileTemplateFactory(templateStream).getTemplates() + val allTemplate = FileTemplateManager.getInstance(dir.project).allJ2eeTemplates + + allTemplate.forEach { child -> + if (child.name.contains(template.name)) { + val properties = FileTemplateManager.getInstance(dir.project).defaultProperties + properties.setProperty("NAME", name) + val suffix = templateSettings[child.name]!!.fileNameSuffix + + // Create test directory + if (suffix.contains("Test") && !dir.virtualFile.path.contains("test")) { + val testPath = File(dir.virtualFile.path.replace("src/main", "src/test")) + testPath.mkdirs() + val testFolder = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(testPath) + val psiDir = testFolder?.let { PsiManager.getInstance(dir.project).findDirectory(it) } as PsiDirectory + FileTemplateUtil.createFromTemplate(child, name + suffix, properties, psiDir) + } else { + FileTemplateUtil.createFromTemplate(child, name + suffix, properties, dir) + } + } + } + return null + } +} \ No newline at end of file diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/UdfViewModelMigrate.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/UdfViewModelMigrate.kt new file mode 100644 index 000000000..866a361d5 --- /dev/null +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/UdfViewModelMigrate.kt @@ -0,0 +1,17 @@ +package com.slack.sgp.intellij.filetemplate + +import com.intellij.ide.actions.CreateFileFromTemplateDialog +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDirectory +import org.jetbrains.kotlin.idea.KotlinFileType + +class UdfViewModelMigrate : CustomCreateFileAction( "UDF ViewModel Convert", "Convert to UDF ViewModel", KotlinFileType.INSTANCE.icon) , + DumbAware { + override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) { + builder.setTitle("UDF ViewModel Convert") + .addKind("UdfViewModel conversion", KotlinFileType.INSTANCE.icon, "UdfViewModel convert") + } + + override fun getActionName(p0: PsiDirectory?, p1: String, p2: String?): String = "UDF ViewModel Convert" +} \ No newline at end of file diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateFactory.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateFactory.kt new file mode 100644 index 000000000..f7b73f818 --- /dev/null +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateFactory.kt @@ -0,0 +1,23 @@ +package com.slack.sgp.intellij.filetemplate.model + +import com.charleskorn.kaml.Yaml +import com.charleskorn.kaml.YamlConfiguration +import com.charleskorn.kaml.decodeFromStream +import java.io.InputStream + + +class FileTemplateFactory(inputStream: InputStream) { + private var templates: Map? = null + + init { + parseFileTemplateFromSettingFile(inputStream) + } + private fun parseFileTemplateFromSettingFile(inputStream: InputStream) { + val table = Yaml(configuration = YamlConfiguration(strictMode = false)).decodeFromStream(inputStream) + templates = table.templates.associateBy { it.name } + } + + fun getTemplates(): Map { + return templates ?: throw IllegalStateException("Templates not loaded properly") + } +} \ No newline at end of file diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/ProjectGenPresenter.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/ProjectGenPresenter.kt index 89d908c95..74d4dbaa4 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/ProjectGenPresenter.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/ProjectGenPresenter.kt @@ -15,12 +15,14 @@ */ package com.slack.sgp.intellij.projectgen +import androidx.compose.material.DropdownMenu import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import com.intellij.openapi.diagnostic.logger import com.slack.circuit.runtime.presenter.Presenter import java.io.File import org.apache.commons.io.FileExistsException @@ -105,6 +107,26 @@ internal class ProjectGenPresenter( ) private val circuit = CheckboxElement(false, name = "Circuit", hint = "Enables Circuit") + private val circuitFeatureName = + TextElement( + "", + "Circuit Feature Name (optional)", + description = + "", + indentLevel = 3, + initialVisibility = false, + dependentElements = listOf(circuit), + ) + + private val circuitFeatureTemplate = + DropdownElement( + initialVisibility = false, + label = "Using templates:", + indentLevel = 3, + selectedText = "Circuit Presenter and Compose UI", + dropdownList = listOf("Circuit Presenter and Compose UI", "Circuit Presenter (without UI)") + ) + private val compose = CheckboxElement(false, name = "Compose", hint = "Enables Jetpack Compose.") @@ -126,6 +148,8 @@ internal class ProjectGenPresenter( daggerRuntimeOnly, compose, circuit, + circuitFeatureName, + circuitFeatureTemplate ) @Composable @@ -137,6 +161,8 @@ internal class ProjectGenPresenter( robolectric.isVisible = android.isChecked androidTest.isVisible = android.isChecked daggerRuntimeOnly.isVisible = dagger.isChecked + circuitFeatureName.isVisible = circuit.isChecked + circuitFeatureTemplate.isVisible = circuit.isChecked && circuitFeatureName.value.isNotBlank() var showDoneDialog by remember { mutableStateOf(false) } var showErrorDialog by remember { mutableStateOf(false) } @@ -180,6 +206,8 @@ internal class ProjectGenPresenter( robolectric.reset() compose.reset() circuit.reset() + circuitFeatureTemplate.reset() + circuitFeatureName.reset() } private fun generate() { @@ -206,6 +234,8 @@ internal class ProjectGenPresenter( compose = compose.isChecked, androidTest = androidTest.isChecked, circuit = circuit.isChecked, + circuitFeatureName = circuitFeatureName.value, + circuitFeatureTemplate = circuitFeatureTemplate.selectedText ) } @@ -223,6 +253,8 @@ internal class ProjectGenPresenter( compose: Boolean, androidTest: Boolean, circuit: Boolean, + circuitFeatureName: String, + circuitFeatureTemplate: String, ) { val features = mutableListOf() val androidLibraryEnabled = @@ -252,7 +284,10 @@ internal class ProjectGenPresenter( } if (circuit) { - features += CircuitFeature + features += CircuitFeature(packageName, circuitFeatureName, circuitFeatureTemplate) + logger().info("HERERE") + logger().info(circuitFeatureName) + logger().info(circuitFeatureTemplate) } val buildFile = BuildFile(emptyList()) diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/ProjectGenUi.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/ProjectGenUi.kt index 1b62b486b..9e9f22c0f 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/ProjectGenUi.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/ProjectGenUi.kt @@ -33,20 +33,18 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.foundation.verticalScroll import androidx.compose.material.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.Checkbox -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.LocalContentColor -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.material.DropdownMenu +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.intellij.openapi.diagnostic.logger +import slack.tooling.projectgen.* import slack.tooling.projectgen.CheckboxElement import slack.tooling.projectgen.DividerElement import slack.tooling.projectgen.SectionElement @@ -55,6 +53,7 @@ import slack.tooling.projectgen.TextElement private const val INDENT_SIZE = 16 // dp // @OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable internal fun ProjectGen(state: ProjectGenScreen.State, modifier: Modifier = Modifier) { if (state.showDoneDialog) { @@ -122,6 +121,39 @@ internal fun ProjectGen(state: ProjectGenScreen.State, modifier: Modifier = Modi element.description?.let { Text(it, style = MaterialTheme.typography.bodySmall) } } } + is DropdownElement -> { + var expanded by remember { mutableStateOf(false) } + Box(Modifier.padding(start = (element.indentLevel * INDENT_SIZE).dp)) { + IconButton(onClick = { expanded = false }) { + Icon( + imageVector = Icons.Default.MoreVert, + contentDescription = "More" + ) + } + + ExposedDropdownMenuBox( + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + ) { + + logger().info("LINHHHH") + TextField( + value = element.selectedText, + label = { Text(element.label) }, + onValueChange = {}, + readOnly = true, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + modifier = Modifier.menuAnchor() + ) + + ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { + element.dropdownList.forEach { item -> + DropdownMenuItem(text = {Text(item)}, onClick = { expanded = false; element.selectedText = item }) + } + } + } + } + } } } Button( diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/UiElement.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/UiElement.kt index 6f9edb919..60c9d82fd 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/UiElement.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/UiElement.kt @@ -21,6 +21,8 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import com.intellij.openapi.diagnostic.logger +import com.slack.sgp.intellij.projectgen.ProjectGenPresenter @Stable internal sealed interface UiElement { @@ -55,6 +57,20 @@ internal class CheckboxElement( override var isVisible: Boolean by mutableStateOf(isVisible) } +internal class DropdownElement( + val initialVisibility: Boolean, + val label: String, + val indentLevel: Int = 0, + val dropdownList: List, + var selectedText: String, +) : UiElement { + override var isVisible: Boolean by mutableStateOf(initialVisibility) + override fun reset() { + isVisible = initialVisibility + } + +} + @Suppress("LongParameterList") internal class TextElement( private val initialValue: String, diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/models.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/models.kt index 0a66a5c83..2c4ba7a9c 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/models.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/models.kt @@ -15,6 +15,15 @@ */ package slack.tooling.projectgen +import com.intellij.ide.fileTemplates.FileTemplateManager +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiManager +import com.slack.sgp.intellij.filetemplate.CreateCircuitFeature +import com.slack.sgp.intellij.filetemplate.model.FileTemplateFactory +import com.slack.sgp.intellij.projectgen.ProjectGenPresenter import com.squareup.kotlinpoet.FileSpec import java.io.File @@ -289,10 +298,25 @@ internal object ComposeFeature : Feature, SlackFeatureVisitor { } } -internal object CircuitFeature : Feature, SlackFeatureVisitor { +internal class CircuitFeature(val packageName: String, val circuitFeatureName: String, val templateName: String) : Feature, + SlackFeatureVisitor { override fun writeToSlackFeatures(builder: FileSpec.Builder) { builder.addStatement("circuit()") } + + override fun renderFiles(projectDir: File) { + if (circuitFeatureName.isBlank()) return + val project = ProjectManager.getInstance().openProjects.first() + val allTemplate = FileTemplateManager.getInstance(project).allJ2eeTemplates + val template = allTemplate.find { it.name == templateName } + val mainSrcDir = + projectDir.resolve("src/main/kotlin/${packageName.replace(".", "/")}").apply { mkdirs() } + val testFolder = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(mainSrcDir) + val psiDir = testFolder?.let { PsiManager.getInstance(project).findDirectory(it) } as PsiDirectory + if (template != null) { + CreateCircuitFeature().createFileFromTemplate(circuitFeatureName, template, psiDir) + } + } } internal class ReadMeFile { diff --git a/skate-plugin/src/main/resources/META-INF/plugin.xml b/skate-plugin/src/main/resources/META-INF/plugin.xml index 6071171e0..73de73eb4 100644 --- a/skate-plugin/src/main/resources/META-INF/plugin.xml +++ b/skate-plugin/src/main/resources/META-INF/plugin.xml @@ -60,6 +60,9 @@ + + + diff --git a/skate-plugin/src/main/resources/fileTemplateSettings.yaml b/skate-plugin/src/main/resources/fileTemplateSettings.yaml index b93806787..bb588c88a 100644 --- a/skate-plugin/src/main/resources/fileTemplateSettings.yaml +++ b/skate-plugin/src/main/resources/fileTemplateSettings.yaml @@ -1,17 +1,17 @@ templates: - - name: Circuit Presenter + Compose UI + - name: Circuit Presenter and Compose UI file_name_suffix: Screen - - name: Circuit Presenter + Compose UI.child.0 + - name: Circuit Presenter and Compose UI.child.0 file_name_suffix: PresenterTest - - name: Circuit Presenter + Compose UI.child.1 + - name: Circuit Presenter and Compose UI.child.1 file_name_suffix: UiTest - - name: Circuit Presenter + Compose UI.child.2 + - name: Circuit Presenter and Compose UI.child.2 file_name_suffix: Presenter - - name: Circuit Presenter + Compose UI.child.3 + - name: Circuit Presenter and Compose UI.child.3 file_name_suffix: Ui - name: Circuit Presenter (without UI) diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.0.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.child.0.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.0.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.child.0.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.1.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.child.1.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.1.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.child.1.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.2.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.child.2.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.2.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.child.2.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.3.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.child.3.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.child.3.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.child.3.kt.ft diff --git a/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.kt.ft b/skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.kt.ft similarity index 100% rename from skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter + Compose UI.kt.ft rename to skate-plugin/src/main/resources/fileTemplates/j2ee/Circuit Presenter and Compose UI.kt.ft diff --git a/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/FileTemplateSettingsParserTest.kt b/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/FileTemplateSettingsParserTest.kt new file mode 100644 index 000000000..17dc8a64b --- /dev/null +++ b/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/FileTemplateSettingsParserTest.kt @@ -0,0 +1,27 @@ +package com.slack.sgp.intellij.filetemplates + +import com.charleskorn.kaml.MissingRequiredPropertyException +import com.google.common.truth.Truth.assertThat +import com.intellij.testFramework.UsefulTestCase.assertThrows +import com.slack.sgp.intellij.filetemplate.model.FileTemplateFactory +import org.junit.Test + +class FileTemplateSettingsParserTest { + @Test + fun testSuccessfulParse() { + val templateStream = this.javaClass.classLoader.getResourceAsStream("test_file_templates.yaml") + val templates = templateStream?.let { FileTemplateFactory(it).getTemplates() } + assertThat(templates).isNotNull() + assertThat(templates?.size).isEqualTo(2) + assertThat(templates?.get("Template1")?.fileNameSuffix).isEqualTo("Main") + assertThat(templates?.get("Template2")?.fileNameSuffix).isEqualTo("Test") + } + + @Test + fun testMissingProperties() { + assertThrows(MissingRequiredPropertyException::class.java) { + val templateStream = this.javaClass.classLoader.getResourceAsStream("test_malformed_file_templates_setting.yaml") + templateStream?.let { FileTemplateFactory(it).getTemplates() } + } + } +} \ No newline at end of file diff --git a/skate-plugin/src/test/resources/test_file_templates.yaml b/skate-plugin/src/test/resources/test_file_templates.yaml new file mode 100644 index 000000000..54275ed97 --- /dev/null +++ b/skate-plugin/src/test/resources/test_file_templates.yaml @@ -0,0 +1,7 @@ +templates: + - name: Template1 + file_name_suffix: Main + extra: True + + - name: Template2 + file_name_suffix: Test \ No newline at end of file diff --git a/skate-plugin/src/test/resources/test_malformed_file_templates_setting.yaml b/skate-plugin/src/test/resources/test_malformed_file_templates_setting.yaml new file mode 100644 index 000000000..85f892a4e --- /dev/null +++ b/skate-plugin/src/test/resources/test_malformed_file_templates_setting.yaml @@ -0,0 +1,5 @@ +templates: + - name: Template1 + + - name: Template2 + file_name_suffix: Test \ No newline at end of file From b53e130df02a3a6a8be9eea9f4e2a2676f7ea62c Mon Sep 17 00:00:00 2001 From: Linh Pham Date: Fri, 29 Mar 2024 14:40:28 -0700 Subject: [PATCH 5/6] add unit tests --- .../filetemplate/CustomCreateFileAction.kt | 70 ++++++++++++------- ...leTemplateFactory.kt => SettingsParser.kt} | 2 +- .../slack/sgp/intellij/projectgen/models.kt | 4 +- .../CustomCreateFileFromTemplateTest.kt | 66 +++++++++++++++++ .../FileTemplateSettingsParserTest.kt | 6 +- 5 files changed, 117 insertions(+), 31 deletions(-) rename skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/{FileTemplateFactory.kt => SettingsParser.kt} (93%) create mode 100644 skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/CustomCreateFileFromTemplateTest.kt diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt index 5c2c2a8ee..c7e0577d3 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt @@ -1,45 +1,67 @@ package com.slack.sgp.intellij.filetemplate +import com.intellij.codeInsight.template.impl.TemplateSettings import com.intellij.ide.actions.CreateFileFromTemplateAction import com.intellij.ide.fileTemplates.FileTemplate import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.ide.fileTemplates.FileTemplateUtil +import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager -import com.slack.sgp.intellij.filetemplate.model.FileTemplateFactory +import com.slack.sgp.intellij.filetemplate.model.SettingsParser +import com.slack.sgp.intellij.filetemplate.model.TemplateSetting +import org.jetbrains.kotlin.idea.gradleTooling.get import java.io.File import java.io.FileNotFoundException +import java.util.* import javax.swing.Icon abstract class CustomCreateFileAction(title: String, desc: String, icon: Icon) : CreateFileFromTemplateAction(title, desc, icon) { public override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? { - Thread.currentThread().contextClassLoader = this.javaClass.classLoader - val templateStream = Thread.currentThread().contextClassLoader.getResourceAsStream("fileTemplateSettings.yaml") + val templateSettings = loadTemplateSettings() + val properties = FileTemplateManager.getInstance(dir.project).defaultProperties.apply { + setProperty("NAME", name) + } + val templates = listOf(template) + getTemplateChildren(dir.project, template) + + return templates.firstNotNullOfOrNull { fileTemplate -> + createFileForTemplate(fileTemplate, name, dir, properties, templateSettings) + } + } + + private fun createFileForTemplate( + template: FileTemplate, + name: String, + dir: PsiDirectory, + properties: Properties, + templateSettings: Map + ): PsiFile? { + val suffix = templateSettings[template.name]?.fileNameSuffix ?: "" + val targetDir = getTargetDirectory(dir, suffix) + return FileTemplateUtil.createFromTemplate(template, name + suffix, properties, targetDir).containingFile + } + + private fun getTemplateChildren(project: Project, template: FileTemplate): List { + val allTemplate = FileTemplateManager.getInstance(project).allJ2eeTemplates + return allTemplate.filter { it.name.contains("child") and it.name.contains(template.name) } + } + + private fun loadTemplateSettings(): Map { + val stream = this.javaClass.classLoader.getResourceAsStream("fileTemplateSettings.yaml") ?: throw FileNotFoundException("File template settings file not found") - val templateSettings = FileTemplateFactory(templateStream).getTemplates() - val allTemplate = FileTemplateManager.getInstance(dir.project).allJ2eeTemplates - - allTemplate.forEach { child -> - if (child.name.contains(template.name)) { - val properties = FileTemplateManager.getInstance(dir.project).defaultProperties - properties.setProperty("NAME", name) - val suffix = templateSettings[child.name]!!.fileNameSuffix - - // Create test directory - if (suffix.contains("Test") && !dir.virtualFile.path.contains("test")) { - val testPath = File(dir.virtualFile.path.replace("src/main", "src/test")) - testPath.mkdirs() - val testFolder = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(testPath) - val psiDir = testFolder?.let { PsiManager.getInstance(dir.project).findDirectory(it) } as PsiDirectory - FileTemplateUtil.createFromTemplate(child, name + suffix, properties, psiDir) - } else { - FileTemplateUtil.createFromTemplate(child, name + suffix, properties, dir) - } - } + return SettingsParser(stream).getTemplates() + } + + private fun getTargetDirectory(dir: PsiDirectory, suffix: String): PsiDirectory { + if (suffix.contains("Test") && !dir.virtualFile.path.contains("test")) { + val testPath = File(dir.virtualFile.path.replace("src/main", "src/test")) + testPath.mkdirs() + val testFolder = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(testPath) + return testFolder?.let { PsiManager.getInstance(dir.project).findDirectory(it) } as PsiDirectory } - return null + return dir } } \ No newline at end of file diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateFactory.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/SettingsParser.kt similarity index 93% rename from skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateFactory.kt rename to skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/SettingsParser.kt index f7b73f818..7a234caee 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/FileTemplateFactory.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/model/SettingsParser.kt @@ -6,7 +6,7 @@ import com.charleskorn.kaml.decodeFromStream import java.io.InputStream -class FileTemplateFactory(inputStream: InputStream) { +class SettingsParser(inputStream: InputStream) { private var templates: Map? = null init { diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/models.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/models.kt index 2c4ba7a9c..b9df46316 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/models.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/projectgen/models.kt @@ -16,14 +16,11 @@ package slack.tooling.projectgen import com.intellij.ide.fileTemplates.FileTemplateManager -import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiManager import com.slack.sgp.intellij.filetemplate.CreateCircuitFeature -import com.slack.sgp.intellij.filetemplate.model.FileTemplateFactory -import com.slack.sgp.intellij.projectgen.ProjectGenPresenter import com.squareup.kotlinpoet.FileSpec import java.io.File @@ -314,6 +311,7 @@ internal class CircuitFeature(val packageName: String, val circuitFeatureName: S val testFolder = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(mainSrcDir) val psiDir = testFolder?.let { PsiManager.getInstance(project).findDirectory(it) } as PsiDirectory if (template != null) { + FileTemplateManager.getInstance(project).defaultProperties.setProperty("PACKAGE_NAME", packageName) CreateCircuitFeature().createFileFromTemplate(circuitFeatureName, template, psiDir) } } diff --git a/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/CustomCreateFileFromTemplateTest.kt b/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/CustomCreateFileFromTemplateTest.kt new file mode 100644 index 000000000..a13bdebe7 --- /dev/null +++ b/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/CustomCreateFileFromTemplateTest.kt @@ -0,0 +1,66 @@ +package com.slack.sgp.intellij.filetemplates + +import com.intellij.ide.fileTemplates.impl.CustomFileTemplate +import com.intellij.testFramework.fixtures.BasePlatformTestCase +import com.slack.sgp.intellij.filetemplate.CreateCircuitFeature + +class CustomCreateFileFromTemplateTest : BasePlatformTestCase() { + private val testTemplate = CustomFileTemplate("test template", "kt").apply { + text = """ + #if (${'$'}{PACKAGE_NAME} && ${'$'}{PACKAGE_NAME} != "")package ${'$'}{PACKAGE_NAME} + + #end + @Parcelize + class ${'$'}{NAME}Screen : Screen { + } + """.trimIndent() + } + + private val circuitTemplate = CustomFileTemplate("Circuit Presenter (without UI)", "kt").apply { + text = """ + #if (${'$'}{PACKAGE_NAME} && ${'$'}{PACKAGE_NAME} != "")package ${'$'}{PACKAGE_NAME} + + #end + @Parcelize + class ${'$'}{NAME}Screen : Screen { + } + """.trimIndent() + } + + fun testClassCreatedFromTemplate() = doTest( + template = testTemplate, + userInput = "MyClass", + expectedFileName = "MyClass.kt", + expectedClassName = "MyClass" + ) + + fun testClassNameCreatedWithSuffix() = doTest( + template = circuitTemplate, + userInput = "MyClass", + expectedFileName = "MyClassScreen.kt", + expectedClassName = "MyClass" + ) + private fun doTest( + template: CustomFileTemplate, + userInput: String, + existentPath: String? = null, + expectedFileName: String, + expectedClassName: String + ) { + if (existentPath != null) { + myFixture.tempDirFixture.findOrCreateDir(existentPath) + } + + val actDir = myFixture.psiManager.findDirectory(myFixture.tempDirFixture.findOrCreateDir("."))!! + val file = CreateCircuitFeature().createFileFromTemplate(userInput, template, actDir)!! + + assertEquals(expectedFileName, file.name) + + val expectedContent = """ + @Parcelize + class ${expectedClassName}Screen : Screen { + } + """.trimIndent() + assertEquals(expectedContent, file.text) + } +} \ No newline at end of file diff --git a/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/FileTemplateSettingsParserTest.kt b/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/FileTemplateSettingsParserTest.kt index 17dc8a64b..d3ab318a4 100644 --- a/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/FileTemplateSettingsParserTest.kt +++ b/skate-plugin/src/test/kotlin/com/slack/sgp/intellij/filetemplates/FileTemplateSettingsParserTest.kt @@ -3,14 +3,14 @@ package com.slack.sgp.intellij.filetemplates import com.charleskorn.kaml.MissingRequiredPropertyException import com.google.common.truth.Truth.assertThat import com.intellij.testFramework.UsefulTestCase.assertThrows -import com.slack.sgp.intellij.filetemplate.model.FileTemplateFactory +import com.slack.sgp.intellij.filetemplate.model.SettingsParser import org.junit.Test class FileTemplateSettingsParserTest { @Test fun testSuccessfulParse() { val templateStream = this.javaClass.classLoader.getResourceAsStream("test_file_templates.yaml") - val templates = templateStream?.let { FileTemplateFactory(it).getTemplates() } + val templates = templateStream?.let { SettingsParser(it).getTemplates() } assertThat(templates).isNotNull() assertThat(templates?.size).isEqualTo(2) assertThat(templates?.get("Template1")?.fileNameSuffix).isEqualTo("Main") @@ -21,7 +21,7 @@ class FileTemplateSettingsParserTest { fun testMissingProperties() { assertThrows(MissingRequiredPropertyException::class.java) { val templateStream = this.javaClass.classLoader.getResourceAsStream("test_malformed_file_templates_setting.yaml") - templateStream?.let { FileTemplateFactory(it).getTemplates() } + templateStream?.let { SettingsParser(it).getTemplates() } } } } \ No newline at end of file From 3979432490593ca716e743ae35126629087cb1b5 Mon Sep 17 00:00:00 2001 From: Linh Pham Date: Fri, 29 Mar 2024 16:10:54 -0700 Subject: [PATCH 6/6] move postition in new menu bar --- .../intellij/filetemplate/CustomCreateFileAction.kt | 13 ++++++++++--- skate-plugin/src/main/resources/META-INF/plugin.xml | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt index c7e0577d3..a749cdd78 100644 --- a/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt +++ b/skate-plugin/src/main/kotlin/com/slack/sgp/intellij/filetemplate/CustomCreateFileAction.kt @@ -5,6 +5,7 @@ import com.intellij.ide.actions.CreateFileFromTemplateAction import com.intellij.ide.fileTemplates.FileTemplate import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.ide.fileTemplates.FileTemplateUtil +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.psi.PsiDirectory @@ -22,14 +23,18 @@ abstract class CustomCreateFileAction(title: String, desc: String, icon: Icon) : public override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? { val templateSettings = loadTemplateSettings() + logger().info("LINHH") + logger().info(templateSettings.toString()) val properties = FileTemplateManager.getInstance(dir.project).defaultProperties.apply { setProperty("NAME", name) } val templates = listOf(template) + getTemplateChildren(dir.project, template) - return templates.firstNotNullOfOrNull { fileTemplate -> - createFileForTemplate(fileTemplate, name, dir, properties, templateSettings) + val createdFiles = mutableListOf() + templates.forEach { fileTemplate -> + createdFiles.add(createFileForTemplate(fileTemplate, name, dir, properties, templateSettings)) } + return createdFiles.firstOrNull() } private fun createFileForTemplate( @@ -46,7 +51,9 @@ abstract class CustomCreateFileAction(title: String, desc: String, icon: Icon) : private fun getTemplateChildren(project: Project, template: FileTemplate): List { val allTemplate = FileTemplateManager.getInstance(project).allJ2eeTemplates - return allTemplate.filter { it.name.contains("child") and it.name.contains(template.name) } + val filtered = allTemplate.filter { it.name.contains("child") && it.name.contains(template.name) } + return filtered + } private fun loadTemplateSettings(): Map { diff --git a/skate-plugin/src/main/resources/META-INF/plugin.xml b/skate-plugin/src/main/resources/META-INF/plugin.xml index 73de73eb4..62c6da6db 100644 --- a/skate-plugin/src/main/resources/META-INF/plugin.xml +++ b/skate-plugin/src/main/resources/META-INF/plugin.xml @@ -55,7 +55,7 @@ class="com.slack.sgp.intellij.projectgen.ProjectGenMenuAction" text="Slack Subproject" description="Create new Slack subproject"> - +