-
Notifications
You must be signed in to change notification settings - Fork 5.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Gradle] Add styling for ToolingDiagnostic messages
^KT-73906
- Loading branch information
1 parent
fcf7d45
commit 778115e
Showing
3 changed files
with
220 additions
and
2 deletions.
There are no files selected for viewing
139 changes: 139 additions & 0 deletions
139
...c/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/StyledToolingDiagnostic.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
/* | ||
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors. | ||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. | ||
*/ | ||
|
||
package org.jetbrains.kotlin.gradle.plugin.diagnostics | ||
|
||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.TerminalColorSupport.TerminalStyle.blue | ||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.TerminalColorSupport.TerminalStyle.bold | ||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.TerminalColorSupport.TerminalStyle.green | ||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.TerminalColorSupport.TerminalStyle.italic | ||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.TerminalColorSupport.TerminalStyle.lightBlue | ||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.TerminalColorSupport.TerminalStyle.orange | ||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.TerminalColorSupport.TerminalStyle.red | ||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.TerminalColorSupport.TerminalStyle.yellow | ||
import org.jetbrains.kotlin.gradle.plugin.diagnostics.ToolingDiagnostic.Severity.* | ||
|
||
/** | ||
* Represents diagnostic icons used to indicate the severity level of diagnostics. | ||
* | ||
* @property icon The visual representation of the diagnostic icon, such as a warning or error symbol. | ||
*/ | ||
enum class DiagnosticIcon(val icon: String) { | ||
WARNING("⚠️"), | ||
ERROR("❌"), | ||
} | ||
|
||
/** | ||
* Represents a diagnostic message in a styled form, intended for tools and plugins. | ||
* | ||
* Provides information about the diagnostic, including a name, a message describing the issue, | ||
* an optional solution, and optional documentation references. | ||
* | ||
* @property name The name of the diagnostic, providing a brief identifier for the issue. | ||
* @property message The descriptive message explaining the diagnostic or issue. | ||
* @property solution An optional proposed solution or recommended steps to resolve the issue. | ||
* @property documentation Optional documentation reference offering additional context or resources. | ||
*/ | ||
interface StyledToolingDiagnostic { | ||
val name: String | ||
val message: String | ||
val solution: String? | ||
val documentation: String? | ||
} | ||
|
||
/** | ||
* This class provides a styled implementation of the `StyledToolingDiagnostic` interface. | ||
* | ||
* It wraps a `ToolingDiagnostic` to present its fields in a styled format | ||
* through methods and properties like `name`, `message`, `solution`, and `documentation`. | ||
* | ||
* @constructor Creates an instance of `StyledToolingDiagnosticImp` using a `ToolingDiagnostic`. | ||
* @param diagnostic The `ToolingDiagnostic` instance containing raw diagnostic data. | ||
* | ||
* The following details are styled: | ||
* - The `name` is constructed with a severity-based icon and a colored identifier name. | ||
* - The `message` is formatted with bold text. | ||
* - The `solution` is presented in a formatted list or as a single-line message, with green styling. | ||
* - The `documentation` is styled in blue, if available. | ||
* | ||
* Severity-based styling: | ||
* - `WARNING`: Yellow text styling for the identifier name. | ||
* - `ERROR` or `FATAL`: Red text styling for the identifier name. | ||
* | ||
* Solution presentation: | ||
* - If one solution is present, it is labeled "Solution" and italicized. | ||
* - If multiple solutions exist, each is listed with a bullet point, italicized, and styled in green. | ||
*/ | ||
private class StyledToolingDiagnosticImp(private val diagnostic: ToolingDiagnostic) : StyledToolingDiagnostic { | ||
override val name: String get() = buildName() | ||
override val message: String get() = buildMessage() | ||
override val solution: String? get() = buildSolution() | ||
override val documentation: String? get() = buildDocumentation() | ||
|
||
private fun buildName(): String { | ||
val icon = when (diagnostic.severity) { | ||
WARNING -> DiagnosticIcon.WARNING | ||
else -> DiagnosticIcon.ERROR | ||
} | ||
return buildString { | ||
append(icon.icon) | ||
append(" ") | ||
append(diagnostic.identifier.displayName.bold().let { | ||
when (diagnostic.severity) { | ||
WARNING -> it.yellow() | ||
ERROR, FATAL -> it.red() | ||
} | ||
}) | ||
} | ||
} | ||
|
||
private fun buildMessage(): String { | ||
// Optional: Early return for messages without code blocks | ||
if (!diagnostic.message.contains("```")) { | ||
return diagnostic.message.bold() | ||
} | ||
|
||
var inCodeBlock = false | ||
val lines = diagnostic.message.lines() | ||
return buildString { | ||
for (line in lines) { | ||
when { | ||
line.trim() == "```" -> { | ||
inCodeBlock = !inCodeBlock | ||
continue | ||
} | ||
inCodeBlock -> appendLine(line.orange()) | ||
else -> appendLine(line.bold()) | ||
} | ||
} | ||
}.trimEnd() | ||
} | ||
|
||
private fun buildSolution(): String? { | ||
val solutions = diagnostic.solutions | ||
if (solutions.isEmpty()) return null | ||
|
||
return buildString { | ||
val prefix = if (solutions.size == 1) "Solution" else "Solutions" | ||
appendLine("$prefix:".bold().green()) | ||
|
||
if (solutions.size == 1) { | ||
append(solutions.single().italic().green()) | ||
} else { | ||
solutions.forEach { solution -> | ||
appendLine(" • ${solution.italic()}".green()) | ||
} | ||
} | ||
}.trimEnd() | ||
} | ||
|
||
private fun buildDocumentation(): String? = | ||
diagnostic.documentation?.let { | ||
val highLightedUrl = it.url.blue() | ||
it.additionalUrlContext.replace(it.url, highLightedUrl).lightBlue() | ||
} | ||
} | ||
|
||
internal fun ToolingDiagnostic.styled(): StyledToolingDiagnostic = StyledToolingDiagnosticImp(this) |
78 changes: 78 additions & 0 deletions
78
.../src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/diagnostics/TerminalColorSupport.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors. | ||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. | ||
*/ | ||
|
||
package org.jetbrains.kotlin.gradle.plugin.diagnostics | ||
|
||
import org.jetbrains.kotlin.konan.target.HostManager | ||
|
||
object TerminalColorSupport { | ||
/** | ||
* Enum representing various types of terminal environments. | ||
* | ||
* This enum class is used to differentiate between terminal environments | ||
* typically encountered across different operating systems. | ||
* | ||
* - `WINDOWS_CMD`: Represents the Windows Command Prompt environment. | ||
* - `WINDOWS_POWERSHELL`: Represents the Windows PowerShell environment. | ||
* - `UNIX_LIKE`: Represents Unix-like terminal environments including Linux, macOS, and other Unix-based systems. | ||
* - `UNKNOWN`: Represents an unrecognized or unsupported terminal environment. | ||
*/ | ||
enum class TerminalType { | ||
WINDOWS_CMD, | ||
WINDOWS_POWERSHELL, | ||
UNIX_LIKE, | ||
UNKNOWN | ||
} | ||
|
||
private fun detectTerminalType() = when { | ||
HostManager.hostIsMingw -> { | ||
when { | ||
System.getenv("PROMPT") != null -> TerminalType.WINDOWS_CMD | ||
System.getenv("PSModulePath") != null -> TerminalType.WINDOWS_POWERSHELL | ||
else -> TerminalType.UNKNOWN | ||
} | ||
} | ||
HostManager.hostIsLinux || HostManager.hostIsMac -> TerminalType.UNIX_LIKE | ||
else -> TerminalType.UNKNOWN | ||
} | ||
|
||
private fun supportsColor() = when (detectTerminalType()) { | ||
TerminalType.WINDOWS_CMD -> false // No native ANSI support | ||
TerminalType.WINDOWS_POWERSHELL -> true // Supports ANSI from PS 6.0+ | ||
TerminalType.UNIX_LIKE -> true | ||
TerminalType.UNKNOWN -> false | ||
} | ||
|
||
/** | ||
* Provides ANSI escape codes for applying various styles and colors to terminal text. | ||
* Includes constants for commonly used styles and colors as well as extension functions | ||
* to easily style strings. | ||
* | ||
* The object is designed to simplify the process of formatting text for terminal output. | ||
* Reset codes are automatically appended after applying styles to ensure proper formatting. | ||
*/ | ||
object TerminalStyle { | ||
// ANSI color and style constants | ||
private const val RESET = "\u001B[0m" | ||
private const val YELLOW = "\u001B[33m" | ||
private const val GREEN = "\u001B[32m" | ||
private const val BOLD = "\u001B[1m" | ||
private const val ITALIC = "\u001B[3m" | ||
private const val RED = "\u001B[31m" | ||
private const val BLUE = "\u001B[34m" | ||
private const val LIGHT_BLUE = "\u001B[36m" | ||
private const val ORANGE = "\u001B[38;5;214m" | ||
|
||
// Convenience extension functions for styling | ||
fun String.bold() = if (supportsColor()) "$BOLD$this$RESET" else "" | ||
fun String.italic() = if (supportsColor()) "$ITALIC$this$RESET" else "" | ||
fun String.yellow() = if (supportsColor()) "$YELLOW$this$RESET" else "" | ||
fun String.green() = if (supportsColor()) "$GREEN$this$RESET" else "" | ||
fun String.red() = if (supportsColor()) "$RED$this$RESET" else "" | ||
fun String.blue() = if (supportsColor()) "$BLUE$this$RESET" else "" | ||
fun String.lightBlue() = if (supportsColor()) "$LIGHT_BLUE$this$RESET" else "" | ||
fun String.orange() = if (supportsColor()) "$ORANGE$this$RESET" else "" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters