Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add circuit feature generation from template to Skate #797

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions skate-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ buildConfig {
}
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}
}
configure<ComposeExtension> {
val kotlinVersion = libs.versions.kotlin.get()
// Flag to disable Compose's kotlin version check because they're often behind
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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 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 and Compose UI")
.addKind("Presenter only", KotlinFileType.INSTANCE.icon, "Circuit Presenter (without UI)")
}

override fun getActionName(directory: PsiDirectory, newName: String, templateName: String) = "Circuit feature"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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.diagnostic.logger
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.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? {
val templateSettings = loadTemplateSettings()
logger<CustomCreateFileAction>().info("LINHH")
logger<CustomCreateFileAction>().info(templateSettings.toString())
val properties = FileTemplateManager.getInstance(dir.project).defaultProperties.apply {
setProperty("NAME", name)
}
val templates = listOf(template) + getTemplateChildren(dir.project, template)

val createdFiles = mutableListOf<PsiFile?>()
templates.forEach { fileTemplate ->
createdFiles.add(createFileForTemplate(fileTemplate, name, dir, properties, templateSettings))
}
return createdFiles.firstOrNull()
}

private fun createFileForTemplate(
template: FileTemplate,
name: String,
dir: PsiDirectory,
properties: Properties,
templateSettings: Map<String, TemplateSetting>
): 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<FileTemplate> {
val allTemplate = FileTemplateManager.getInstance(project).allJ2eeTemplates
val filtered = allTemplate.filter { it.name.contains("child") && it.name.contains(template.name) }
return filtered

}

private fun loadTemplateSettings(): Map<String, TemplateSetting> {
val stream = this.javaClass.classLoader.getResourceAsStream("fileTemplateSettings.yaml")
?: throw FileNotFoundException("File template settings file not found")
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 dir
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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<TemplateSetting>,
)
Original file line number Diff line number Diff line change
@@ -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 SettingsParser(inputStream: InputStream) {
private var templates: Map<String, TemplateSetting>? = null

init {
parseFileTemplateFromSettingFile(inputStream)
}
private fun parseFileTemplateFromSettingFile(inputStream: InputStream) {
val table = Yaml(configuration = YamlConfiguration(strictMode = false)).decodeFromStream<FileTemplateSettings>(inputStream)
templates = table.templates.associateBy { it.name }
}

fun getTemplates(): Map<String, TemplateSetting> {
return templates ?: throw IllegalStateException("Templates not loaded properly")
}
}
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.")

Expand All @@ -126,6 +148,8 @@ internal class ProjectGenPresenter(
daggerRuntimeOnly,
compose,
circuit,
circuitFeatureName,
circuitFeatureTemplate
)

@Composable
Expand All @@ -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) }
Expand Down Expand Up @@ -180,6 +206,8 @@ internal class ProjectGenPresenter(
robolectric.reset()
compose.reset()
circuit.reset()
circuitFeatureTemplate.reset()
circuitFeatureName.reset()
}

private fun generate() {
Expand All @@ -206,6 +234,8 @@ internal class ProjectGenPresenter(
compose = compose.isChecked,
androidTest = androidTest.isChecked,
circuit = circuit.isChecked,
circuitFeatureName = circuitFeatureName.value,
circuitFeatureTemplate = circuitFeatureTemplate.selectedText
)
}

Expand All @@ -223,6 +253,8 @@ internal class ProjectGenPresenter(
compose: Boolean,
androidTest: Boolean,
circuit: Boolean,
circuitFeatureName: String,
circuitFeatureTemplate: String,
) {
val features = mutableListOf<Feature>()
val androidLibraryEnabled =
Expand Down Expand Up @@ -252,7 +284,10 @@ internal class ProjectGenPresenter(
}

if (circuit) {
features += CircuitFeature
features += CircuitFeature(packageName, circuitFeatureName, circuitFeatureTemplate)
logger<ProjectGenPresenter>().info("HERERE")
logger<ProjectGenPresenter>().info(circuitFeatureName)
logger<ProjectGenPresenter>().info(circuitFeatureTemplate)
}

val buildFile = BuildFile(emptyList())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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<ProjectGenPresenter>().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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<String>,
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,
Expand Down
Loading