From c67871b75eba8759922a6be5259ad5f37ffc9ae0 Mon Sep 17 00:00:00 2001 From: WarningImHack3r <43064022+WarningImHack3r@users.noreply.github.com> Date: Wed, 3 Jan 2024 18:02:25 +0100 Subject: [PATCH] Create plugin --- .gitignore | 1 + CHANGELOG.md | 11 +- README.md | 38 +-- build.gradle.kts | 5 +- gradle.properties | 8 +- gradle/libs.versions.toml | 8 +- qodana.yml | 2 +- .../intellijshadcnplugin/MyBundle.kt | 20 -- .../backend/ISPScanner.kt | 20 ++ .../backend/helpers/FileManager.kt | 65 +++++ .../backend/helpers/ShellRunner.kt | 33 +++ .../backend/http/RequestSender.kt | 37 +++ .../backend/sources/ISPComponent.kt | 6 + .../backend/sources/ISPSource.kt | 19 ++ .../backend/sources/ISPStyle.kt | 6 + .../backend/sources/ISPSvelteSource.kt | 227 ++++++++++++++++++ .../listeners/ISPToolWindowListener.kt | 25 ++ .../MyApplicationActivationListener.kt | 12 - .../services/MyProjectService.kt | 17 -- .../toolWindow/MyToolWindowFactory.kt | 45 ---- .../intellijshadcnplugin/ui/ISPToolWindow.kt | 43 ++++ .../ui/ISPWindowContents.kt | 221 +++++++++++++++++ src/main/kotlin/icons/ISPIcons.kt | 8 + src/main/resources/META-INF/plugin.xml | 20 +- src/main/resources/META-INF/pluginIcon.svg | 5 + src/main/resources/icons/shadcn.svg | 5 + .../resources/messages/MyBundle.properties | 3 - .../intellijshadcnplugin/DummyTests.kt | 9 + .../intellijshadcnplugin/MyPluginTest.kt | 39 --- src/test/testData/rename/foo.xml | 3 - src/test/testData/rename/foo_after.xml | 3 - 31 files changed, 784 insertions(+), 180 deletions(-) delete mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/MyBundle.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/ISPScanner.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/FileManager.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/ShellRunner.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/http/RequestSender.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPComponent.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPSource.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPStyle.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPSvelteSource.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/listeners/ISPToolWindowListener.kt delete mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/listeners/MyApplicationActivationListener.kt delete mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/services/MyProjectService.kt delete mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/toolWindow/MyToolWindowFactory.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/ui/ISPToolWindow.kt create mode 100644 src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/ui/ISPWindowContents.kt create mode 100644 src/main/kotlin/icons/ISPIcons.kt create mode 100644 src/main/resources/META-INF/pluginIcon.svg create mode 100644 src/main/resources/icons/shadcn.svg delete mode 100644 src/main/resources/messages/MyBundle.properties create mode 100644 src/test/kotlin/com/github/warningimhack3r/intellijshadcnplugin/DummyTests.kt delete mode 100644 src/test/kotlin/com/github/warningimhack3r/intellijshadcnplugin/MyPluginTest.kt delete mode 100644 src/test/testData/rename/foo.xml delete mode 100644 src/test/testData/rename/foo_after.xml diff --git a/.gitignore b/.gitignore index e2e5d94..2289e50 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea .qodana build +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index ddbd8f9..2315663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,16 @@ <!-- Keep a Changelog guide -> https://keepachangelog.com --> +<!-- Types of changes memo: +— “Added” for new features. +— “Changed” for changes in existing functionality. +— “Deprecated” for soon-to-be removed features. +— “Removed” for now removed features. +— “Fixed” for any bug fixes. +— “Security” in case of vulnerabilities. +--> # intellij-shadcn-plugin Changelog ## [Unreleased] + ### Added -- Initial scaffold created from [IntelliJ Platform Plugin Template](https://github.com/JetBrains/intellij-platform-plugin-template) +- Initial release diff --git a/README.md b/README.md index 9ed4a40..321ee4f 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,36 @@ # intellij-shadcn-plugin ![Build](https://github.com/WarningImHack3r/intellij-shadcn-plugin/workflows/Build/badge.svg) -[![Version](https://img.shields.io/jetbrains/plugin/v/PLUGIN_ID.svg)](https://plugins.jetbrains.com/plugin/PLUGIN_ID) -[![Downloads](https://img.shields.io/jetbrains/plugin/d/PLUGIN_ID.svg)](https://plugins.jetbrains.com/plugin/PLUGIN_ID) +[![Version](https://img.shields.io/jetbrains/plugin/v/com.github.warningimhack3r.intellijshadcnplugin.svg)](https://plugins.jetbrains.com/plugin/com.github.warningimhack3r.intellijshadcnplugin) +[![Downloads](https://img.shields.io/jetbrains/plugin/d/com.github.warningimhack3r.intellijshadcnplugin.svg)](https://plugins.jetbrains.com/plugin/com.github.warningimhack3r.intellijshadcnplugin) ## Template ToDo list -- [x] Create a new [IntelliJ Platform Plugin Template][template] project. -- [ ] Get familiar with the [template documentation][template]. -- [ ] Adjust the [pluginGroup](./gradle.properties), [plugin ID](./src/main/resources/META-INF/plugin.xml) and [sources package](./src/main/kotlin). -- [ ] Adjust the plugin description in `README` (see [Tips][docs:plugin-description]) -- [ ] Review the [Legal Agreements](https://plugins.jetbrains.com/docs/marketplace/legal-agreements.html?from=IJPluginTemplate). - [ ] [Publish a plugin manually](https://plugins.jetbrains.com/docs/intellij/publishing-plugin.html?from=IJPluginTemplate) for the first time. -- [ ] Set the `PLUGIN_ID` in the above README badges. - [ ] Set the [Plugin Signing](https://plugins.jetbrains.com/docs/intellij/plugin-signing.html?from=IJPluginTemplate) related [secrets](https://github.com/JetBrains/intellij-platform-plugin-template#environment-variables). - [ ] Set the [Deployment Token](https://plugins.jetbrains.com/docs/marketplace/plugin-upload.html?from=IJPluginTemplate). -- [ ] Click the <kbd>Watch</kbd> button on the top of the [IntelliJ Platform Plugin Template][template] to be notified about releases containing new features and fixes. +## Description <!-- Plugin description --> -This Fancy IntelliJ Platform Plugin is going to be your implementation of the brilliant ideas that you have. - -This specific section is a source for the [plugin.xml](/src/main/resources/META-INF/plugin.xml) file which will be extracted by the [Gradle](/build.gradle.kts) during the build process. - -To keep everything working, do not remove `<!-- ... -->` sections. +Manage your shadcn/ui components in your project. + +This plugin will help you manage your shadcn/ui components through a simple tool window. Add, remove, update them with a single click. + +## Features +- Automatically detect shadcn/ui components in your project +- Instantly add, remove, update them with a single click +- Refreshes on opening the tool window +- Supports _all_ shadcn/ui implementations: Svelte, React, Vue, Solid, and even Kotlin/JS +- Browse available components +- Easily search for remote or existing components +- (Soon) support monorepos +- ...and more! + +## Usage +Simply open the `shadcn/ui` tool window and start managing your components. +If you don't see the tool window, you can open it from `View > Tool Windows > shadcn/ui`. + +## Planned Features +- Add support for monorepos <!-- Plugin description end --> ## Installation @@ -41,4 +50,3 @@ To keep everything working, do not remove `<!-- ... -->` sections. Plugin based on the [IntelliJ Platform Plugin Template][template]. [template]: https://github.com/JetBrains/intellij-platform-plugin-template -[docs:plugin-description]: https://plugins.jetbrains.com/docs/intellij/plugin-user-experience.html#plugin-description-and-presentation diff --git a/build.gradle.kts b/build.gradle.kts index bf0c939..95bc807 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ fun environment(key: String) = providers.environmentVariable(key) plugins { id("java") // Java support alias(libs.plugins.kotlin) // Kotlin support + kotlin(libs.plugins.serialization.get().pluginId) version libs.versions.kotlin // Kotlin Serialization support alias(libs.plugins.gradleIntelliJPlugin) // Gradle IntelliJ Plugin alias(libs.plugins.changelog) // Gradle Changelog Plugin alias(libs.plugins.qodana) // Gradle Qodana Plugin @@ -19,18 +20,18 @@ version = properties("pluginVersion").get() // Configure project's dependencies repositories { mavenCentral() + gradlePluginPortal() } // Dependencies are managed with Gradle version catalog - read more: https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog dependencies { -// implementation(libs.annotations) } // Set the JVM language level used to build the project. Use Java 11 for 2020.3+, and Java 17 for 2022.2+. kotlin { @Suppress("UnstableApiUsage") jvmToolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(11) vendor = JvmVendorSpec.JETBRAINS } } diff --git a/gradle.properties b/gradle.properties index dea38c3..0854f89 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,15 +4,15 @@ pluginGroup = com.github.warningimhack3r.intellijshadcnplugin pluginName = intellij-shadcn-plugin pluginRepositoryUrl = https://github.com/WarningImHack3r/intellij-shadcn-plugin # SemVer format -> https://semver.org -pluginVersion = 0.0.1 +pluginVersion = 1.0.0 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html -pluginSinceBuild = 223 -pluginUntilBuild = 233.* +pluginSinceBuild = 212 +pluginUntilBuild = # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension platformType = IC -platformVersion = 2022.3.3 +platformVersion = 2021.3 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f6b4ec3..3cd7cad 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,20 +1,14 @@ [versions] -# libraries -annotations = "24.1.0" - -# plugins kotlin = "1.9.22" changelog = "2.2.0" gradleIntelliJPlugin = "1.16.1" qodana = "0.1.13" kover = "0.7.5" -[libraries] -annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" } - [plugins] changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } gradleIntelliJPlugin = { id = "org.jetbrains.intellij", version.ref = "gradleIntelliJPlugin" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +serialization = { id = "plugin.serialization", version.ref = "kotlin" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } diff --git a/qodana.yml b/qodana.yml index cbf640f..200f48c 100644 --- a/qodana.yml +++ b/qodana.yml @@ -3,7 +3,7 @@ version: 1.0 linter: jetbrains/qodana-jvm-community:latest -projectJDK: "17" +projectJDK: "11" profile: name: qodana.recommended exclude: diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/MyBundle.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/MyBundle.kt deleted file mode 100644 index 1c699cb..0000000 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/MyBundle.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.warningimhack3r.intellijshadcnplugin - -import com.intellij.DynamicBundle -import org.jetbrains.annotations.NonNls -import org.jetbrains.annotations.PropertyKey - -@NonNls -private const val BUNDLE = "messages.MyBundle" - -object MyBundle : DynamicBundle(BUNDLE) { - - @JvmStatic - fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = - getMessage(key, *params) - - @Suppress("unused") - @JvmStatic - fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = - getLazyMessage(key, *params) -} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/ISPScanner.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/ISPScanner.kt new file mode 100644 index 0000000..70e1f96 --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/ISPScanner.kt @@ -0,0 +1,20 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend + +import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager +import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.ISPSource +import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.ISPSvelteSource +import com.intellij.openapi.project.Project + +object ISPScanner { + + fun findShadcnImplementation(project: Project): ISPSource? { + FileManager(project).getVirtualFilesByName("components.json").firstOrNull()?.let { componentsJson -> + val contents = componentsJson.contentsToByteArray().decodeToString() + if (contents.contains("shadcn-svelte.com")) { + return ISPSvelteSource(project) + } + } + // TODO: Add other sources + return null + } +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/FileManager.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/FileManager.kt new file mode 100644 index 0000000..6089774 --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/FileManager.kt @@ -0,0 +1,65 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend.helpers + +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiFile +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import java.nio.file.NoSuchFileException + +class FileManager(private val project: Project) { + fun saveFileAtPath(file: PsiFile, path: String) { + var deepest = getDeepestFileForPath(path) + val deepestRelativePath = deepest.path.substringAfter("${project.basePath!!}/") + path.substringAfter(deepestRelativePath).split('/').filterNot { it.isEmpty() }.forEach { subdirectory -> + deepest = deepest.createChildDirectory(this, subdirectory) + } + deepest.createChildData(this, file.name).apply { + setBinaryContent(file.text.toByteArray()) + } + } + + fun deleteFileAtPath(path: String): Boolean { + return getFileAtPath(path)?.delete(this)?.let { true } ?: false + } + + fun getVirtualFilesByName(name: String): Collection<VirtualFile> { + return FilenameIndex.getVirtualFilesByName( + name, + GlobalSearchScope.projectScope(project) + ).filter { file -> + val nodeModule = file.path.contains("node_modules") + if (!name.startsWith(".")) { + !nodeModule && !file.path.substringAfter(project.basePath!!).startsWith(".") + } else !nodeModule + } + } + + private fun getDeepestFileForPath(filePath: String): VirtualFile { + var paths = filePath.split('/') + var currentFile = getVirtualFilesByName(paths.first()).firstOrNull() ?: throw NoSuchFileException("No file found at path $filePath") + paths = paths.drop(1) + for (path in paths) { + val child = currentFile.findChild(path) + if (child == null) { + return currentFile + } else { + currentFile = child + } + } + return currentFile + } + + fun getFileAtPath(filePath: String): VirtualFile? { + try { + val deepest = getDeepestFileForPath(filePath) + return if (deepest.name == filePath.substringAfterLast('/')) deepest else null + } catch (e: Exception) { + return null + } + } + + fun getFileContentsAtPath(path: String): String? { + return getFileAtPath(path)?.contentsToByteArray()?.decodeToString() + } +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/ShellRunner.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/ShellRunner.kt new file mode 100644 index 0000000..3578168 --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/ShellRunner.kt @@ -0,0 +1,33 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend.helpers + +import com.intellij.openapi.project.Project +import com.jetbrains.rd.util.printlnError +import java.io.File + +class ShellRunner(private val project: Project? = null) { + private val failedCommands = mutableSetOf<String>() + + private fun isWindows() = System.getProperty("os.name").lowercase().contains("win") + + fun execute(command: Array<String>): String? { + val commandName = command.firstOrNull() ?: return null + if (isWindows() && failedCommands.contains(commandName)) { + command[0] = "$commandName.cmd" + } + return try { + val process = ProcessBuilder(*command) + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .directory(project?.basePath?.let { File(it) }) + .start() + process.waitFor() + process.inputStream.bufferedReader().readText() + } catch (e: Exception) { + if (isWindows() && !commandName.endsWith(".cmd")) { + failedCommands.add(commandName) + return execute(arrayOf("$commandName.cmd") + command.drop(1).toTypedArray()) + } + printlnError("Error while executing \"${command.joinToString(" ")}\": ${e.message}") + null + } + } +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/http/RequestSender.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/http/RequestSender.kt new file mode 100644 index 0000000..0cf12bc --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/http/RequestSender.kt @@ -0,0 +1,37 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend.http + +import java.net.HttpURLConnection +import java.net.URL + +// Credit to: https://gist.github.com/GrzegorzDyrda/be47602fc855a52fba240dd2c2adc2d5 +object RequestSender { + + /** + * Sends an HTTP request to the given [url], using the given HTTP [method]. The request can also + * include custom [headers] and [body]. + * + * Returns the [Response] object containing [statusCode][Response.statusCode], + * [headers][Response.headers] and [body][Response.body]. + */ + fun sendRequest(url: String, method: String = "GET", headers: Map<String, String>? = null, body: String? = null): Response { + val conn = URL(url).openConnection() as HttpURLConnection + + with(conn) { + requestMethod = method + doOutput = body != null + headers?.forEach(::setRequestProperty) + } + + if (body != null) { + conn.outputStream.use { + it.write(body.toByteArray()) + } + } + + val responseBody = conn.inputStream.use { it.readBytes() }.toString(Charsets.UTF_8) + + return Response(conn.responseCode, conn.headerFields, responseBody) + } + + data class Response(val statusCode: Int, val headers: Map<String, List<String>>? = null, val body: String? = null) +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPComponent.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPComponent.kt new file mode 100644 index 0000000..7fd5b1d --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPComponent.kt @@ -0,0 +1,6 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend.sources + +data class ISPComponent( + val name: String, + val description: String? = null +) diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPSource.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPSource.kt new file mode 100644 index 0000000..7571449 --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPSource.kt @@ -0,0 +1,19 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend.sources + +interface ISPSource { + + var domain: String + var language: String + + fun fetchAllComponents(): List<ISPComponent> + + fun fetchAllStyles(): List<ISPStyle> + + fun getInstalledComponents(): List<String> + + fun addComponent(componentName: String) + + fun isComponentUpToDate(componentName: String): Boolean + + fun removeComponent(componentName: String) +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPStyle.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPStyle.kt new file mode 100644 index 0000000..e2d317f --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPStyle.kt @@ -0,0 +1,6 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend.sources + +data class ISPStyle( + val name: String, + val label: String +) diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPSvelteSource.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPSvelteSource.kt new file mode 100644 index 0000000..2f9eb15 --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/ISPSvelteSource.kt @@ -0,0 +1,227 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend.sources + +import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager +import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.ShellRunner +import com.github.warningimhack3r.intellijshadcnplugin.backend.http.RequestSender +import com.intellij.openapi.fileTypes.FileTypeManager +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFileFactory +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import java.nio.file.NoSuchFileException + +class ISPSvelteSource(private val project: Project): ISPSource { + override var domain = "https://www.shadcn-svelte.com" + override var language = "Svelte" + + private fun fetchComponent(componentName: String): SvelteTypes.SvelteComponentWithContents { + val style = getLocalConfig().style + val response = RequestSender.sendRequest("$domain/registry/styles/$style/$componentName.json") + return response.body?.let { Json.decodeFromString<SvelteTypes.SvelteComponentWithContents>(it) } ?: throw Exception("Component not found") + } + + private fun getLocalConfig(): SvelteTypes.Config { + return FileManager(project).getFileContentsAtPath("components.json")?.let { + Json.decodeFromString(it) + } ?: throw NoSuchFileException("components.json not found") + } + + private fun resolveAlias(alias: String): String { + if (!alias.startsWith("$") && !alias.startsWith("@")) return alias + var tsConfig = FileManager(project).getFileContentsAtPath(".svelte-kit/tsconfig.json") + if (tsConfig == null) { + ShellRunner(project).execute(arrayOf("npx", "svelte-kit", "sync")) + Thread.sleep(250) // wait for the sync to create the files + tsConfig = FileManager(project).getFileContentsAtPath(".svelte-kit/tsconfig.json") ?: throw NoSuchFileException("Cannot get or generate .svelte-kit/tsconfig.json") + } + val aliasPath = Json.parseToJsonElement(tsConfig) + .jsonObject["compilerOptions"] + ?.jsonObject?.get("paths") + ?.jsonObject?.get(alias.substringBefore("/")) + ?.jsonArray?.get(0) + ?.jsonPrimitive?.content ?: throw Exception("Cannot find alias $alias") + return aliasPath.substringAfter("/").plus("/").plus(alias.substringAfter("/")) + } + + private fun replaceImports(contents: String): String { + val config = getLocalConfig() + return contents.replace( + Regex("@/registry/[^/]+"), if (config.aliases.components.startsWith("\$")) { + "\\${config.aliases.components}" // fixes Kotlin silently crashing when the replacement starts with $ with a regex + } else config.aliases.components + ).replace( + "\$lib/utils", config.aliases.utils + ) + } + + override fun fetchAllComponents(): List<ISPComponent> { + val response = RequestSender.sendRequest("$domain/registry/index.json") + return response.body?.let { + Json.decodeFromString<List<SvelteTypes.SvelteComponent>>(it) + }?.map { ISPComponent(it.name) } ?: emptyList() + } + + override fun fetchAllStyles(): List<ISPStyle> { + val response = RequestSender.sendRequest("$domain/registry/styles/index.json") + return response.body?.let { Json.decodeFromString<List<ISPStyle>>(it) } ?: emptyList() + } + + override fun getInstalledComponents(): List<String> { + return FileManager(project).getFileAtPath( + resolveAlias(getLocalConfig().aliases.components) + "/" + SvelteTypes.ComponentKind.UI.name.lowercase() + )?.children?.map { it.name }?.sorted() ?: emptyList() + } + + override fun addComponent(componentName: String) { + fun getRegistryDependencies(component: SvelteTypes.SvelteComponentWithContents): List<SvelteTypes.SvelteComponentWithContents> { + return component.registryDependencies.map { registryDependency -> + val dependency = fetchComponent(registryDependency) + listOf(dependency, *getRegistryDependencies(dependency).toTypedArray()) + }.flatten() + } + + val component = fetchComponent(componentName) + val components = listOf(component, *getRegistryDependencies(fetchComponent(componentName)).toTypedArray()) + val config = getLocalConfig() + components.forEach { downloadedComponent -> + downloadedComponent.files.forEach { file -> + val path = "${resolveAlias(config.aliases.components)}/${component.type.name.lowercase()}/${downloadedComponent.name}" + val psiFile = PsiFileFactory.getInstance(project).createFileFromText( + file.name, + FileTypeManager.getInstance().getFileTypeByExtension(file.name.substringAfterLast('.')), + replaceImports(file.content) + ) + FileManager(project).saveFileAtPath(psiFile, path) + } + } + // TODO: what to do with the dependencies to install? Notify or install them? + } + + override fun isComponentUpToDate(componentName: String): Boolean { + val config = getLocalConfig() + val remoteComponent = fetchComponent(componentName) + return remoteComponent.files.all { file -> + FileManager(project).getFileContentsAtPath( + "${resolveAlias(config.aliases.components)}/${remoteComponent.type.name.lowercase()}/${remoteComponent.name}/${file.name}" + ) == replaceImports(file.content) + } + } + + override fun removeComponent(componentName: String) { + val remoteComponent = fetchComponent(componentName) + FileManager(project).deleteFileAtPath( + "${resolveAlias(getLocalConfig().aliases.components)}/${remoteComponent.type.name.lowercase()}/${remoteComponent.name}" + ) + } +} + +object SvelteTypes { + /** + * The kind of component. + */ + @Suppress("PROVIDED_RUNTIME_TOO_LOW") + @Serializable + enum class ComponentKind { + @SerialName("components:ui") + UI, + @SerialName("components:component") + COMPONENT, + @SerialName("components:example") + EXAMPLE + } + + /** + * A shadcn-svelte component in the registry. + * @param name The name of the component. + * @param dependencies The npm dependencies of the component. + * @param registryDependencies The other components that this component depends on. + * @param files The files that make up the component. + * @param type The kind of component (always "components:ui" for now). + */ + @Suppress("PROVIDED_RUNTIME_TOO_LOW") // https://github.com/Kotlin/kotlinx.serialization/issues/993#issuecomment-984742051 + @Serializable + data class SvelteComponent( + val name: String, + val dependencies: List<String>, + val registryDependencies: List<String>, + val files: List<String>, + val type: ComponentKind + ) + + /** + * A shadcn-svelte component in the registry. + * @param name The name of the component. + * @param dependencies The npm dependencies of the component. + * @param registryDependencies The other components that this component depends on. + * @param files The files that make up the component. + * @param type The kind of component (always "components:ui" for now). + */ + @Suppress("PROVIDED_RUNTIME_TOO_LOW") + @Serializable + data class SvelteComponentWithContents( + val name: String, + val dependencies: List<String>, + val registryDependencies: List<String>, + val files: List<File>, + val type: ComponentKind + ) { + /** + * A component's file. + * @param name The name of the file. + * @param content The contents of the file. + */ + @Suppress("PROVIDED_RUNTIME_TOO_LOW") + @Serializable + data class File( + val name: String, + val content: String + ) + } + + /** + * A shadcn-svelte locally installed components.json file. + * @param `$schema` The schema URL for the file. + * @param style The library style installed (currently "default" or "new-york"). + * @param tailwind The Tailwind configuration. + * @param aliases The aliases for the components and utils directories. + */ + @Suppress("PROVIDED_RUNTIME_TOO_LOW", "kotlin:S117") + @Serializable + data class Config( + val `$schema`: String, + val style: String, + val tailwind: Tailwind, + val aliases: Aliases + ) { + /** + * The Tailwind configuration. + * @param config The relative path to the Tailwind config file. + * @param css The relative path of the Tailwind CSS file. + * @param baseColor The library's base color. + */ + @Suppress("PROVIDED_RUNTIME_TOO_LOW") + @Serializable + data class Tailwind( + val config: String, + val css: String, + val baseColor: String + ) + + /** + * The aliases for the components and utils directories. + * @param components The alias for the components directory. + * @param utils The alias for the utils directory. + */ + @Suppress("PROVIDED_RUNTIME_TOO_LOW") + @Serializable + data class Aliases( + val components: String, + val utils: String + ) + } +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/listeners/ISPToolWindowListener.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/listeners/ISPToolWindowListener.kt new file mode 100644 index 0000000..37df885 --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/listeners/ISPToolWindowListener.kt @@ -0,0 +1,25 @@ +package com.github.warningimhack3r.intellijshadcnplugin.listeners + +import com.github.warningimhack3r.intellijshadcnplugin.ui.ISPWindowContents +import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.ToolWindowManager +import com.intellij.openapi.wm.ex.ToolWindowManagerListener + +class ISPToolWindowListener(private val project: Project) : ToolWindowManagerListener { + private val toolWindowId = "shadcn/ui" + private var isToolWindowOpen: Boolean? = null + + override fun stateChanged(toolWindowManager: ToolWindowManager) { + val previousState = isToolWindowOpen + isToolWindowOpen = toolWindowManager.getToolWindow(toolWindowId)?.isVisible ?: false + if (previousState == false && isToolWindowOpen == true) { + toolWindowManager.getToolWindow(toolWindowId)?.let { + with(it.contentManager.getContent(0)!!.component) { + remove(components[0]) + add(ISPWindowContents(project).panel()) + revalidate() + } + } + } + } +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/listeners/MyApplicationActivationListener.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/listeners/MyApplicationActivationListener.kt deleted file mode 100644 index 0f3074d..0000000 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/listeners/MyApplicationActivationListener.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.warningimhack3r.intellijshadcnplugin.listeners - -import com.intellij.openapi.application.ApplicationActivationListener -import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.wm.IdeFrame - -internal class MyApplicationActivationListener : ApplicationActivationListener { - - override fun applicationActivated(ideFrame: IdeFrame) { - thisLogger().warn("Don't forget to remove all non-needed sample code files with their corresponding registration entries in `plugin.xml`.") - } -} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/services/MyProjectService.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/services/MyProjectService.kt deleted file mode 100644 index ac21c18..0000000 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/services/MyProjectService.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.warningimhack3r.intellijshadcnplugin.services - -import com.intellij.openapi.components.Service -import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.project.Project -import com.github.warningimhack3r.intellijshadcnplugin.MyBundle - -@Service(Service.Level.PROJECT) -class MyProjectService(project: Project) { - - init { - thisLogger().info(MyBundle.message("projectService", project.name)) - thisLogger().warn("Don't forget to remove all non-needed sample code files with their corresponding registration entries in `plugin.xml`.") - } - - fun getRandomNumber() = (1..100).random() -} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/toolWindow/MyToolWindowFactory.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/toolWindow/MyToolWindowFactory.kt deleted file mode 100644 index 640887e..0000000 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/toolWindow/MyToolWindowFactory.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.github.warningimhack3r.intellijshadcnplugin.toolWindow - -import com.intellij.openapi.components.service -import com.intellij.openapi.diagnostic.thisLogger -import com.intellij.openapi.project.Project -import com.intellij.openapi.wm.ToolWindow -import com.intellij.openapi.wm.ToolWindowFactory -import com.intellij.ui.components.JBLabel -import com.intellij.ui.components.JBPanel -import com.intellij.ui.content.ContentFactory -import com.github.warningimhack3r.intellijshadcnplugin.MyBundle -import com.github.warningimhack3r.intellijshadcnplugin.services.MyProjectService -import javax.swing.JButton - - -class MyToolWindowFactory : ToolWindowFactory { - - init { - thisLogger().warn("Don't forget to remove all non-needed sample code files with their corresponding registration entries in `plugin.xml`.") - } - - override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { - val myToolWindow = MyToolWindow(toolWindow) - val content = ContentFactory.getInstance().createContent(myToolWindow.getContent(), null, false) - toolWindow.contentManager.addContent(content) - } - - override fun shouldBeAvailable(project: Project) = true - - class MyToolWindow(toolWindow: ToolWindow) { - - private val service = toolWindow.project.service<MyProjectService>() - - fun getContent() = JBPanel<JBPanel<*>>().apply { - val label = JBLabel(MyBundle.message("randomLabel", "?")) - - add(label) - add(JButton(MyBundle.message("shuffle")).apply { - addActionListener { - label.text = MyBundle.message("randomLabel", service.getRandomNumber()) - } - }) - } - } -} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/ui/ISPToolWindow.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/ui/ISPToolWindow.kt new file mode 100644 index 0000000..c4f98e8 --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/ui/ISPToolWindow.kt @@ -0,0 +1,43 @@ +package com.github.warningimhack3r.intellijshadcnplugin.ui + +import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.SimpleToolWindowPanel +import com.intellij.openapi.wm.ToolWindow +import com.intellij.openapi.wm.ToolWindowFactory +import com.intellij.ui.components.JBLabel +import icons.ISPIcons +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.future.asCompletableFuture +import javax.swing.SwingConstants + +class ISPToolWindow: ToolWindowFactory { + override fun init(toolWindow: ToolWindow) { + ApplicationManager.getApplication().invokeLater { + toolWindow.setIcon(ISPIcons.logo) + } + } + + @OptIn(DelicateCoroutinesApi::class) + override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { + with(toolWindow.contentManager) { + addContent(factory.createContent(SimpleToolWindowPanel(true).apply { + GlobalScope.async { + return@async runReadAction { + FileManager(project).getVirtualFilesByName("package.json").size + } + }.asCompletableFuture().thenApplyAsync { count -> + if (count > 1) { + add(JBLabel("Multiple projects detected, not supported yet.", SwingConstants.CENTER)) + } else { + add(ISPWindowContents(project).panel()) + } + } + }, null, false)) + } + } +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/ui/ISPWindowContents.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/ui/ISPWindowContents.kt new file mode 100644 index 0000000..fdedb9f --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/ui/ISPWindowContents.kt @@ -0,0 +1,221 @@ +package com.github.warningimhack3r.intellijshadcnplugin.ui + +import com.github.warningimhack3r.intellijshadcnplugin.backend.ISPScanner +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.runWriteAction +import com.intellij.openapi.project.Project +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.components.JBTextField +import com.intellij.util.ui.JBUI +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.future.asCompletableFuture +import java.awt.BorderLayout +import java.awt.Color +import java.awt.Dimension +import java.awt.GridLayout +import java.awt.event.KeyAdapter +import java.awt.event.KeyEvent +import java.util.* +import java.util.concurrent.CompletableFuture +import javax.swing.* +import javax.swing.border.CompoundBorder +import javax.swing.border.MatteBorder +import javax.swing.border.TitledBorder + +class ISPWindowContents(private val project: Project) { + data class Item( + val title: String, + val subtitle: String?, + val actions: List<LabeledAction>, + val disabled: Boolean = false + ) + + data class LabeledAction( + val label: String, + val actionOnEnd: CompletionAction, + val action: () -> Unit + ) + + enum class CompletionAction { + REMOVE_TRIGGER, + REMOVE_ROW, + DISABLE_ROW + } + + @OptIn(DelicateCoroutinesApi::class) + fun panel() = JPanel(GridLayout(0, 1)).apply { + border = JBUI.Borders.empty(10) + + // Add a component panel + add(createPanel("Add a component") { + GlobalScope.async { + val source = runReadAction { ISPScanner.findShadcnImplementation(project) } + if (source == null) return@async emptyList() + val installedComponents = runReadAction { source.getInstalledComponents() } + source.fetchAllComponents().map { component -> + Item( + component.name, + component.description ?: "${component.name.replace("-", " ") + .replaceFirstChar { + if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() + }} component for ${source.language}", + listOf( + LabeledAction("Add", CompletionAction.DISABLE_ROW) { + runWriteAction { source.addComponent(component.name) } + } + ), + installedComponents.contains(component.name) + ) + } + }.asCompletableFuture() + }.apply { + border = BorderFactory.createCompoundBorder( + BorderFactory.createMatteBorder(0, 0, 1, 0, JBUI.CurrentTheme.ToolWindow.borderColor()), + JBUI.Borders.emptyBottom(10) + ) + }) + + // Manage components panel + add(createPanel("Manage components") { + GlobalScope.async { + val source = runReadAction { ISPScanner.findShadcnImplementation(project) } + if (source == null) return@async emptyList() + runReadAction { source.getInstalledComponents() }.map { component -> + Item( + component, + null, + listOfNotNull( + LabeledAction("Update", CompletionAction.REMOVE_TRIGGER) { + runWriteAction { source.addComponent(component) } + }.takeIf { + runReadAction { !source.isComponentUpToDate(component) } + }, + LabeledAction("Remove", CompletionAction.REMOVE_ROW) { + runWriteAction { source.removeComponent(component) } + } + ) + ) + } + }.asCompletableFuture() + }.apply { + border = JBUI.Borders.emptyTop(10) + }) + } + + private fun createPanel(title: String, listContents: () -> CompletableFuture<List<Item>>) = JPanel().apply panel@ { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + // Title + val titledBorder = TitledBorder( + BorderFactory.createMatteBorder(1, 0, 0, 0, JBUI.CurrentTheme.ToolWindow.borderColor()), + title + ) + add(JPanel().apply { + minimumSize = Dimension(Int.MAX_VALUE, preferredSize.height + 20) + maximumSize = Dimension(Int.MAX_VALUE, minimumSize.height) + border = titledBorder + }) + var items: List<Item> = emptyList() + var scrollPane: JBScrollPane? = null + // Search bar + add(JBTextField().apply { + maximumSize = Dimension(Int.MAX_VALUE, this.preferredSize.height) + addKeyListener(object : KeyAdapter() { + override fun keyReleased(e: KeyEvent) { + if (scrollPane != null) { + this@panel.remove(scrollPane) + scrollPane = componentsList(items.filter { + it.title.lowercase().contains(text.lowercase()) + || it.subtitle?.lowercase()?.contains(text.lowercase()) == true + }) + this@panel.add(scrollPane) + this@panel.revalidate() + } + } + }) + }) + // Components list + listContents() + .thenApplyAsync { + items = it + titledBorder.title = "$title (${it.size})" + scrollPane = componentsList(items) + add(scrollPane) + revalidate() + } + } + + private fun componentsList(rows: List<Item>) = JBScrollPane().apply { + setViewportView(JPanel(GridLayout(0, 1)).apply { + rows.forEach { row -> + add(createRow(row)) + } + }) + } + + private fun createRow(rowData: Item) = JPanel(BorderLayout()).apply row@ { + border = CompoundBorder( + MatteBorder(0, 0, 1, 0, JBUI.CurrentTheme.ToolWindow.borderColor()), + JBUI.Borders.empty(10) + ) + + // Title and subtitle vertically stacked + add(JPanel().apply { + layout = BoxLayout(this, BoxLayout.Y_AXIS) + add(textWrappingLabel(rowData.title)) + rowData.subtitle?.let { subtitle -> + add(textWrappingLabel(subtitle, JBUI.CurrentTheme.Label.disabledForeground())) + } + }, BorderLayout.PAGE_START) + + // Actions horizontally stacked + add(JPanel(BorderLayout()).apply { + add(JPanel().apply actions@ { + layout = BoxLayout(this, BoxLayout.X_AXIS) + + val progressBar = JProgressBar().apply { + border = JBUI.Borders.emptyRight(10) + preferredSize = Dimension(100, preferredSize.height) + isIndeterminate = true + isVisible = false + } + add(progressBar) + rowData.actions.forEach { action -> + add(JButton(action.label).apply { + isEnabled = !rowData.disabled + addActionListener { + isEnabled = false + progressBar.isVisible = !isEnabled + action.action() + isEnabled = true + progressBar.isVisible = !isEnabled + when (action.actionOnEnd) { + CompletionAction.REMOVE_TRIGGER -> parent.remove(this) + CompletionAction.REMOVE_ROW -> { + this@row.parent.remove(this@row) + // TODO: Update the other list & both counters + } + CompletionAction.DISABLE_ROW -> { + this@actions.components.forEach { it.isEnabled = false } + // TODO: Update the other list & both counters + } + } + } + }) + } + }, BorderLayout.LINE_END) + }, BorderLayout.PAGE_END) + } + + private fun textWrappingLabel(text: String, foregroundColor: Color = JBUI.CurrentTheme.Label.foreground()) = + JTextArea(text).apply { + wrapStyleWord = true + lineWrap = true + isEditable = false + font = JBUI.Fonts.label() + foreground = foregroundColor + background = UIManager.getColor("Label.background") + border = UIManager.getBorder("Label.border") + } +} diff --git a/src/main/kotlin/icons/ISPIcons.kt b/src/main/kotlin/icons/ISPIcons.kt new file mode 100644 index 0000000..54cbc7f --- /dev/null +++ b/src/main/kotlin/icons/ISPIcons.kt @@ -0,0 +1,8 @@ +package icons + +import com.intellij.openapi.util.IconLoader + +object ISPIcons { + @JvmField + val logo = IconLoader.getIcon("/icons/shadcn.svg", javaClass) +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5ce07cd..335fbcf 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,18 +1,22 @@ <!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html --> <idea-plugin> <id>com.github.warningimhack3r.intellijshadcnplugin</id> - <name>intellij-shadcn-plugin Template</name> - <vendor>warningimhack3r</vendor> + <name>shadcn/ui Components Manager</name> + <vendor url="https://github.com/WarningImHack3r">WarningImHack3r</vendor> <depends>com.intellij.modules.platform</depends> - <resource-bundle>messages.MyBundle</resource-bundle> + <projectListeners> + <listener + class="com.github.warningimhack3r.intellijshadcnplugin.listeners.ISPToolWindowListener" + topic="com.intellij.openapi.wm.ex.ToolWindowManagerListener" /> + </projectListeners> <extensions defaultExtensionNs="com.intellij"> - <toolWindow factoryClass="com.github.warningimhack3r.intellijshadcnplugin.toolWindow.MyToolWindowFactory" id="MyToolWindow"/> + <toolWindow + id="shadcn/ui" + icon="ISPIcons.logo" + anchor="right" + factoryClass="com.github.warningimhack3r.intellijshadcnplugin.ui.ISPToolWindow" /> </extensions> - - <applicationListeners> - <listener class="com.github.warningimhack3r.intellijshadcnplugin.listeners.MyApplicationActivationListener" topic="com.intellij.openapi.application.ApplicationActivationListener"/> - </applicationListeners> </idea-plugin> diff --git a/src/main/resources/META-INF/pluginIcon.svg b/src/main/resources/META-INF/pluginIcon.svg new file mode 100644 index 0000000..f5433ae --- /dev/null +++ b/src/main/resources/META-INF/pluginIcon.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> + <rect width="256" height="256" fill="black" /> + <line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" /> + <line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" /> +</svg> diff --git a/src/main/resources/icons/shadcn.svg b/src/main/resources/icons/shadcn.svg new file mode 100644 index 0000000..726f73a --- /dev/null +++ b/src/main/resources/icons/shadcn.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="13" height="13"> + <rect width="256" height="256" fill="none" /> + <line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" /> + <line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" /> +</svg> diff --git a/src/main/resources/messages/MyBundle.properties b/src/main/resources/messages/MyBundle.properties deleted file mode 100644 index 2e041d8..0000000 --- a/src/main/resources/messages/MyBundle.properties +++ /dev/null @@ -1,3 +0,0 @@ -projectService=Project service: {0} -randomLabel=The random number is: {0} -shuffle=Shuffle diff --git a/src/test/kotlin/com/github/warningimhack3r/intellijshadcnplugin/DummyTests.kt b/src/test/kotlin/com/github/warningimhack3r/intellijshadcnplugin/DummyTests.kt new file mode 100644 index 0000000..f81db28 --- /dev/null +++ b/src/test/kotlin/com/github/warningimhack3r/intellijshadcnplugin/DummyTests.kt @@ -0,0 +1,9 @@ +package com.github.warningimhack3r.intellijshadcnplugin + +import com.intellij.testFramework.fixtures.BasePlatformTestCase + +class DummyTests : BasePlatformTestCase() { + fun testDummy() { + assertTrue(true) + } +} diff --git a/src/test/kotlin/com/github/warningimhack3r/intellijshadcnplugin/MyPluginTest.kt b/src/test/kotlin/com/github/warningimhack3r/intellijshadcnplugin/MyPluginTest.kt deleted file mode 100644 index 67a78d6..0000000 --- a/src/test/kotlin/com/github/warningimhack3r/intellijshadcnplugin/MyPluginTest.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.github.warningimhack3r.intellijshadcnplugin - -import com.intellij.ide.highlighter.XmlFileType -import com.intellij.openapi.components.service -import com.intellij.psi.xml.XmlFile -import com.intellij.testFramework.TestDataPath -import com.intellij.testFramework.fixtures.BasePlatformTestCase -import com.intellij.util.PsiErrorElementUtil -import com.github.warningimhack3r.intellijshadcnplugin.services.MyProjectService - -@TestDataPath("\$CONTENT_ROOT/src/test/testData") -class MyPluginTest : BasePlatformTestCase() { - - fun testXMLFile() { - val psiFile = myFixture.configureByText(XmlFileType.INSTANCE, "<foo>bar</foo>") - val xmlFile = assertInstanceOf(psiFile, XmlFile::class.java) - - assertFalse(PsiErrorElementUtil.hasErrors(project, xmlFile.virtualFile)) - - assertNotNull(xmlFile.rootTag) - - xmlFile.rootTag?.let { - assertEquals("foo", it.name) - assertEquals("bar", it.value.text) - } - } - - fun testRename() { - myFixture.testRename("foo.xml", "foo_after.xml", "a2") - } - - fun testProjectService() { - val projectService = project.service<MyProjectService>() - - assertNotSame(projectService.getRandomNumber(), projectService.getRandomNumber()) - } - - override fun getTestDataPath() = "src/test/testData/rename" -} diff --git a/src/test/testData/rename/foo.xml b/src/test/testData/rename/foo.xml deleted file mode 100644 index b21e9f2..0000000 --- a/src/test/testData/rename/foo.xml +++ /dev/null @@ -1,3 +0,0 @@ -<root> - <a<caret>1>Foo</a1> -</root> diff --git a/src/test/testData/rename/foo_after.xml b/src/test/testData/rename/foo_after.xml deleted file mode 100644 index 980ca96..0000000 --- a/src/test/testData/rename/foo_after.xml +++ /dev/null @@ -1,3 +0,0 @@ -<root> - <a2>Foo</a2> -</root>