Skip to content

Commit

Permalink
0.7.1
Browse files Browse the repository at this point in the history
- Add support for solid-ui.com
- Add error reporter on crash
- Add support for non SvelteKit Svelte projects
- Improve tool window appearance
- Improve implementation detection accuracy
  • Loading branch information
WarningImHack3r committed Feb 11, 2024
1 parent 8c82794 commit a3e8f9f
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 15 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

- Rework `class`es replacement detection mechanism to be 100% accurate
- Add tests for this
- Add support for Vue's `typescript` option (transpiling TypeScript to JavaScript in `*.vue` files)
- Add support for Vue/Svelte `typescript` option (transpiling TypeScript to JavaScript as well as in `*.vue` & `*.svelte` files)

## Description

Expand Down Expand Up @@ -37,6 +37,9 @@ If you don't see the tool window, you can open it from `View > Tool Windows > sh

## Planned Features

- Add a button to open the docs of a component in the `Add component` panel
- Initialize the CLI if not existing yet?
- Add diff mechanism to components (remove spaces, \n, \t, \r, etc.?)
- Parse `vite.config.(js|ts)` (and others like `nuxt.config.ts`) to resolve aliases as a fallback of `tsconfig.json`
- 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
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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.0
pluginVersion = 0.7.1

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 213
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl.Svel
import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl.VueSource
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

object SourceScanner {

fun findShadcnImplementation(project: Project): Source<*>? {
return FileManager(project).getFileAtPath("components.json")?.let { componentsJson ->
val contents = componentsJson.contentsToByteArray().decodeToString()
return FileManager(project).getFileContentsAtPath("components.json")?.let { componentsJson ->
val contents = Json.parseToJsonElement(componentsJson).jsonObject
val schema = contents["\$schema"]?.jsonPrimitive?.content ?: ""
when {
contents.contains("shadcn-svelte.com") -> SvelteSource(project)
contents.contains("ui.shadcn.com") -> ReactSource(project)
contents.contains("shadcn-vue.com")
|| contents.contains("\"framework\": \"") -> VueSource(project)
contents.contains("shadcn-solid") -> SolidSource(project)
schema.contains("shadcn-svelte.com") -> SvelteSource(project)
schema.contains("ui.shadcn.com") -> ReactSource(project)
schema.contains("shadcn-vue.com")
|| contents.keys.contains("framework") -> VueSource(project)
schema.contains("shadcn-solid")
|| schema.contains("solid-ui.com") -> SolidSource(project)
else -> null
}
}.also {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ class DependencyManager(private val project: Project) {

fun isDependencyInstalled(dependency: String): Boolean {
// Read the package.json file
return FileManager(project).getFileAtPath("package.json")?.let { packageJson ->
val contents = packageJson.contentsToByteArray().decodeToString()
Json.parseToJsonElement(contents).jsonObject.filter {
return FileManager(project).getFileContentsAtPath("package.json")?.let { packageJson ->
Json.parseToJsonElement(packageJson).jsonObject.filter {
it.key == "dependencies" || it.key == "devDependencies"
}.map { it.value.jsonObject.keys }.flatten().also {
logger<DependencyManager>().debug("Installed dependencies: $it, is $dependency installed? ${it.contains(dependency)}")
}.contains(dependency)
// Check if the dependency is installed
} ?: false
} ?: false.also {
logger<DependencyManager>().error("package.json not found, returning false")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import java.net.URI
import java.nio.file.NoSuchFileException

abstract class Source<C : Config>(val project: Project, private val serializer: KSerializer<C>) {
private val log = logger<Source<*>>()
private val log = logger<Source<C>>()
abstract var framework: String
private val domain: String
get() = URI(getLocalConfig().`$schema`).let { uri ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl

import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.DependencyManager
import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager
import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.ShellRunner
import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.Source
Expand All @@ -24,10 +25,14 @@ class SvelteSource(project: Project) : Source<SvelteConfig>(project, SvelteConfi
if (!alias.startsWith("$") && !alias.startsWith("@")) return alias.also {
log.debug("Alias $alias does not start with $ or @, returning it as-is")
}
val configFile = ".svelte-kit/tsconfig.json"
val usesKit = DependencyManager(project).isDependencyInstalled("@sveltejs/kit")
val configFile = if (usesKit) ".svelte-kit/tsconfig.json" else "tsconfig.json"
val fileManager = FileManager(project)
var tsConfig = fileManager.getFileContentsAtPath(configFile)
if (tsConfig == null) {
if (!usesKit) throw NoSuchFileException("Cannot get $configFile").also {
log.error("Failed to get $configFile, throwing exception")
}
val res = ShellRunner(project).execute(arrayOf("npx", "svelte-kit", "sync"))
if (res == null) {
NotificationManager(project).sendNotification(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.github.warningimhack3r.intellijshadcnplugin.errorsubmitter

import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.DumbAwareAction

/**
* Force an Exception from within this plugin. This action is only used for testing the plugin
* [ErrorReportSubmitter][com.github.warningimhack3r.intellijshadcnplugin.errorsubmitter.GitHubErrorReportSubmitter] extension point.
*
* This can be enabled by setting the below in `Help -> Diagnostic Tools -> Debug Log Settings...`:
* ```
* #com.github.warningimhack3r.intellijshadcnplugin.errorreport.ErrorThrowingAction
* ```
*/
class ErrorThrowingAction : DumbAwareAction() {
companion object {
private val LOG = logger<ErrorThrowingAction>()
}

override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = LOG.isDebugEnabled
}

override fun actionPerformed(e: AnActionEvent) {
if (Math.random() < 0.5) {
throw StringIndexOutOfBoundsException("String Index Out Of Bounds! Yikes!")
} else {
throw ClassCastException("Class Cast Exception! Oh Boy!")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package com.github.warningimhack3r.intellijshadcnplugin.errorsubmitter

import com.intellij.ide.BrowserUtil
import com.intellij.ide.DataManager
import com.intellij.ide.troubleshooting.CompositeGeneralTroubleInfoCollector
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.diagnostic.ErrorReportSubmitter
import com.intellij.openapi.diagnostic.IdeaLoggingEvent
import com.intellij.openapi.diagnostic.SubmittedReportInfo
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.util.Consumer
import java.awt.Component
import java.net.URI
import java.net.URLEncoder
import java.nio.charset.StandardCharsets


// Inspired by:
// https://github.com/ChrisCarini/loc-change-count-detector-jetbrains-plugin/blob/main/src/main/java/com/chriscarini/jetbrains/locchangecountdetector/errorhandler/GitHubErrorReportSubmitter.java
// https://github.com/SonarSource/sonarlint-intellij/blob/master/src/main/java/org/sonarlint/intellij/errorsubmitter/BlameSonarSource.java
class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
companion object {
private const val MAX_URL_LENGTH = 2083
private const val BUG_LOGS_KEY = "bug-logs"
private const val TRIMMED_STACKTRACE_MARKER = "\n\n<TRIMMED STACKTRACE>"
private const val WORM_UNICODE = "\uD83D\uDC1B"
}

override fun getReportActionText() = "$WORM_UNICODE Open an Issue on GitHub"

override fun submit(
events: Array<out IdeaLoggingEvent>,
additionalInfo: String?,
parentComponent: Component,
consumer: Consumer<in SubmittedReportInfo>
): Boolean {
return try {
val event = if (events.isNotEmpty()) events.first() else null

val stackTrace = event?.throwableText ?: ""
val simpleErrorMessage = if (event != null && !event.message.isNullOrEmpty()) {
event.message
} else stackTrace.substringBefore("\n")

val project = CommonDataKeys.PROJECT.getData(
DataManager.getInstance().getDataContext(parentComponent)
) ?: getLastFocusedOrOpenedProject()

BrowserUtil.browse(buildAbbreviatedUrl(
mapOf(
"title" to "[crash] $simpleErrorMessage",
"bug-explanation" to (additionalInfo ?: ""),
BUG_LOGS_KEY to stackTrace.split("\n").filter {
!it.trim().startsWith("at java.desktop/")
&& !it.trim().startsWith("at java.base/")
}.joinToString("\n"),
/*"device-os" to with(System.getProperty("os.name").lowercase()) {
when { // Windows, macOS or Linux
startsWith("windows") -> "Windows"
startsWith("mac") -> "macOS"
else -> "Linux"
}
},*/ // currently cannot be set (https://github.com/orgs/community/discussions/44983)
"additional-device-info" to getDefaultHelpBlock(project)
).filterValues { it.isNotEmpty() }
))
consumer.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.NEW_ISSUE))
true
} catch (e: Exception) {
consumer.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.FAILED))
false
}
}

private fun buildAbbreviatedUrl(fields: Map<String, String>): URI {
val url = buildUrl(fields)
return URI(if (url.length > MAX_URL_LENGTH) {
val newMap = fields.toMutableMap()
newMap[BUG_LOGS_KEY]?.let { fullLog ->
val logLessUrlLength = buildUrl(fields.mapValues { (key, value) ->
if (key == BUG_LOGS_KEY) "" else value
}).length
val encodedLogDiff = URLEncoder.encode(fullLog, StandardCharsets.UTF_8).length - fullLog.length
newMap[BUG_LOGS_KEY] = fullLog.take(
(MAX_URL_LENGTH - logLessUrlLength - encodedLogDiff).coerceAtLeast(fullLog.substringBefore("\n").length)
).run {
if (length > fullLog.substringBefore("\n").length + TRIMMED_STACKTRACE_MARKER.length) {
"${take(length - TRIMMED_STACKTRACE_MARKER.length)}$TRIMMED_STACKTRACE_MARKER"
} else this
}
}
val shorterLogUrl = buildUrl(newMap)
if (shorterLogUrl.length > MAX_URL_LENGTH) {
buildUrl(fields.filter { (key, _) ->
key == "title" || key == "additional-device-info"
})
} else shorterLogUrl
} else url)
}

private fun buildUrl(fields: Map<String, String>) = buildString {
append("https://github.com/WarningImHack3r/intellij-shadcn-plugin/issues/new?labels=bug&template=bug_report.yml")
fields.forEach { (key, value) ->
append("&$key=${URLEncoder.encode(value, StandardCharsets.UTF_8)}")
}
}

private fun getDefaultHelpBlock(project: Project): String {
return CompositeGeneralTroubleInfoCollector().collectInfo(project).run {
val trimmedAndCleaned = split("\n".toRegex()).filter { trim().isNotEmpty() }
// Build, JRE, JVM, OS
trimmedAndCleaned
.dropWhile { s -> s == "=== About ==="}
.takeWhile { s -> s != "=== System ===" }
.filter { s -> !s.startsWith("idea.") && !s.startsWith("Theme") }
.joinToString("\n") + "\n" +
// Plugins
trimmedAndCleaned
.dropWhile { s -> s != "=== Plugins ===" }
.takeWhile { s -> s.isNotBlank() && s.isNotEmpty() }
.joinToString("\n")
}
}

/**
* Get the last focused or opened project; this is a best-effort attempt.
* Code pulled from [org.jetbrains.ide.RestService]
*
* @return the [Project][com.intellij.openapi.project.Project] that was last in focus or open.
*/
private fun getLastFocusedOrOpenedProject(): Project {
val project = IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project
if (project == null) {
val projectManager = ProjectManager.getInstance()
val openProjects = projectManager.openProjects
return if (openProjects.isNotEmpty()) openProjects.first() else projectManager.defaultProject
}
return project
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class ISPWindowContents(private val project: Project) {
rows.forEach { row ->
add(createRow(row))
}
add(JPanel())
})
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

<depends>com.intellij.modules.platform</depends>

<actions>
<action id="com.github.warningimhack3r.npmupdatedependencies.errorsubmitter.ErrorThrowingAction"
class="com.github.warningimhack3r.intellijshadcnplugin.errorsubmitter.ErrorThrowingAction"
text="[shadcn/ui Debug] Throw an Error"
description="[shadcn/ui Debug] Try out the error handler"
icon="AllIcons.Debugger.Db_exception_breakpoint" />
</actions>

<projectListeners>
<listener
class="com.github.warningimhack3r.intellijshadcnplugin.listeners.ToolWindowListener"
Expand All @@ -21,5 +29,7 @@
<notificationGroup
id="shadcn/ui"
displayType="STICKY_BALLOON" />
<errorHandler
implementation="com.github.warningimhack3r.intellijshadcnplugin.errorsubmitter.GitHubErrorReportSubmitter" />
</extensions>
</idea-plugin>

0 comments on commit a3e8f9f

Please sign in to comment.