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

Rewrite the Tailwind classes replacement & imports replacement engines, update configs #37

Merged
merged 16 commits into from
May 18, 2024
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@

## [Unreleased]

### Changed

- Entirely rewrite the Tailwind classes replacement engine to be more accurate and faster
- Consequently, the plugin now depends
on [Svelte](https://plugins.jetbrains.com/plugin/12375-svelte) & [Vue](https://plugins.jetbrains.com/plugin/9442-vue-js)
extensions and only works on WebStorm or IntelliJ IDEA Ultimate

## [0.7.7] - 2024-03-29

### Changed
Expand Down
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@
[![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)

## ToDo list before 1.0.0
## 1.0.0 roadmap

- Rework `class`es replacement detection mechanism to be 100% accurate
- Add tests for this
- Add support for Vue `typescript` option (transpiling TypeScript to JavaScript as well as in `*.vue` files)
- See https://github.com/radix-vue/shadcn-vue/issues/378

## Description

<!-- Plugin description -->
Manage your shadcn/ui components in your project. Supports Svelte, React, Vue, and Solid.

This plugin will help you manage your shadcn/ui components through a simple tool window. Add, remove, update them with a single click.
**This plugin will only work with an existing `components.json` file. Manually copied components will not be detected otherwise.**
This plugin will help you manage your shadcn/ui components through a simple tool window. Add, remove, update them with a
single click.
**This plugin will only work with an existing `components.json` file. Manually copied components will not be detected
otherwise.**

## Features

Expand All @@ -33,7 +34,8 @@ This plugin will help you manage your shadcn/ui components through a simple tool

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`.
**When adding or removing components, the tool window won't refresh automatically yet. You can refresh it by closing and reopening it.**
**When adding or removing components, the tool window won't refresh automatically yet. You can refresh it by closing and
reopening it.**

## Planned Features

Expand All @@ -44,21 +46,23 @@ If you don't see the tool window, you can open it from `View > Tool Windows > sh
- Figure out a clean way to refresh the tool window automatically after adding or removing components
- Refresh/recreate the tool window automatically when the project finishes indexing
- Add support for monorepos

<!-- Plugin description end -->

## Installation

- Using the IDE built-in plugin system:

<kbd>Settings/Preferences</kbd> > <kbd>Plugins</kbd> > <kbd>Marketplace</kbd> > <kbd>Search for "intellij-shadcn-plugin"</kbd> >

<kbd>Settings/Preferences</kbd> > <kbd>Plugins</kbd> > <kbd>Marketplace</kbd> > <kbd>Search for "
intellij-shadcn-plugin"</kbd> >
<kbd>Install</kbd>

- Manually:

Download the [latest release](https://github.com/WarningImHack3r/intellij-shadcn-plugin/releases/latest) and install it manually using
Download the [latest release](https://github.com/WarningImHack3r/intellij-shadcn-plugin/releases/latest) and install
it manually using
<kbd>Settings/Preferences</kbd> > <kbd>Plugins</kbd> > <kbd>⚙️</kbd> > <kbd>Install plugin from disk...</kbd>


---
Plugin based on the [IntelliJ Platform Plugin Template][template].

Expand Down
6 changes: 3 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ pluginGroup = com.github.warningimhack3r.intellijshadcnplugin
pluginName = intellij-shadcn-plugin
pluginRepositoryUrl = https://github.com/WarningImHack3r/intellij-shadcn-plugin
# SemVer format -> https://semver.org
pluginVersion = 0.7.7
pluginVersion = 0.8.0

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 213
pluginUntilBuild =

# IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension
platformType = IC
platformType = IU
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
platformPlugins =
platformPlugins = JavaScript, dev.blachut.svelte.lang:0.21.1, org.jetbrains.plugins.vue:213.5744.223

# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion = 8.6
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.github.warningimhack3r.intellijshadcnplugin.backend.helpers

import com.github.warningimhack3r.intellijshadcnplugin.notifications.NotificationManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import kotlinx.serialization.json.Json
Expand All @@ -22,9 +21,7 @@ class DependencyManager(private val project: Project) {
"yarn.lock" to "yarn",
"bun.lockb" to "bun"
).filter {
runReadAction {
fileManager.getVirtualFilesByName(it.key).isNotEmpty()
}
fileManager.getVirtualFilesByName(it.key).isNotEmpty()
}.values.firstOrNull()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.github.warningimhack3r.intellijshadcnplugin.backend.helpers

import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
Expand All @@ -20,16 +22,18 @@ class FileManager(private val project: Project) {
path.substringAfter(deepestRelativePath).split('/').filterNot { it.isEmpty() }.also {
log.debug("Creating subdirectories ${it.joinToString(", ")}")
}.forEach { subdirectory ->
deepest = deepest.createChildDirectory(this, subdirectory)
deepest = runWriteAction { deepest.createChildDirectory(this, subdirectory) }
}
deepest.createChildData(this, file.name).setBinaryContent(file.text.toByteArray()).also {
runWriteAction {
deepest.createChildData(this, file.name).setBinaryContent(file.text.toByteArray())
}.also {
log.debug("Saved file ${file.name} under ${deepest.path}")
}
}

fun deleteFile(file: VirtualFile): Boolean {
return try {
file.delete(this).let { true }
runWriteAction { file.delete(this) }.let { true }
} catch (e: IOException) {
false
}.also {
Expand All @@ -51,10 +55,12 @@ class FileManager(private val project: Project) {
// a simple call to FilenameIndex.getVirtualFilesByName.
// This is a dirty workaround to make it work on production,
// because it works fine during local development.
FilenameIndex.getVirtualFilesByName(
"components.json",
GlobalSearchScope.projectScope(project)
).firstOrNull().also {
runReadAction {
FilenameIndex.getVirtualFilesByName(
"components.json",
GlobalSearchScope.projectScope(project)
)
}.firstOrNull().also {
if (it == null) {
log.warn("components.json not found with the workaround")
}
Expand All @@ -64,10 +70,12 @@ class FileManager(private val project: Project) {
log.warn("No file named $name found with the workaround")
}
} else {
FilenameIndex.getVirtualFilesByName(
name,
GlobalSearchScope.projectScope(project)
)
runReadAction {
FilenameIndex.getVirtualFilesByName(
name,
GlobalSearchScope.projectScope(project)
)
}
}).filter { file ->
!file.path.contains("/node_modules/") && !file.path.contains("/.git/")
}.sortedBy { file ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.github.warningimhack3r.intellijshadcnplugin.backend.helpers

import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiFileFactory

object PsiHelper {

fun createPsiFile(project: Project, fileName: String, text: String): PsiFile {
assert(fileName.contains('.')) { "File name must contain an extension" }
return runReadAction {
PsiFileFactory.getInstance(project).createFileFromText(
fileName, FileTypeManager.getInstance().getFileTypeByExtension(
fileName.substringAfterLast('.')
), text
)
}
}

fun writeAction(file: PsiFile, description: String? = null, action: () -> Unit) {
WriteCommandAction.runWriteCommandAction(
file.project, description,
"com.github.warningimhack3r.intellijshadcnplugin",
action, file
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ package com.github.warningimhack3r.intellijshadcnplugin.backend.sources

import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.DependencyManager
import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager
import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.PsiHelper
import com.github.warningimhack3r.intellijshadcnplugin.backend.http.RequestSender
import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.config.Config
import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.remote.Component
import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.remote.ComponentWithContents
import com.github.warningimhack3r.intellijshadcnplugin.notifications.NotificationManager
import com.intellij.notification.NotificationAction
import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileTypes.FileTypeManager
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiFile
import kotlinx.serialization.KSerializer
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
Expand Down Expand Up @@ -50,13 +50,9 @@ abstract class Source<C : Config>(val project: Project, private val serializer:

protected abstract fun resolveAlias(alias: String): String

protected fun cleanAlias(alias: String): String = if (alias.startsWith("\$")) {
"\\$alias" // fixes Kotlin silently crashing when the replacement starts with $ with a regex
} else alias

protected open fun adaptFileExtensionToConfig(extension: String): String = extension

protected abstract fun adaptFileToConfig(contents: String): String
protected abstract fun adaptFileToConfig(file: PsiFile)

protected open fun fetchComponent(componentName: String): ComponentWithContents {
return RequestSender.sendRequest("$domain/registry/styles/${getLocalConfig().style}/$componentName.json")
Expand Down Expand Up @@ -101,6 +97,7 @@ abstract class Source<C : Config>(val project: Project, private val serializer:
}

open fun addComponent(componentName: String) {
val config = getLocalConfig()
// Install component
val component = fetchComponent(componentName)
val installedComponents = getInstalledComponents()
Expand All @@ -113,7 +110,7 @@ abstract class Source<C : Config>(val project: Project, private val serializer:
log.debug("Installing ${it.size} components: ${it.joinToString(", ") { component -> component.name }}")
}.forEach { downloadedComponent ->
val path =
"${resolveAlias(getLocalConfig().aliases.components)}/${component.type.substringAfterLast(":")}" + if (usesDirectoriesForComponents()) {
"${resolveAlias(config.aliases.components)}/${component.type.substringAfterLast(":")}" + if (usesDirectoriesForComponents()) {
"/${downloadedComponent.name}"
} else ""
// Check for deprecated components
Expand All @@ -134,7 +131,7 @@ abstract class Source<C : Config>(val project: Project, private val serializer:
listOf(
NotificationAction.createSimple("Remove " + if (multipleFiles) "them" else "it") {
remotelyDeletedFiles.forEach { file ->
runWriteAction { fileManager.deleteFile(file) }
fileManager.deleteFile(file)
}
log.info(
"Removed deprecated file${if (multipleFiles) "s" else ""} from ${downloadedComponent.name} (${
Expand All @@ -157,13 +154,10 @@ abstract class Source<C : Config>(val project: Project, private val serializer:
}
}
downloadedComponent.files.forEach { file ->
val psiFile = PsiFileFactory.getInstance(project).createFileFromText(
adaptFileExtensionToConfig(file.name),
FileTypeManager.getInstance().getFileTypeByExtension(
adaptFileExtensionToConfig(file.name).substringAfterLast('.')
),
adaptFileToConfig(file.content)
val psiFile = PsiHelper.createPsiFile(
project, adaptFileExtensionToConfig(file.name), file.content
)
adaptFileToConfig(psiFile)
fileManager.saveFileAtPath(psiFile, path)
}
}
Expand Down Expand Up @@ -205,14 +199,21 @@ abstract class Source<C : Config>(val project: Project, private val serializer:

open fun isComponentUpToDate(componentName: String): Boolean {
val remoteComponent = fetchComponent(componentName)
val componentPath =
"${resolveAlias(getLocalConfig().aliases.components)}/${remoteComponent.type.substringAfterLast(":")}${
if (usesDirectoriesForComponents()) {
"/${remoteComponent.name}"
} else ""
}"
val fileManager = FileManager(project)
return remoteComponent.files.all { file ->
(FileManager(project).getFileContentsAtPath(
"${resolveAlias(getLocalConfig().aliases.components)}/${remoteComponent.type.substringAfterLast(":")}${
if (usesDirectoriesForComponents()) {
"/${remoteComponent.name}"
} else ""
}/${file.name}"
) == adaptFileToConfig(file.content)).also {
val psiFile = PsiHelper.createPsiFile(
project, adaptFileExtensionToConfig(file.name), file.content
)
adaptFileToConfig(psiFile)
(fileManager.getFileContentsAtPath("$componentPath/${file.name}") == runReadAction {
psiFile.text
}).also {
log.debug("File ${file.name} for ${remoteComponent.name} is ${if (it) "" else "NOT "}up to date")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ import kotlinx.serialization.Serializable
@Suppress("PROVIDED_RUNTIME_TOO_LOW", "kotlin:S117")
@Serializable
class ReactConfig(
override val `$schema`: String,
override val `$schema`: String = "https://ui.shadcn.com/schema.json",
override val style: String,
override val tailwind: Tailwind,
val rsc: Boolean,
val rsc: Boolean = false,
val tsx: Boolean = true,
override val tailwind: Tailwind,
override val aliases: Aliases
) : Config() {

Expand All @@ -35,7 +35,7 @@ class ReactConfig(
override val config: String,
override val css: String,
override val baseColor: String,
val cssVariables: Boolean,
val cssVariables: Boolean = true,
val prefix: String = ""
) : Config.Tailwind()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable
@Suppress("PROVIDED_RUNTIME_TOO_LOW", "kotlin:S117")
@Serializable
class SolidConfig(
override val `$schema`: String,
override val `$schema`: String = "",
override val style: String,
override val tailwind: VueConfig.Tailwind,
override val aliases: Aliases
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import kotlinx.serialization.Serializable
@Suppress("PROVIDED_RUNTIME_TOO_LOW", "kotlin:S117")
@Serializable
class SvelteConfig(
override val `$schema`: String,
override val `$schema`: String = "https://shadcn-svelte.com/schema.json",
override val style: String,
override val tailwind: Tailwind,
val typescript: Boolean = true,
override val aliases: Aliases
override val aliases: Aliases,
val typescript: Boolean = true
) : Config() {

@Serializable
Expand Down
Loading
Loading