diff --git a/.idea/modules.xml b/.idea/modules.xml index 9d286827273e3..0e8ee3fd92c71 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -754,6 +754,7 @@ <module fileurl="file://$PROJECT_DIR$/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml" filepath="$PROJECT_DIR$/platform/jewel/int-ui/int-ui-standalone/intellij.platform.jewel.intUi.standalone.iml" /> <module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/core/intellij.platform.jewel.markdown.core.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/core/intellij.platform.jewel.markdown.core.iml" /> <module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/extension/autolink/intellij.platform.jewel.markdown.extension.autolink.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/extension/autolink/intellij.platform.jewel.markdown.extension.autolink.iml" /> + <module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/extension/gfm-tables/intellij.platform.jewel.markdown.extension.gfmTables.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/extension/gfm-tables/intellij.platform.jewel.markdown.extension.gfmTables.iml" /> <module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/extension/gfm-alerts/intellij.platform.jewel.markdown.extension.gfmAlerts.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/extension/gfm-alerts/intellij.platform.jewel.markdown.extension.gfmAlerts.iml" /> <module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/ide-laf-bridge-styling/intellij.platform.jewel.markdown.ideLafBridgeStyling.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/ide-laf-bridge-styling/intellij.platform.jewel.markdown.ideLafBridgeStyling.iml" /> <module fileurl="file://$PROJECT_DIR$/platform/jewel/markdown/int-ui-standalone-styling/intellij.platform.jewel.markdown.intUiStandaloneStyling.iml" filepath="$PROJECT_DIR$/platform/jewel/markdown/int-ui-standalone-styling/intellij.platform.jewel.markdown.intUiStandaloneStyling.iml" /> @@ -1351,4 +1352,4 @@ <module fileurl="file://$PROJECT_DIR$/plugins/kotlin/util/test-generator-fir/kotlin.util.test-generator-fir.iml" filepath="$PROJECT_DIR$/plugins/kotlin/util/test-generator-fir/kotlin.util.test-generator-fir.iml" /> </modules> </component> -</project> \ No newline at end of file +</project> diff --git a/platform/jewel/art/docs/custom-chrome.png b/platform/jewel/art/docs/custom-chrome.png index a8a331a18fe9e..92274515e5d03 100644 Binary files a/platform/jewel/art/docs/custom-chrome.png and b/platform/jewel/art/docs/custom-chrome.png differ diff --git a/platform/jewel/art/docs/gfm-table-dark.png b/platform/jewel/art/docs/gfm-table-dark.png new file mode 100644 index 0000000000000..64fe6bd200a09 Binary files /dev/null and b/platform/jewel/art/docs/gfm-table-dark.png differ diff --git a/platform/jewel/art/docs/gfm-table-light.png b/platform/jewel/art/docs/gfm-table-light.png new file mode 100644 index 0000000000000..23c914fe7f6a5 Binary files /dev/null and b/platform/jewel/art/docs/gfm-table-light.png differ diff --git a/platform/jewel/art/docs/markdown-renderer.png b/platform/jewel/art/docs/markdown-renderer.png new file mode 100644 index 0000000000000..e56d4765a56ef Binary files /dev/null and b/platform/jewel/art/docs/markdown-renderer.png differ diff --git a/platform/jewel/art/docs/merge-dialog.png b/platform/jewel/art/docs/merge-dialog.png index d2bc1749ee1df..3ddcbefbd27c3 100644 Binary files a/platform/jewel/art/docs/merge-dialog.png and b/platform/jewel/art/docs/merge-dialog.png differ diff --git a/platform/jewel/foundation/api/foundation.api b/platform/jewel/foundation/api/foundation.api index 6a5bd61e71c24..059fa5bf37656 100644 --- a/platform/jewel/foundation/api/foundation.api +++ b/platform/jewel/foundation/api/foundation.api @@ -241,7 +241,7 @@ public final class org/jetbrains/jewel/foundation/code/MimeTypeKt { } public abstract interface class org/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter { - public abstract fun highlight-C7ITchA (Ljava/lang/String;Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; + public abstract fun highlight-zTGadEY (Ljava/lang/String;Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; } public final class org/jetbrains/jewel/foundation/code/highlighting/CodeHighlighterKt { @@ -251,7 +251,11 @@ public final class org/jetbrains/jewel/foundation/code/highlighting/CodeHighligh public final class org/jetbrains/jewel/foundation/code/highlighting/NoOpCodeHighlighter : org/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter { public static final field $stable I public static final field INSTANCE Lorg/jetbrains/jewel/foundation/code/highlighting/NoOpCodeHighlighter; - public fun highlight-C7ITchA (Ljava/lang/String;Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; + public fun highlight-zTGadEY (Ljava/lang/String;Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; +} + +public final class org/jetbrains/jewel/foundation/layout/BasicTableLayoutKt { + public static final fun BasicTableLayout-yE4rkUQ (IIJLandroidx/compose/ui/Modifier;FLjava/util/List;Landroidx/compose/runtime/Composer;II)V } public class org/jetbrains/jewel/foundation/lazy/DefaultMacOsSelectableColumnKeybindings : org/jetbrains/jewel/foundation/lazy/DefaultSelectableColumnKeybindings { diff --git a/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter.kt b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter.kt index aa638f587295d..15c37fa4b6f1a 100644 --- a/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter.kt +++ b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/code/highlighting/CodeHighlighter.kt @@ -24,7 +24,7 @@ public interface CodeHighlighter { * * @see [NoOpCodeHighlighter] */ - public fun highlight(code: String, mimeType: MimeType): Flow<AnnotatedString> + public fun highlight(code: String, mimeType: MimeType?): Flow<AnnotatedString> } public val LocalCodeHighlighter: ProvidableCompositionLocal<CodeHighlighter> = staticCompositionLocalOf { diff --git a/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/code/highlighting/NoOpCodeHighlighter.kt b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/code/highlighting/NoOpCodeHighlighter.kt index b99d90f800a78..1b21b1c68e70e 100644 --- a/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/code/highlighting/NoOpCodeHighlighter.kt +++ b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/code/highlighting/NoOpCodeHighlighter.kt @@ -6,5 +6,5 @@ import kotlinx.coroutines.flow.flowOf import org.jetbrains.jewel.foundation.code.MimeType public object NoOpCodeHighlighter : CodeHighlighter { - override fun highlight(code: String, mimeType: MimeType): Flow<AnnotatedString> = flowOf(AnnotatedString(code)) + override fun highlight(code: String, mimeType: MimeType?): Flow<AnnotatedString> = flowOf(AnnotatedString(code)) } diff --git a/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/layout/BasicTableLayout.kt b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/layout/BasicTableLayout.kt new file mode 100644 index 0000000000000..6b59870ac5bb0 --- /dev/null +++ b/platform/jewel/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/layout/BasicTableLayout.kt @@ -0,0 +1,220 @@ +package org.jetbrains.jewel.foundation.layout + +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.math.max + +/** + * A simple table that sizes columns to take as much room as they need. If the horizontal space available is less than + * what the cells would take, all columns are sized proportionally to their intrinsic width so that they still can fit + * the available width. + * + * Cells **must** only contain one top-level component. If you need your cells to contain more than one, wrap your cell + * content in a [`Box`][Box], [`Column`][androidx.compose.foundation.layout.Column], + * [`Row`][androidx.compose.foundation.layout.Row], etc. + * + * Incoming height constraints are ignored. The table will always take up as much vertical room as it needs. If you want + * to constrain the table height consider wrapping it in a + * [`VerticallyScrollableContainer`][org.jetbrains.jewel.ui.component.VerticallyScrollableContainer]. + * + * @param rowCount The number of rows this table has. + * @param columnCount The number of columns this table has. + * @param cellBorderColor The color of the cell borders. Set to [Color.Unspecified] to avoid drawing the borders — in + * which case, the [cellBorderWidth] acts as padding. + * @param modifier Modifier to apply to the table. + * @param cellBorderWidth The width of the table's borders. + * @param rows The rows that make up the table. Each row is a list of composables, one per row cell. + */ +@Suppress("KDocUnresolvedReference") +@Composable +public fun BasicTableLayout( + rowCount: Int, + columnCount: Int, + cellBorderColor: Color, + modifier: Modifier = Modifier, + cellBorderWidth: Dp = 1.dp, + rows: List<List<@Composable () -> Unit>>, +) { + var rowHeights by remember { mutableStateOf(emptyList<Int>()) } + var columnWidths by remember { mutableStateOf(emptyList<Int>()) } + + Layout( + modifier = + modifier.thenIf(rowHeights.size == rowCount && columnWidths.size == columnCount) { + drawTableBorders(cellBorderColor, cellBorderWidth, rowHeights, columnWidths) + }, + content = { rows.forEach { row -> row.forEach { cell -> cell() } } }, + measurePolicy = { measurables, incomingConstraints -> + require(rows.size == rowCount) { "Found ${rows.size} rows, but expected $rowCount." } + require(measurables.size == rowCount * columnCount) { + "Found ${measurables.size} cells, but expected ${rowCount * columnCount}." + } + + val intrinsicColumnWidths = IntArray(columnCount) + val measurablesByRow = measurables.chunked(columnCount) + for ((rowIndex, rowCells) in rows.withIndex()) { + require(rowCells.size == columnCount) { + "Row $rowIndex contains ${rowCells.size} cells, but it should have $columnCount cells." + } + + for ((columnIndex, cell) in rowCells.withIndex()) { + // Measure each cell individually + val measurable = measurablesByRow[rowIndex][columnIndex] + + // Store the intrinsic width for each column, assuming we have infinite + // vertical space available to display each cell (which we do) + val intrinsicCellWidth = measurable.maxIntrinsicWidth(height = Int.MAX_VALUE) + intrinsicColumnWidths[columnIndex] = + max(intrinsicColumnWidths[columnIndex].or(0), intrinsicCellWidth) + } + } + + // The available width we can assign to cells is equal to the max width from the + // incoming + // constraints, minus the vertical borders applied between columns and to the sides of + // the + // table + val cellBorderWidthPx = cellBorderWidth.roundToPx() + val totalHorizontalBordersWidth = cellBorderWidthPx * (columnCount + 1) + val minTableIntrinsicWidth = intrinsicColumnWidths.sum() + totalHorizontalBordersWidth + val availableWidth = incomingConstraints.maxWidth + + // We want to size the columns as a ratio of their intrinsic size to the available width + // if there is not enough room to show them all, or as their intrinsic width if they all + // fit + var tableWidth = 0 + + if (minTableIntrinsicWidth <= availableWidth) { + // We have enough room for all columns, use intrinsic column sizes + tableWidth = minTableIntrinsicWidth + } else { + // We can't fit all columns in the available width; set their size proportionally + // to the intrinsic width, so they all fit within the available horizontal space + val scaleRatio = availableWidth.toFloat() / minTableIntrinsicWidth + for (i in 0 until columnCount) { + // By truncating the decimal side, we may end up a few pixels short than the + // available width, but at least we're never exceeding it. + intrinsicColumnWidths[i] = (intrinsicColumnWidths[i] * scaleRatio).toInt() + tableWidth += intrinsicColumnWidths[i] + } + tableWidth += totalHorizontalBordersWidth + } + columnWidths = intrinsicColumnWidths.toList() + + // The height of each row is the maximum intrinsic height of their cells, calculated + // from the (possibly scaled) intrinsic column widths we just computed + val intrinsicRowHeights = IntArray(rowCount) + var tableHeight = 0 + measurablesByRow.mapIndexed { rowIndex, rowMeasurables -> + var maxCellHeight = 0 + for ((columnIndex, cellMeasurable) in rowMeasurables.withIndex()) { + val columnWidth = columnWidths[columnIndex] + val cellHeight = cellMeasurable.maxIntrinsicHeight(width = columnWidth) + maxCellHeight = max(maxCellHeight, cellHeight) + } + + tableHeight += maxCellHeight + intrinsicRowHeights[rowIndex] = maxCellHeight + } + rowHeights = intrinsicRowHeights.toList() + + // Add the horizontal borders drawn between rows and on top and bottom of the table + tableHeight += cellBorderWidthPx * (rowCount + 1) + + // Measure all cells, using the fixed constraints we calculated for each row and column + val placeables = + measurables.chunked(columnCount).mapIndexed { rowIndex, cellMeasurables -> + cellMeasurables.mapIndexed { columnIndex, cellMeasurable -> + val cellConstraints = Constraints.fixed(columnWidths[columnIndex], rowHeights[rowIndex]) + cellMeasurable.measure(cellConstraints) + } + } + + layout(tableWidth, tableHeight) { + // Place cells. We start by leaving space for the top and start-side borders + var y = cellBorderWidthPx + + placeables.forEachIndexed { rowIndex, cellPlaceables -> + var x = cellBorderWidthPx + + var rowHeight = 0 + cellPlaceables.forEach { cellPlaceable -> + cellPlaceable.placeRelative(x, y) + x += cellBorderWidthPx + x += cellPlaceable.width + rowHeight = cellPlaceable.height.coerceAtLeast(rowHeight) + } + + y += cellBorderWidthPx + y += rowHeight + } + } + }, + ) +} + +private fun Modifier.drawTableBorders( + cellBorderColor: Color, + cellBorderWidth: Dp, + rowHeights: List<Int>, + columnWidths: List<Int>, +) = drawBehind { + val borderWidthPx = cellBorderWidth.toPx() + val halfBorderWidthPx = borderWidthPx / 2f + + // First, draw the outer border + drawRect( + color = cellBorderColor, + topLeft = Offset(halfBorderWidthPx, halfBorderWidthPx), + size = Size(size.width - borderWidthPx, size.height - borderWidthPx), + style = Stroke(width = borderWidthPx), + ) + + // Then, draw all horizontal borders below rows. + // No need to draw the last horizontal border as it's covered by the border rect + var y = halfBorderWidthPx + val endX = size.width - borderWidthPx + + for (i in 0 until rowHeights.lastIndex) { + y += rowHeights[i].toFloat() + borderWidthPx + drawLine( + color = cellBorderColor, + start = Offset(halfBorderWidthPx, y), + end = Offset(endX, y), + strokeWidth = borderWidthPx, + ) + } + + // Lastly, draw all vertical borders to the end of columns + // (minus the last one, as before) + var x = halfBorderWidthPx + val endY = size.height - borderWidthPx + + for (i in 0 until columnWidths.lastIndex) { + x += columnWidths[i].toFloat() + borderWidthPx + drawLine( + color = cellBorderColor, + start = Offset(x, halfBorderWidthPx), + end = Offset(x, endY), + strokeWidth = borderWidthPx, + ) + } +} + +// TODO remove this once thenIf is moved to foundation +private inline fun Modifier.thenIf(precondition: Boolean, action: Modifier.() -> Modifier): Modifier = + if (precondition) action() else this diff --git a/platform/jewel/gradle/libs.versions.toml b/platform/jewel/gradle/libs.versions.toml index 8787ebe45de52..921fd3a896df8 100644 --- a/platform/jewel/gradle/libs.versions.toml +++ b/platform/jewel/gradle/libs.versions.toml @@ -19,6 +19,7 @@ poko = "0.17.1" [libraries] commonmark-core = { module = "org.commonmark:commonmark", version.ref = "commonmark" } commonmark-ext-autolink = { module = "org.commonmark:commonmark-ext-autolink", version.ref = "commonmark" } +commonmark-ext-gfm-tables = { module = "org.commonmark:commonmark-ext-gfm-tables", version.ref = "commonmark" } filePicker = { module = "com.darkrockstudios:mpfilepicker", version = "3.1.0" } diff --git a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/code/highlighting/LexerBasedCodeHighlighter.kt b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/code/highlighting/LexerBasedCodeHighlighter.kt index 4706c3cfad4b7..506e7466c0e5b 100644 --- a/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/code/highlighting/LexerBasedCodeHighlighter.kt +++ b/platform/jewel/ide-laf-bridge/src/main/kotlin/org/jetbrains/jewel/bridge/code/highlighting/LexerBasedCodeHighlighter.kt @@ -34,8 +34,8 @@ internal class LexerBasedCodeHighlighter( private val reHighlightingRequests: Flow<Unit>, private val highlightDispatcher: CoroutineDispatcher = Dispatchers.Default, ) : CodeHighlighter { - override fun highlight(code: String, mimeType: MimeType): Flow<AnnotatedString> { - val language = mimeType.toLanguageOrNull() ?: return flowOf(AnnotatedString(code)) + override fun highlight(code: String, mimeType: MimeType?): Flow<AnnotatedString> { + val language = mimeType?.toLanguageOrNull() ?: return flowOf(AnnotatedString(code)) val fileExtension = language.associatedFileType?.defaultExtension ?: return flowOf(AnnotatedString(code)) val virtualFile = LightVirtualFile("markdown_code_block_${code.hashCode()}.$fileExtension", language, code) val colorScheme = EditorColorsManager.getInstance().globalScheme diff --git a/platform/jewel/int-ui/int-ui-standalone/api/int-ui-standalone.api b/platform/jewel/int-ui/int-ui-standalone/api/int-ui-standalone.api index b3a68979a38c5..633afb0644365 100644 --- a/platform/jewel/int-ui/int-ui-standalone/api/int-ui-standalone.api +++ b/platform/jewel/int-ui/int-ui-standalone/api/int-ui-standalone.api @@ -603,14 +603,22 @@ public final class org/jetbrains/jewel/intui/standalone/styling/IntUiWarningBann } public final class org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalColorsKt { - public static final fun dark-GyCwops (Lorg/jetbrains/jewel/foundation/BorderColors$Companion;JJJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/foundation/BorderColors; - public static final fun dark-Hformbs (Lorg/jetbrains/jewel/foundation/OutlineColors$Companion;JJJJJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/foundation/OutlineColors; - public static final fun dark-Hformbs (Lorg/jetbrains/jewel/foundation/TextColors$Companion;JJJJJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/foundation/TextColors; - public static final fun dark-yrwZFoE (Lorg/jetbrains/jewel/foundation/GlobalColors$Companion;Lorg/jetbrains/jewel/foundation/BorderColors;Lorg/jetbrains/jewel/foundation/OutlineColors;Lorg/jetbrains/jewel/foundation/TextColors;JLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/foundation/GlobalColors; - public static final fun light-GyCwops (Lorg/jetbrains/jewel/foundation/BorderColors$Companion;JJJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/foundation/BorderColors; - public static final fun light-Hformbs (Lorg/jetbrains/jewel/foundation/OutlineColors$Companion;JJJJJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/foundation/OutlineColors; - public static final fun light-Hformbs (Lorg/jetbrains/jewel/foundation/TextColors$Companion;JJJJJLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/foundation/TextColors; - public static final fun light-yrwZFoE (Lorg/jetbrains/jewel/foundation/GlobalColors$Companion;Lorg/jetbrains/jewel/foundation/BorderColors;Lorg/jetbrains/jewel/foundation/OutlineColors;Lorg/jetbrains/jewel/foundation/TextColors;JLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/foundation/GlobalColors; + public static final fun dark-jdqzblg (Lorg/jetbrains/jewel/foundation/OutlineColors$Companion;JJJJJ)Lorg/jetbrains/jewel/foundation/OutlineColors; + public static final fun dark-jdqzblg (Lorg/jetbrains/jewel/foundation/TextColors$Companion;JJJJJ)Lorg/jetbrains/jewel/foundation/TextColors; + public static synthetic fun dark-jdqzblg$default (Lorg/jetbrains/jewel/foundation/OutlineColors$Companion;JJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/foundation/OutlineColors; + public static synthetic fun dark-jdqzblg$default (Lorg/jetbrains/jewel/foundation/TextColors$Companion;JJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/foundation/TextColors; + public static final fun dark-xwkQ0AY (Lorg/jetbrains/jewel/foundation/GlobalColors$Companion;Lorg/jetbrains/jewel/foundation/BorderColors;Lorg/jetbrains/jewel/foundation/OutlineColors;Lorg/jetbrains/jewel/foundation/TextColors;J)Lorg/jetbrains/jewel/foundation/GlobalColors; + public static synthetic fun dark-xwkQ0AY$default (Lorg/jetbrains/jewel/foundation/GlobalColors$Companion;Lorg/jetbrains/jewel/foundation/BorderColors;Lorg/jetbrains/jewel/foundation/OutlineColors;Lorg/jetbrains/jewel/foundation/TextColors;JILjava/lang/Object;)Lorg/jetbrains/jewel/foundation/GlobalColors; + public static final fun dark-zSO0fhY (Lorg/jetbrains/jewel/foundation/BorderColors$Companion;JJJ)Lorg/jetbrains/jewel/foundation/BorderColors; + public static synthetic fun dark-zSO0fhY$default (Lorg/jetbrains/jewel/foundation/BorderColors$Companion;JJJILjava/lang/Object;)Lorg/jetbrains/jewel/foundation/BorderColors; + public static final fun light-jdqzblg (Lorg/jetbrains/jewel/foundation/OutlineColors$Companion;JJJJJ)Lorg/jetbrains/jewel/foundation/OutlineColors; + public static final fun light-jdqzblg (Lorg/jetbrains/jewel/foundation/TextColors$Companion;JJJJJ)Lorg/jetbrains/jewel/foundation/TextColors; + public static synthetic fun light-jdqzblg$default (Lorg/jetbrains/jewel/foundation/OutlineColors$Companion;JJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/foundation/OutlineColors; + public static synthetic fun light-jdqzblg$default (Lorg/jetbrains/jewel/foundation/TextColors$Companion;JJJJJILjava/lang/Object;)Lorg/jetbrains/jewel/foundation/TextColors; + public static final fun light-xwkQ0AY (Lorg/jetbrains/jewel/foundation/GlobalColors$Companion;Lorg/jetbrains/jewel/foundation/BorderColors;Lorg/jetbrains/jewel/foundation/OutlineColors;Lorg/jetbrains/jewel/foundation/TextColors;J)Lorg/jetbrains/jewel/foundation/GlobalColors; + public static synthetic fun light-xwkQ0AY$default (Lorg/jetbrains/jewel/foundation/GlobalColors$Companion;Lorg/jetbrains/jewel/foundation/BorderColors;Lorg/jetbrains/jewel/foundation/OutlineColors;Lorg/jetbrains/jewel/foundation/TextColors;JILjava/lang/Object;)Lorg/jetbrains/jewel/foundation/GlobalColors; + public static final fun light-zSO0fhY (Lorg/jetbrains/jewel/foundation/BorderColors$Companion;JJJ)Lorg/jetbrains/jewel/foundation/BorderColors; + public static synthetic fun light-zSO0fhY$default (Lorg/jetbrains/jewel/foundation/BorderColors$Companion;JJJILjava/lang/Object;)Lorg/jetbrains/jewel/foundation/BorderColors; } public final class org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalMetricsKt { diff --git a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalColors.kt b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalColors.kt index a6461a75ad75b..480e2910c45c9 100644 --- a/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalColors.kt +++ b/platform/jewel/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalColors.kt @@ -1,6 +1,5 @@ package org.jetbrains.jewel.intui.standalone.theme -import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import org.jetbrains.jewel.foundation.BorderColors import org.jetbrains.jewel.foundation.GlobalColors @@ -9,7 +8,6 @@ import org.jetbrains.jewel.foundation.TextColors import org.jetbrains.jewel.intui.core.theme.IntUiDarkTheme import org.jetbrains.jewel.intui.core.theme.IntUiLightTheme -@Composable public fun GlobalColors.Companion.light( borders: BorderColors = BorderColors.light(), outlines: OutlineColors = OutlineColors.light(), @@ -17,7 +15,6 @@ public fun GlobalColors.Companion.light( paneBackground: Color = IntUiLightTheme.colors.gray(13), ): GlobalColors = GlobalColors(borders = borders, outlines = outlines, text = text, panelBackground = paneBackground) -@Composable public fun GlobalColors.Companion.dark( borders: BorderColors = BorderColors.dark(), outlines: OutlineColors = OutlineColors.dark(), @@ -25,21 +22,18 @@ public fun GlobalColors.Companion.dark( paneBackground: Color = IntUiDarkTheme.colors.gray(2), ): GlobalColors = GlobalColors(borders = borders, outlines = outlines, text = text, panelBackground = paneBackground) -@Composable public fun BorderColors.Companion.light( normal: Color = IntUiLightTheme.colors.gray(12), focused: Color = IntUiLightTheme.colors.gray(14), disabled: Color = IntUiLightTheme.colors.gray(11), ): BorderColors = BorderColors(normal, focused, disabled) -@Composable public fun BorderColors.Companion.dark( normal: Color = IntUiDarkTheme.colors.gray(1), focused: Color = IntUiDarkTheme.colors.gray(2), disabled: Color = IntUiDarkTheme.colors.gray(4), ): BorderColors = BorderColors(normal, focused, disabled) -@Composable public fun TextColors.Companion.light( normal: Color = IntUiLightTheme.colors.gray(1), selected: Color = IntUiLightTheme.colors.gray(1), @@ -48,7 +42,6 @@ public fun TextColors.Companion.light( error: Color = IntUiLightTheme.colors.red(4), ): TextColors = TextColors(normal, selected, disabled, info, error) -@Composable public fun TextColors.Companion.dark( normal: Color = IntUiDarkTheme.colors.gray(12), selected: Color = IntUiDarkTheme.colors.gray(12), @@ -57,7 +50,6 @@ public fun TextColors.Companion.dark( error: Color = IntUiDarkTheme.colors.red(7), ): TextColors = TextColors(normal, selected, disabled, info, error) -@Composable public fun OutlineColors.Companion.light( focused: Color = IntUiLightTheme.colors.blue(4), focusedWarning: Color = IntUiLightTheme.colors.yellow(4), @@ -66,7 +58,6 @@ public fun OutlineColors.Companion.light( error: Color = IntUiLightTheme.colors.red(9), ): OutlineColors = OutlineColors(focused, focusedWarning, focusedError, warning, error) -@Composable public fun OutlineColors.Companion.dark( focused: Color = IntUiDarkTheme.colors.blue(6), focusedWarning: Color = IntUiDarkTheme.colors.yellow(4), diff --git a/platform/jewel/markdown/README.md b/platform/jewel/markdown/README.md index c41644a86d724..954cee544b720 100644 --- a/platform/jewel/markdown/README.md +++ b/platform/jewel/markdown/README.md @@ -12,12 +12,13 @@ Additional supported Markdown, via extensions: * Alerts ([GitHub Flavored Markdown][alerts-specs]) — see [`extension-gfm-alerts`](extension/gfm-alerts) * Autolink (standard CommonMark, but provided as extension) — see [`extension-autolink`](extension/autolink) +* Tables ([GitHub Flavored Markdown](https://github.github.com/gfm/#tables-extension-)) — see [ + `extension-gfm-tables`](extension/gfm-tables) [alerts-specs]: https://github.com/orgs/community/discussions/16925 On the roadmap, but not currently supported — in no particular order: -* Tables ([GitHub Flavored Markdown](https://github.github.com/gfm/#tables-extension-)) * Strikethrough ([GitHub Flavored Markdown](https://github.github.com/gfm/#strikethrough-extension-)) * Image loading (via [Coil 3](https://coil-kt.github.io/coil/upgrading_to_coil3/)) * Task list items ([GitHub Flavored Markdown](https://github.github.com/gfm/#task-list-items-extension-)) @@ -119,7 +120,7 @@ those. By default, the processor will ignore any kind of Markdown it doesn't support. To support additional features, such as ones found in GitHub Flavored Markdown, you can use extensions. If you don't specify any extension, the processor will be restricted to the [CommonMark specs](https://specs.commonmark.org) as supported by -[`commonmark-java`](https://github.com/commonmark/commonmark-java). +[`commonmark-java`](https://github.com/commonmark/commonmark-java). > [!NOTE] > Images are not supported yet, even if they are part of the CommonMark specs. @@ -136,7 +137,7 @@ val processor = MarkdownProcessor(parsingExtensions) // Where the rendering happens... val blockRenderer = remember(markdownStyling, isDark) { val rendererExtensions = listOf<MarkdownRendererExtension>(/*...*/) - + if (isDark) { MarkdownBlockRenderer.dark(rendererExtensions) } else { @@ -156,5 +157,6 @@ own inline renderer, this is something to be careful about. You can see this in action running the Standalone sample, and selecting Markdown from the top-left menu. -The following image shows Jewel Markdown rendering this very Jewel Markdown README. - +The following image shows the Jewel Markdown renderer displaying the Jewel readme. + + diff --git a/platform/jewel/markdown/core/api/core.api b/platform/jewel/markdown/core/api/core.api index 0c38528e9c194..cc78884d4b6f6 100644 --- a/platform/jewel/markdown/core/api/core.api +++ b/platform/jewel/markdown/core/api/core.api @@ -321,10 +321,16 @@ public final class org/jetbrains/jewel/markdown/processing/MarkdownProcessor { public fun <init> ()V public fun <init> (Ljava/util/List;Lorg/jetbrains/jewel/markdown/MarkdownMode;Lorg/commonmark/parser/Parser;)V public synthetic fun <init> (Ljava/util/List;Lorg/jetbrains/jewel/markdown/MarkdownMode;Lorg/commonmark/parser/Parser;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getExtensions ()Ljava/util/List; public final fun processChildren (Lorg/commonmark/node/Node;)Ljava/util/List; public final fun processMarkdownDocument (Ljava/lang/String;)Ljava/util/List; } +public final class org/jetbrains/jewel/markdown/processing/ProcessingUtilKt { + public static final fun readInlineContent (Lorg/commonmark/node/Node;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Ljava/util/List; + public static final fun toInlineMarkdownOrNull (Lorg/commonmark/node/Node;Lorg/jetbrains/jewel/markdown/processing/MarkdownProcessor;)Lorg/jetbrains/jewel/markdown/InlineMarkdown; +} + public class org/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer : org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer { public static final field $stable I public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/DefaultInlineMarkdownRenderer$Companion; @@ -340,6 +346,11 @@ public class org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer public fun <init> (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;)V public synthetic fun <init> (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V protected final fun MaybeScrollingContainer (ZLandroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V + public fun createCopy (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer; + public fun getInlineRenderer ()Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer; + public fun getRendererExtensions ()Ljava/util/List; + public fun getRootStyling ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling; + public fun plus (Lorg/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer; public fun render (Ljava/util/List;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$BlockQuote;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;Landroidx/compose/runtime/Composer;I)V @@ -354,8 +365,8 @@ public class org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$ListItem;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Paragraph;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V - public fun render-EWr_ITI (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock;Ljava/lang/String;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;Landroidx/compose/runtime/Composer;I)V public fun renderThematicBreak (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$ThematicBreak;Landroidx/compose/runtime/Composer;I)V + public fun renderWithMimeType-EWr_ITI (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock;Ljava/lang/String;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;Landroidx/compose/runtime/Composer;I)V } public abstract interface class org/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer { @@ -393,6 +404,11 @@ public final class org/jetbrains/jewel/markdown/rendering/InlinesStyling$Compani public abstract interface class org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer { public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer$Companion; + public abstract fun createCopy (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer; + public abstract fun getInlineRenderer ()Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer; + public abstract fun getRendererExtensions ()Ljava/util/List; + public abstract fun getRootStyling ()Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling; + public abstract fun plus (Lorg/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer; public abstract fun render (Ljava/util/List;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$BlockQuote;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$BlockQuote;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public abstract fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;Landroidx/compose/runtime/Composer;I)V @@ -413,6 +429,10 @@ public abstract interface class org/jetbrains/jewel/markdown/rendering/MarkdownB public final class org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer$Companion { } +public final class org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer$DefaultImpls { + public static synthetic fun createCopy$default (Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Ljava/util/List;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer; +} + public final class org/jetbrains/jewel/markdown/rendering/MarkdownStyling { public static final field $stable I public static final field Companion Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Companion; @@ -783,7 +803,7 @@ public class org/jetbrains/jewel/markdown/scrolling/ScrollSyncMarkdownBlockRende public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$IndentedCodeBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Indented;Landroidx/compose/runtime/Composer;I)V public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Heading;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Heading$HN;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$Paragraph;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Paragraph;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V - public fun render-EWr_ITI (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock;Ljava/lang/String;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;Landroidx/compose/runtime/Composer;I)V + public fun renderWithMimeType-EWr_ITI (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CodeBlock$FencedCodeBlock;Ljava/lang/String;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling$Code$Fenced;Landroidx/compose/runtime/Composer;I)V } public abstract class org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizer { diff --git a/platform/jewel/markdown/core/build.gradle.kts b/platform/jewel/markdown/core/build.gradle.kts index 135428bcd81ad..19e3dfb770d5b 100644 --- a/platform/jewel/markdown/core/build.gradle.kts +++ b/platform/jewel/markdown/core/build.gradle.kts @@ -16,7 +16,6 @@ dependencies { } publicApiValidation { - // TODO Oleg remove this once migrated to value classes excludedClassRegexes = setOf("org.jetbrains.jewel.markdown.MarkdownBlock.*") } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt index ea1796f76fc2f..d0f19f7df8686 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/MarkdownProcessor.kt @@ -56,7 +56,7 @@ import org.jetbrains.jewel.markdown.scrolling.ScrollingSynchronizer */ @ExperimentalJewelApi public class MarkdownProcessor( - private val extensions: List<MarkdownProcessorExtension> = emptyList(), + public val extensions: List<MarkdownProcessorExtension> = emptyList(), private val markdownMode: MarkdownMode = MarkdownMode.Standalone, private val commonMarkParser: Parser = MarkdownParserFactory.create(markdownMode is MarkdownMode.EditorPreview, extensions), @@ -267,14 +267,14 @@ public class MarkdownProcessor( } private fun Paragraph.toMarkdownParagraph(): MarkdownBlock.Paragraph = - MarkdownBlock.Paragraph(readInlineContent().toList()) + MarkdownBlock.Paragraph(readInlineContent(this@MarkdownProcessor).toList()) private fun BlockQuote.toMarkdownBlockQuote(): MarkdownBlock.BlockQuote = MarkdownBlock.BlockQuote(processChildren(this)) private fun Heading.toMarkdownHeadingOrNull(): MarkdownBlock.Heading? { if (level < 1 || level > 6) return null - return MarkdownBlock.Heading(inlineContent = readInlineContent().toList(), level = level) + return MarkdownBlock.Heading(inlineContent = readInlineContent(this@MarkdownProcessor).toList(), level = level) } private fun FencedCodeBlock.toMarkdownCodeBlockOrNull(): CodeBlock.FencedCodeBlock = @@ -345,7 +345,5 @@ public class MarkdownProcessor( return MarkdownBlock.HtmlBlock(literal.trimEnd('\n')) } - private fun Block.readInlineContent() = readInlineContent(this@MarkdownProcessor, extensions) - private data class State(val lines: List<String>, val blocks: List<Block>, val indexes: List<Pair<Int, Int>>) } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt index b0f65dcfb9509..39c05112dad9f 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt @@ -11,53 +11,58 @@ import org.commonmark.node.Node import org.commonmark.node.SoftLineBreak as CMSoftLineBreak import org.commonmark.node.StrongEmphasis as CMStrongEmphasis import org.commonmark.node.Text as CMText -import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.jewel.foundation.ExperimentalJewelApi import org.jetbrains.jewel.markdown.InlineMarkdown import org.jetbrains.jewel.markdown.WithInlineMarkdown import org.jetbrains.jewel.markdown.WithTextContent -import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension -@VisibleForTesting -internal fun Node.readInlineContent( - markdownProcessor: MarkdownProcessor, - extensions: List<MarkdownProcessorExtension>, -): List<InlineMarkdown> = buildList { +/** + * Reads the contents of this [Node] as a list of [InlineMarkdown] nodes. + * + * @param markdownProcessor Used to parse the inline contents as needed. + * @return A list of the contents as parsed [InlineMarkdown]. + * @see toInlineMarkdownOrNull + */ +@ExperimentalJewelApi +public fun Node.readInlineContent(markdownProcessor: MarkdownProcessor): List<InlineMarkdown> = buildList { var current = this@readInlineContent.firstChild while (current != null) { - val inline = current.toInlineMarkdownOrNull(markdownProcessor, extensions) + val inline = current.toInlineMarkdownOrNull(markdownProcessor) if (inline != null) add(inline) current = current.next } } -@VisibleForTesting -internal fun Node.toInlineMarkdownOrNull( - markdownProcessor: MarkdownProcessor, - extensions: List<MarkdownProcessorExtension>, -) = +/** + * Tries parsing a CommonMark [Node] to an [InlineMarkdown] node. + * + * @param markdownProcessor Used to parse the contents of this node, as needed. + * @return The parsed [InlineMarkdown], or null if it is a custom node that can't be parsed by any of the + * [`MarkdownInlineProcessorExtension`][org.jetbrains.jewel.markdown.extensions.MarkdownInlineProcessorExtension]s + * registered to [markdownProcessor]. + * @see org.jetbrains.jewel.markdown.extensions.MarkdownInlineProcessorExtension + * @see readInlineContent + */ +public fun Node.toInlineMarkdownOrNull(markdownProcessor: MarkdownProcessor): InlineMarkdown? = when (this) { is CMText -> InlineMarkdown.Text(literal) is CMLink -> InlineMarkdown.Link( destination = destination, title = title, - inlineContent = readInlineContent(markdownProcessor, extensions), + inlineContent = readInlineContent(markdownProcessor), ) is CMEmphasis -> - InlineMarkdown.Emphasis( - delimiter = openingDelimiter, - inlineContent = readInlineContent(markdownProcessor, extensions), - ) + InlineMarkdown.Emphasis(delimiter = openingDelimiter, inlineContent = readInlineContent(markdownProcessor)) - is CMStrongEmphasis -> - InlineMarkdown.StrongEmphasis(openingDelimiter, readInlineContent(markdownProcessor, extensions)) + is CMStrongEmphasis -> InlineMarkdown.StrongEmphasis(openingDelimiter, readInlineContent(markdownProcessor)) is CMCode -> InlineMarkdown.Code(literal) is CMHtmlInline -> InlineMarkdown.HtmlInline(literal) is CMImage -> { - val inlineContent = readInlineContent(markdownProcessor, extensions) + val inlineContent = readInlineContent(markdownProcessor) InlineMarkdown.Image( source = destination, alt = inlineContent.renderAsSimpleText().trim(), @@ -69,7 +74,7 @@ internal fun Node.toInlineMarkdownOrNull( is CMHardLineBreak -> InlineMarkdown.HardLineBreak is CMSoftLineBreak -> InlineMarkdown.SoftLineBreak is CMCustomNode -> - extensions + markdownProcessor.extensions .find { it.inlineProcessorExtension?.canProcess(this) == true } ?.inlineProcessorExtension ?.processInlineMarkdown(this, markdownProcessor) diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt index 08ed08e3de867..a34f852a1f3d7 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt @@ -60,11 +60,15 @@ import org.jetbrains.jewel.ui.component.Divider import org.jetbrains.jewel.ui.component.HorizontallyScrollableContainer import org.jetbrains.jewel.ui.component.Text +/** + * Default implementation of [MarkdownBlockRenderer] that uses the provided styling, extensions, and inline renderer to + * render [MarkdownBlock]s into Compose UI elements. + */ @ExperimentalJewelApi public open class DefaultMarkdownBlockRenderer( - private val rootStyling: MarkdownStyling, - private val rendererExtensions: List<MarkdownRendererExtension> = emptyList(), - private val inlineRenderer: InlineMarkdownRenderer = DefaultInlineMarkdownRenderer(rendererExtensions), + override val rootStyling: MarkdownStyling, + override val rendererExtensions: List<MarkdownRendererExtension> = emptyList(), + override val inlineRenderer: InlineMarkdownRenderer = DefaultInlineMarkdownRenderer(rendererExtensions), ) : MarkdownBlockRenderer { @Composable override fun render( @@ -365,7 +369,7 @@ public open class DefaultMarkdownBlockRenderer( ) } - render(block, mimeType, styling) + renderWithMimeType(block, mimeType, styling) if (styling.infoPosition.verticalAlignment == Alignment.Bottom) { FencedBlockInfo( @@ -381,7 +385,11 @@ public open class DefaultMarkdownBlockRenderer( } @Composable - public open fun render(block: FencedCodeBlock, mimeType: MimeType, styling: MarkdownStyling.Code.Fenced) { + public open fun renderWithMimeType( + block: FencedCodeBlock, + mimeType: MimeType, + styling: MarkdownStyling.Code.Fenced, + ) { val content = block.content val highlightedCode by LocalCodeHighlighter.current.highlight(content, mimeType).collectAsState(AnnotatedString(content)) @@ -451,4 +459,19 @@ public open class DefaultMarkdownBlockRenderer( content() } } + + public override fun createCopy( + rootStyling: MarkdownStyling?, + rendererExtensions: List<MarkdownRendererExtension>?, + inlineRenderer: InlineMarkdownRenderer?, + ): MarkdownBlockRenderer = + DefaultMarkdownBlockRenderer( + rootStyling ?: this.rootStyling, + rendererExtensions ?: this.rendererExtensions, + inlineRenderer ?: this.inlineRenderer, + ) + + @ExperimentalJewelApi + override operator fun plus(extension: MarkdownRendererExtension): MarkdownBlockRenderer = + DefaultMarkdownBlockRenderer(rootStyling, rendererExtensions = rendererExtensions + extension, inlineRenderer) } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer.kt index b66ebc4979a6a..85deda09d7a79 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer.kt @@ -13,9 +13,24 @@ import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.OrderedList import org.jetbrains.jewel.markdown.MarkdownBlock.ListBlock.UnorderedList import org.jetbrains.jewel.markdown.MarkdownBlock.ListItem import org.jetbrains.jewel.markdown.MarkdownBlock.Paragraph - +import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension + +/** + * Renders one or more [MarkdownBlock]s into a Compose UI. + * + * @param rootStyling The [MarkdownStyling] to use to render the Markdown into composables. + * @param rendererExtensions The [MarkdownRendererExtension]s used to render [MarkdownBlock.CustomBlock]s. + * @param inlineRenderer The [InlineMarkdownRenderer] used to render + * [inline content][org.jetbrains.jewel.markdown.InlineMarkdown]. + * @see render + */ +@Suppress("ComposableNaming") @ExperimentalJewelApi public interface MarkdownBlockRenderer { + public val rootStyling: MarkdownStyling + public val rendererExtensions: List<MarkdownRendererExtension> + public val inlineRenderer: InlineMarkdownRenderer + @Composable public fun render( blocks: List<MarkdownBlock>, @@ -103,5 +118,17 @@ public interface MarkdownBlockRenderer { @Composable public fun render(block: HtmlBlock, styling: MarkdownStyling.HtmlBlock) + /** + * Creates a copy of this instance, using the provided non-null parameters, or the current values for the null ones. + */ + public fun createCopy( + rootStyling: MarkdownStyling? = null, + rendererExtensions: List<MarkdownRendererExtension>? = null, + inlineRenderer: InlineMarkdownRenderer? = null, + ): MarkdownBlockRenderer + + /** Creates a copy of this [MarkdownBlockRenderer] with the same properties, plus the provided [extension]. */ + @ExperimentalJewelApi public operator fun plus(extension: MarkdownRendererExtension): MarkdownBlockRenderer + public companion object } diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollSyncMarkdownBlockRenderer.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollSyncMarkdownBlockRenderer.kt index a1dc3ce32ff77..18717c8151431 100644 --- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollSyncMarkdownBlockRenderer.kt +++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollSyncMarkdownBlockRenderer.kt @@ -74,11 +74,11 @@ public open class ScrollSyncMarkdownBlockRenderer( } @Composable - override fun render(block: FencedCodeBlock, mimeType: MimeType, styling: MarkdownStyling.Code.Fenced) { + override fun renderWithMimeType(block: FencedCodeBlock, mimeType: MimeType, styling: MarkdownStyling.Code.Fenced) { val synchronizer = (JewelTheme.markdownMode as? MarkdownMode.EditorPreview)?.scrollingSynchronizer ?: run { - super.render(block, mimeType, styling) + super.renderWithMimeType(block, mimeType, styling) return } diff --git a/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt index 05f6a8c683cbc..94d1eb08a199f 100644 --- a/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt +++ b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import java.util.Arrays import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -59,7 +60,6 @@ import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test -import java.util.Arrays @Suppress("LargeClass") class ScrollingSynchronizerTest { diff --git a/platform/jewel/markdown/extension/gfm-alerts/build.gradle.kts b/platform/jewel/markdown/extension/gfm-alerts/build.gradle.kts index 918e727a6e249..802a08600d14c 100644 --- a/platform/jewel/markdown/extension/gfm-alerts/build.gradle.kts +++ b/platform/jewel/markdown/extension/gfm-alerts/build.gradle.kts @@ -13,7 +13,6 @@ dependencies { } publicApiValidation { - // TODO Oleg remove this once migrated to value classes excludedClassRegexes = setOf("org.jetbrains.jewel.markdown.extensions.github.alerts.*") } diff --git a/platform/jewel/markdown/extension/gfm-tables/README.md b/platform/jewel/markdown/extension/gfm-tables/README.md new file mode 100644 index 0000000000000..8c6bc26f05aae --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-tables/README.md @@ -0,0 +1,43 @@ +# GitHub Flavored Markdown — tables extension + +This extension adds support for tables, a [GFM extension](https://github.github.com/gfm/#tables-extension-) over +CommonMark. Tables are parsed by the `commonmark-ext-gfm-tables` library, and rendered using a themed +[`BasicTableLayout`](../../../foundation/src/main/kotlin/org/jetbrains/jewel/foundation/layout/BasicTableLayout.kt). + +The default table styling matches the GitHub styling: + +| Light theme | Dark theme | +|-------------------------------------------------------------------------|-----------------------------------------------------------------------| +|  |  | + +## Usage + +To use the tables extension, you need to add the `GitHubTableProcessorExtension` to your `MarkdownProcessor`, and the +`GitHubTableRendererExtension` to the `MarkdownBlockRenderer`. For example, in standalone mode: + +```kotlin +val isDark = JewelTheme.isDark + +val markdownStyling = remember(isDark) { if (isDark) MarkdownStyling.dark() else MarkdownStyling.light() } + +val processor = remember { MarkdownProcessor(listOf(GitHubTableProcessorExtension)) } + +val blockRenderer = + remember(markdownStyling) { + if (isDark) { + MarkdownBlockRenderer.dark( + styling = markdownStyling, + rendererExtensions = listOf(GitHubTableRendererExtension(GfmTableStyling.dark(), markdownStyling)), + ) + } else { + MarkdownBlockRenderer.light( + styling = markdownStyling, + rendererExtensions = listOf(GitHubTableRendererExtension(GfmTableStyling.light(), markdownStyling)), + ) + } + } + +ProvideMarkdownStyling(markdownStyling, blockRenderer, NoOpCodeHighlighter) { + // Your UI that renders Markdown goes here +} +``` diff --git a/platform/jewel/markdown/extension/gfm-tables/api/gfm-tables.api b/platform/jewel/markdown/extension/gfm-tables/api/gfm-tables.api new file mode 100644 index 0000000000000..80434a86cf946 --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-tables/api/gfm-tables.api @@ -0,0 +1,79 @@ +public final class org/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors$Companion; + public synthetic fun <init> (JJJLorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getAlternateRowBackgroundColor-0d7_KjU ()J + public final fun getBorderColor-0d7_KjU ()J + public final fun getRowBackgroundColor-0d7_KjU ()J + public final fun getRowBackgroundStyle ()Lorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors$Companion { +} + +public final class org/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics$Companion; + public synthetic fun <init> (FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Horizontal;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getBorderWidth-D9Ej5fM ()F + public final fun getCellPadding ()Landroidx/compose/foundation/layout/PaddingValues; + public final fun getDefaultCellContentAlignment ()Landroidx/compose/ui/Alignment$Horizontal; + public final fun getHeaderDefaultCellContentAlignment ()Landroidx/compose/ui/Alignment$Horizontal; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics$Companion { +} + +public final class org/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling { + public static final field $stable I + public static final field Companion Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling$Companion; + public synthetic fun <init> (JJJLorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle;FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/text/font/FontWeight;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun <init> (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics;Landroidx/compose/ui/text/font/FontWeight;)V + public fun equals (Ljava/lang/Object;)Z + public final fun getColors ()Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors; + public final fun getHeaderBaseFontWeight ()Landroidx/compose/ui/text/font/FontWeight; + public final fun getMetrics ()Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling$Companion { +} + +public final class org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRenderer : org/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension { + public static final field $stable I + public fun <init> (Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling;)V + public fun canRender (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CustomBlock;)Z + public fun render (Lorg/jetbrains/jewel/markdown/MarkdownBlock$CustomBlock;Lorg/jetbrains/jewel/markdown/rendering/MarkdownBlockRenderer;Lorg/jetbrains/jewel/markdown/rendering/InlineMarkdownRenderer;ZLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Landroidx/compose/runtime/Composer;I)V +} + +public final class org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension : org/jetbrains/jewel/markdown/extensions/MarkdownProcessorExtension { + public static final field $stable I + public static final field INSTANCE Lorg/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension; + public fun getBlockProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockProcessorExtension; + public fun getInlineProcessorExtension ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineProcessorExtension; + public fun getParserExtension ()Lorg/commonmark/parser/Parser$ParserExtension; + public fun getTextRendererExtension ()Lorg/commonmark/renderer/text/TextContentRenderer$TextContentRendererExtension; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableRendererExtension : org/jetbrains/jewel/markdown/extensions/MarkdownRendererExtension { + public static final field $stable I + public fun <init> (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling;Lorg/jetbrains/jewel/markdown/rendering/MarkdownStyling;)V + public fun getBlockRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownBlockRendererExtension; + public fun getInlineRenderer ()Lorg/jetbrains/jewel/markdown/extensions/MarkdownInlineRendererExtension; +} + +public final class org/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle : java/lang/Enum { + public static final field Normal Lorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle; + public static final field Striped Lorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle; + public static fun values ()[Lorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle; +} + diff --git a/platform/jewel/markdown/extension/gfm-tables/build.gradle.kts b/platform/jewel/markdown/extension/gfm-tables/build.gradle.kts new file mode 100644 index 0000000000000..b73c9d355ad4e --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-tables/build.gradle.kts @@ -0,0 +1,27 @@ +import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag + +plugins { + jewel + `jewel-publish` + `jewel-check-public-api` + alias(libs.plugins.composeDesktop) + alias(libs.plugins.compose.compiler) +} + +dependencies { + implementation(projects.markdown.core) + implementation(libs.commonmark.ext.gfm.tables) + + testImplementation(compose.desktop.uiTestJUnit4) +} + +publicApiValidation { + excludedClassRegexes = setOf("org.jetbrains.jewel.markdown.extensions.github.tables.*") +} + +publishing.publications.named<MavenPublication>("main") { + val ijpTarget = project.property("ijp.target") as String + artifactId = "jewel-markdown-extension-${project.name}-$ijpTarget" +} + +composeCompiler { featureFlags.add(ComposeFeatureFlag.OptimizeNonSkippingGroups) } diff --git a/platform/jewel/markdown/extension/gfm-tables/intellij.platform.jewel.markdown.extension.gfmTables.iml b/platform/jewel/markdown/extension/gfm-tables/intellij.platform.jewel.markdown.extension.gfmTables.iml new file mode 100644 index 0000000000000..185fa1181782b --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-tables/intellij.platform.jewel.markdown.extension.gfmTables.iml @@ -0,0 +1,375 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="FacetManager"> + <facet type="kotlin-language" name="Kotlin"> + <configuration version="5" platform="JVM 17" allPlatforms="JVM [17]" useProjectSettings="false"> + <compilerSettings> + <option name="additionalArguments" value="-Xjvm-default=all -opt-in=androidx.compose.ui.ExperimentalComposeUiApi -Xcontext-receivers -opt-in=androidx.compose.foundation.ExperimentalFoundationApi -opt-in=org.jetbrains.jewel.foundation.ExperimentalJewelApi -opt-in=org.jetbrains.jewel.foundation.InternalJewelApi -P plugin:poko-compiler-plugin:pokoAnnotation=org/jetbrains/jewel/foundation/GenerateDataFunctions plugin:poko:pokoAnnotation=org/jetbrains/jewel/foundation/GenerateDataFunctions" /> + </compilerSettings> + <compilerArguments> + <stringArguments> + <stringArg name="jvmTarget" arg="17" /> + <stringArg name="apiVersion" arg="2.0" /> + <stringArg name="languageVersion" arg="2.0" /> + </stringArguments> + <arrayArguments> + <arrayArg name="pluginClasspaths"> + <args> + <arg>$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-compose-compiler-plugin/2.1.0/kotlin-compose-compiler-plugin-2.1.0.jar</arg> + <arg>$MAVEN_REPOSITORY$/dev/drewhamilton/poko/poko-compiler-plugin/0.18.2/poko-compiler-plugin-0.18.2.jar</arg> + </args> + </arrayArg> + </arrayArguments> + </compilerArguments> + </configuration> + </facet> + </component> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/src/test/kotlin" isTestSource="false" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="library" name="kotlin-stdlib" level="project" /> + <orderEntry type="library" name="kotlinx-coroutines-core" level="project" /> + <orderEntry type="library" name="jetbrains-annotations" level="project" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="module" module-name="intellij.platform.jewel.markdown.core" /> + <orderEntry type="module" module-name="intellij.platform.jewel.ui" /> + <orderEntry type="module" module-name="intellij.platform.jewel.foundation" /> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui.test.junit4" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui-test-junit4:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test-junit4/1.7.1/ui-test-junit4-1.7.1.jar"> + <sha256sum>c53f675b2af4696e6022598145a44a9e9feb35216a188163ae5bf99a18e76bdb</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test-junit4/1.7.1/ui-test-junit4-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test-junit4/1.7.1/ui-test-junit4-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui.test.junit4.desktop" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui-test-junit4-desktop:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test-junit4-desktop/1.7.1/ui-test-junit4-desktop-1.7.1.jar"> + <sha256sum>f1e334b73d49ceab00afe776a4c393de77b4fc805e9ff488dac596a2663b1dc1</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test-junit4-desktop/1.7.1/ui-test-junit4-desktop-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test-junit4-desktop/1.7.1/ui-test-junit4-desktop-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.hamcrest.hamcrest.core" type="repository"> + <properties include-transitive-deps="false" maven-id="org.hamcrest:hamcrest-core:1.3"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar"> + <sha256sum>66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui.test" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui-test:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test/1.7.1/ui-test-1.7.1.jar"> + <sha256sum>a7dfdfd2b9d6668c646275948eba357aab6407f635f2dc09fe90258a2f202337</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test/1.7.1/ui-test-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test/1.7.1/ui-test-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui.test.desktop" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui-test-desktop:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test-desktop/1.7.1/ui-test-desktop-1.7.1.jar"> + <sha256sum>34dfab5b4fa8b2913f00facdddc049e2fef4b176f8e7cde0ad306485674c3b5c</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test-desktop/1.7.1/ui-test-desktop-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-test-desktop/1.7.1/ui-test-desktop-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.runtime.runtime" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.runtime:runtime:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/runtime/runtime/1.7.1/runtime-1.7.1.jar"> + <sha256sum>5c84c3bb1c1b636ae36030a0f98d4629afa29e192a9c78f57f22ce2c8ff6bc96</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/runtime/runtime/1.7.1/runtime-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/runtime/runtime/1.7.1/runtime-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui/1.7.1/ui-1.7.1.jar"> + <sha256sum>c106c0f705717889663555fe4cb7742612bd47b56a128bf7a9f589f24c64cb08</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui/1.7.1/ui-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui/1.7.1/ui-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui.text" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui-text:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-text/1.7.1/ui-text-1.7.1.jar"> + <sha256sum>8ff9a2b8c93b0287d8fa837d5b0917923168cb97dea1ada1a8d4aa7bb17c9f15</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-text/1.7.1/ui-text-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-text/1.7.1/ui-text-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui.unit" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui-unit:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-unit/1.7.1/ui-unit-1.7.1.jar"> + <sha256sum>41da5d61c56877b5b5d01e2a2bcb967cbfaf4d45c1a1aa124c0601c7a3de74e7</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-unit/1.7.1/ui-unit-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-unit/1.7.1/ui-unit-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.annotations" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains:annotations:23.0.0"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar"> + <sha256sum>7b0f19724082cbfcbc66e5abea2b9bc92cf08a1ea11e191933ed43801eb3cd05</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.desktop.desktop" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.desktop:desktop:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/desktop/desktop/1.7.1/desktop-1.7.1.jar"> + <sha256sum>bff3d1e895fd5abd54ee725dab59214acabf900e1a0784544d20cf20521bf9f3</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/desktop/desktop/1.7.1/desktop-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/desktop/desktop/1.7.1/desktop-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.desktop.desktop.jvm" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.desktop:desktop-jvm:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/desktop/desktop-jvm/1.7.1/desktop-jvm-1.7.1.jar"> + <sha256sum>62c816073195cc0119dc1d66b178544bdb772ef8bee4879954528c6798b17ebe</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/desktop/desktop-jvm/1.7.1/desktop-jvm-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/desktop/desktop-jvm/1.7.1/desktop-jvm-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.runtime.runtime" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.runtime:runtime:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/runtime/runtime/1.7.1/runtime-1.7.1.jar"> + <sha256sum>5c84c3bb1c1b636ae36030a0f98d4629afa29e192a9c78f57f22ce2c8ff6bc96</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/runtime/runtime/1.7.1/runtime-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/runtime/runtime/1.7.1/runtime-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui/1.7.1/ui-1.7.1.jar"> + <sha256sum>c106c0f705717889663555fe4cb7742612bd47b56a128bf7a9f589f24c64cb08</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui/1.7.1/ui-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui/1.7.1/ui-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui.tooling.preview" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui-tooling-preview:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-tooling-preview/1.7.1/ui-tooling-preview-1.7.1.jar"> + <sha256sum>1b409141a9ee6ed38e76a8fb8f9cf280030bf45375ba7e07e60b2742039b573e</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-tooling-preview/1.7.1/ui-tooling-preview-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-tooling-preview/1.7.1/ui-tooling-preview-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.ui.ui.tooling.preview.desktop" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui-tooling-preview-desktop:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-tooling-preview-desktop/1.7.1/ui-tooling-preview-desktop-1.7.1.jar"> + <sha256sum>c5cfad0b125e978d915f8b4c38630bc5d548e59fe8a0e838c0cc84283c605943</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-tooling-preview-desktop/1.7.1/ui-tooling-preview-desktop-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/ui/ui-tooling-preview-desktop/1.7.1/ui-tooling-preview-desktop-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module-library" scope="TEST"> + <library name="org.jetbrains.compose.runtime.runtime" type="repository"> + <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.runtime:runtime:1.7.1"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/compose/runtime/runtime/1.7.1/runtime-1.7.1.jar"> + <sha256sum>5c84c3bb1c1b636ae36030a0f98d4629afa29e192a9c78f57f22ce2c8ff6bc96</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/runtime/runtime/1.7.1/runtime-1.7.1.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/compose/runtime/runtime/1.7.1/runtime-1.7.1-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="module" module-name="intellij.libraries.junit4" scope="TEST" /> + <orderEntry type="module" module-name="intellij.libraries.compose.foundation.desktop" /> + <orderEntry type="library" scope="TEST" name="kotlinx-coroutines-test" level="project" /> + <orderEntry type="module-library"> + <library name="commonmark.ext.gfm.tables" type="repository"> + <properties maven-id="org.commonmark:commonmark-ext-gfm-tables:0.24.0"> + <verification> + <artifact url="file://$MAVEN_REPOSITORY$/org/commonmark/commonmark-ext-gfm-tables/0.24.0/commonmark-ext-gfm-tables-0.24.0.jar"> + <sha256sum>b54dc332f931e6d07c2766144c087b08f3693677e368151a67020b4e95bb4b99</sha256sum> + </artifact> + <artifact url="file://$MAVEN_REPOSITORY$/org/commonmark/commonmark/0.24.0/commonmark-0.24.0.jar"> + <sha256sum>679338e0b7fc15c02d275d598654b01a149893bc28a87992e90123c8d06af25b</sha256sum> + </artifact> + </verification> + </properties> + <CLASSES> + <root url="jar://$MAVEN_REPOSITORY$/org/commonmark/commonmark-ext-gfm-tables/0.24.0/commonmark-ext-gfm-tables-0.24.0.jar!/" /> + <root url="jar://$MAVEN_REPOSITORY$/org/commonmark/commonmark/0.24.0/commonmark-0.24.0.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MAVEN_REPOSITORY$/org/commonmark/commonmark-ext-gfm-tables/0.24.0/commonmark-ext-gfm-tables-0.24.0-sources.jar!/" /> + <root url="jar://$MAVEN_REPOSITORY$/org/commonmark/commonmark/0.24.0/commonmark-0.24.0-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + </component> +</module> \ No newline at end of file diff --git a/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRenderer.kt b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRenderer.kt new file mode 100644 index 0000000000000..a5ee4d1a204bc --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRenderer.kt @@ -0,0 +1,176 @@ +package org.jetbrains.jewel.markdown.extensions.github.tables + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.foundation.layout.BasicTableLayout +import org.jetbrains.jewel.foundation.theme.JewelTheme +import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.MarkdownBlock.CustomBlock +import org.jetbrains.jewel.markdown.extensions.MarkdownBlockRendererExtension +import org.jetbrains.jewel.markdown.rendering.InlineMarkdownRenderer +import org.jetbrains.jewel.markdown.rendering.InlinesStyling +import org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling + +@OptIn(ExperimentalJewelApi::class) +public class GitHubTableBlockRenderer( + private val rootStyling: MarkdownStyling, + private val tableStyling: GfmTableStyling, +) : MarkdownBlockRendererExtension { + override fun canRender(block: CustomBlock): Boolean = block is TableBlock + + @Composable + override fun render( + block: CustomBlock, + blockRenderer: MarkdownBlockRenderer, + inlineRenderer: InlineMarkdownRenderer, + enabled: Boolean, + onUrlClick: (String) -> Unit, + onTextClick: () -> Unit, + ) { + val tableBlock = block as TableBlock + + // Headers usually have a tweaked font weight + val headerRootStyling = + remember(JewelTheme.name, blockRenderer, tableStyling.headerBaseFontWeight) { + val rootStyling = blockRenderer.rootStyling + val semiboldInlinesStyling = + rootStyling.paragraph.inlinesStyling.withFontWeight(tableStyling.headerBaseFontWeight) + + // Given cells can only contain inlines, and not block-level nodes, we are ok with + // only overriding the Paragraph styling. + MarkdownStyling( + rootStyling.blockVerticalSpacing, + MarkdownStyling.Paragraph(semiboldInlinesStyling), + rootStyling.heading, + rootStyling.blockQuote, + rootStyling.code, + rootStyling.list, + rootStyling.image, + rootStyling.thematicBreak, + rootStyling.htmlBlock, + ) + } + + val headerRenderer = remember(headerRootStyling) { blockRenderer.createCopy(rootStyling = headerRootStyling) } + + val rows = + remember(tableBlock, blockRenderer, inlineRenderer, tableStyling) { + val headerCells = + tableBlock.header.cells.map<TableCell, @Composable () -> Unit> { cell -> + { + Cell( + cell = cell, + backgroundColor = tableStyling.colors.rowBackgroundColor, + padding = tableStyling.metrics.cellPadding, + defaultAlignment = tableStyling.metrics.headerDefaultCellContentAlignment, + blockRenderer = headerRenderer, + enabled = enabled, + paragraphStyling = headerRenderer.rootStyling.paragraph, + onUrlClick = onUrlClick, + onTextClick = onTextClick, + ) + } + } + + val rowsCells = + tableBlock.rows.map<TableRow, List<@Composable () -> Unit>> { row -> + row.cells.map<TableCell, @Composable () -> Unit> { cell -> + { + val backgroundColor = + if (tableStyling.colors.rowBackgroundStyle == RowBackgroundStyle.Striped) { + if (cell.rowIndex % 2 == 0) { + tableStyling.colors.alternateRowBackgroundColor + } else { + tableStyling.colors.rowBackgroundColor + } + } else { + tableStyling.colors.rowBackgroundColor + } + + Cell( + cell = cell, + backgroundColor = backgroundColor, + padding = tableStyling.metrics.cellPadding, + defaultAlignment = tableStyling.metrics.defaultCellContentAlignment, + blockRenderer = blockRenderer, + enabled = enabled, + paragraphStyling = rootStyling.paragraph, + onUrlClick = onUrlClick, + onTextClick = onTextClick, + ) + } + } + } + + listOf(headerCells) + rowsCells + } + + BasicTableLayout( + rowCount = tableBlock.rowCount, + columnCount = tableBlock.columnCount, + cellBorderColor = tableStyling.colors.borderColor, + cellBorderWidth = tableStyling.metrics.borderWidth, + rows = rows, + ) + } + + private fun InlinesStyling.withFontWeight(newFontWeight: FontWeight) = + InlinesStyling( + textStyle = textStyle.copy(fontWeight = newFontWeight), + inlineCode = inlineCode.copy(fontWeight = newFontWeight), + link = link.copy(fontWeight = newFontWeight), + linkDisabled = linkDisabled.copy(fontWeight = newFontWeight), + linkHovered = linkHovered.copy(fontWeight = newFontWeight), + linkFocused = linkFocused.copy(fontWeight = newFontWeight), + linkPressed = linkPressed.copy(fontWeight = newFontWeight), + linkVisited = linkVisited.copy(fontWeight = newFontWeight), + emphasis = emphasis.copy(fontWeight = newFontWeight), + strongEmphasis = strongEmphasis.copy(fontWeight = newFontWeight), + inlineHtml = inlineHtml.copy(fontWeight = newFontWeight), + renderInlineHtml = renderInlineHtml, + ) + + @Composable + private fun Cell( + cell: TableCell, + backgroundColor: Color, + padding: PaddingValues, + defaultAlignment: Alignment.Horizontal, + blockRenderer: MarkdownBlockRenderer, + enabled: Boolean, + paragraphStyling: MarkdownStyling.Paragraph, + onUrlClick: (String) -> Unit, + onTextClick: () -> Unit, + ) { + Box( + modifier = Modifier.background(backgroundColor).padding(padding), + contentAlignment = (cell.alignment ?: defaultAlignment).asContentAlignment(), + ) { + blockRenderer.render( + block = MarkdownBlock.Paragraph(cell.content), + styling = paragraphStyling, + enabled = enabled, + onUrlClick = onUrlClick, + onTextClick = onTextClick, + ) + } + } + + private fun Alignment.Horizontal.asContentAlignment() = + when (this) { + Alignment.Start -> Alignment.TopStart + Alignment.CenterHorizontally -> Alignment.TopCenter + Alignment.End -> Alignment.TopEnd + else -> error("Unsupported alignment: $this") + } +} diff --git a/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension.kt b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension.kt new file mode 100644 index 0000000000000..21dc1543b80e6 --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableProcessorExtension.kt @@ -0,0 +1,159 @@ +package org.jetbrains.jewel.markdown.extensions.github.tables + +import androidx.compose.ui.Alignment +import org.commonmark.ext.gfm.tables.TableBlock as CommonMarkTableBlock +import org.commonmark.ext.gfm.tables.TableBody as CommonMarkTableBody +import org.commonmark.ext.gfm.tables.TableCell as CommonMarkTableCell +import org.commonmark.ext.gfm.tables.TableCell.Alignment.CENTER +import org.commonmark.ext.gfm.tables.TableCell.Alignment.LEFT +import org.commonmark.ext.gfm.tables.TableCell.Alignment.RIGHT +import org.commonmark.ext.gfm.tables.TableHead as CommonMarkTableHeader +import org.commonmark.ext.gfm.tables.TableRow as CommonMarkTableRow +import org.commonmark.ext.gfm.tables.TablesExtension +import org.commonmark.node.CustomBlock +import org.commonmark.node.Node +import org.commonmark.parser.Parser.Builder +import org.commonmark.parser.Parser.ParserExtension +import org.commonmark.renderer.text.TextContentRenderer +import org.commonmark.renderer.text.TextContentRenderer.TextContentRendererExtension +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.markdown.InlineMarkdown +import org.jetbrains.jewel.markdown.MarkdownBlock +import org.jetbrains.jewel.markdown.extensions.MarkdownBlockProcessorExtension +import org.jetbrains.jewel.markdown.extensions.MarkdownBlockRendererExtension +import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension +import org.jetbrains.jewel.markdown.extensions.MarkdownRendererExtension +import org.jetbrains.jewel.markdown.processing.MarkdownProcessor +import org.jetbrains.jewel.markdown.processing.readInlineContent +import org.jetbrains.jewel.markdown.rendering.MarkdownStyling + +@OptIn(ExperimentalJewelApi::class) +public object GitHubTableProcessorExtension : MarkdownProcessorExtension { + override val parserExtension: ParserExtension = GitHubTablesCommonMarkExtension + override val textRendererExtension: TextContentRendererExtension = GitHubTablesCommonMarkExtension + + override val blockProcessorExtension: MarkdownBlockProcessorExtension = GitHubTablesProcessorExtension + + private object GitHubTablesProcessorExtension : MarkdownBlockProcessorExtension { + override fun canProcess(block: CustomBlock): Boolean = block is CommonMarkTableBlock + + override fun processMarkdownBlock( + block: CustomBlock, + processor: MarkdownProcessor, + ): MarkdownBlock.CustomBlock? { + val tableBlock = block as CommonMarkTableBlock + val header = tableBlock.firstChild as? CommonMarkTableHeader ?: return null + + val body = tableBlock.lastChild as? CommonMarkTableBody + + return try { + TableBlock( + TableHeader( + // The header contains only one CommonMarkTableRow + (header.firstChild as CommonMarkTableRow).mapCellsIndexed { columnIndex, cell -> + TableCell( + rowIndex = 0, + columnIndex = columnIndex, + content = cell.readInlineContent(processor), + alignment = getAlignment(cell), + ) + } + ), + body + ?.mapRowsIndexed { rowIndex, row -> + TableRow( + rowIndex = rowIndex, + row.mapCellsIndexed { columnIndex, cell -> + TableCell( + rowIndex = rowIndex + 1, // The header is row zero + columnIndex = columnIndex, + content = cell.readInlineContent(processor), + alignment = getAlignment(cell), + ) + }, + ) + } + .orEmpty(), + ) + } catch (_: IllegalArgumentException) { + null + } + } + + private fun getAlignment(cell: CommonMarkTableCell) = + when (cell.alignment) { + LEFT -> Alignment.Start + CENTER -> Alignment.CenterHorizontally + RIGHT -> Alignment.End + null -> null + } + + private inline fun CommonMarkTableRow.mapCellsIndexed( + mapper: (Int, CommonMarkTableCell) -> TableCell + ): List<TableCell> = buildList { + forEachChildIndexed { index, child -> if (child is CommonMarkTableCell) add(mapper(index, child)) } + } + + private inline fun CommonMarkTableBody.mapRowsIndexed( + mapper: (Int, CommonMarkTableRow) -> TableRow + ): List<TableRow> = buildList { + forEachChildIndexed { index, child -> if (child is CommonMarkTableRow) add(mapper(index, child)) } + } + + private inline fun Node.forEachChildIndexed(action: (Int, Node) -> Unit) { + var child = firstChild + + var index = 0 + while (child != null) { + action(index, child) + index++ + child = child.next + } + } + } +} + +private object GitHubTablesCommonMarkExtension : ParserExtension, TextContentRendererExtension { + override fun extend(parserBuilder: Builder) { + parserBuilder.extensions(listOf(TablesExtension.create())) + } + + override fun extend(rendererBuilder: TextContentRenderer.Builder) { + rendererBuilder.extensions(listOf(TablesExtension.create())) + } +} + +@OptIn(ExperimentalJewelApi::class) +public class GitHubTableRendererExtension(tableStyling: GfmTableStyling, rootStyling: MarkdownStyling) : + MarkdownRendererExtension { + override val blockRenderer: MarkdownBlockRendererExtension = GitHubTableBlockRenderer(rootStyling, tableStyling) +} + +internal data class TableBlock(val header: TableHeader, val rows: List<TableRow>) : MarkdownBlock.CustomBlock { + val rowCount: Int = rows.size + 1 // We always have a header + val columnCount: Int + + init { + require(header.cells.isNotEmpty()) { "Header cannot be empty" } + val headerColumns = header.cells.size + + if (rows.isNotEmpty()) { + val bodyColumns = rows.first().cells.size + require(rows.all { it.cells.size == bodyColumns }) { "Inconsistent cell count in table body" } + require(headerColumns == bodyColumns) { "Inconsistent cell count between table body and header" } + } + + columnCount = headerColumns + } +} + +internal data class TableHeader(val cells: List<TableCell>) : CustomBlock() + +internal data class TableRow(val rowIndex: Int, val cells: List<TableCell>) : CustomBlock() + +internal data class TableCell( + val rowIndex: Int, + val columnIndex: Int, + val content: List<InlineMarkdown>, + val alignment: Alignment.Horizontal?, +) : CustomBlock() diff --git a/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableStyling.kt b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableStyling.kt new file mode 100644 index 0000000000000..238e14ccc90fa --- /dev/null +++ b/platform/jewel/markdown/extension/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableStyling.kt @@ -0,0 +1,72 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the +// Apache 2.0 license. +package org.jetbrains.jewel.markdown.extensions.github.tables + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import org.jetbrains.jewel.foundation.ExperimentalJewelApi +import org.jetbrains.jewel.foundation.GenerateDataFunctions + +@GenerateDataFunctions +public class GfmTableStyling( + public val colors: GfmTableColors, + public val metrics: GfmTableMetrics, + public val headerBaseFontWeight: FontWeight, +) { + @ExperimentalJewelApi + public constructor( + borderColor: Color, + rowBackgroundColor: Color, + alternateRowBackgroundColor: Color, + rowBackgroundStyle: RowBackgroundStyle, + borderWidth: Dp, + cellPadding: PaddingValues, + defaultCellContentAlignment: Alignment.Horizontal, + headerDefaultCellContentAlignment: Alignment.Horizontal, + headerBaseFontWeight: FontWeight, + ) : this( + colors = GfmTableColors(borderColor, rowBackgroundColor, alternateRowBackgroundColor, rowBackgroundStyle), + metrics = + GfmTableMetrics(borderWidth, cellPadding, defaultCellContentAlignment, headerDefaultCellContentAlignment), + headerBaseFontWeight = headerBaseFontWeight, + ) + + public companion object +} + +@GenerateDataFunctions +public class GfmTableColors( + public val borderColor: Color, + public val rowBackgroundColor: Color, + public val alternateRowBackgroundColor: Color, + public val rowBackgroundStyle: RowBackgroundStyle, +) { + public companion object +} + +public enum class RowBackgroundStyle { + /** + * All rows have the same background color, [GfmTableColors.rowBackgroundColor]. In this style, + * [GfmTableColors.alternateRowBackgroundColor] is ignored. + */ + Normal, + + /** + * Rows have alternate colors. Odd rows use [GfmTableColors.rowBackgroundColor] and even rows use + * [GfmTableColors.alternateRowBackgroundColor]. + */ + Striped, +} + +@GenerateDataFunctions +public class GfmTableMetrics( + public val borderWidth: Dp, + public val cellPadding: PaddingValues, + public val defaultCellContentAlignment: Alignment.Horizontal, + public val headerDefaultCellContentAlignment: Alignment.Horizontal, +) { + public companion object +} diff --git a/platform/jewel/markdown/ide-laf-bridge-styling/api/ide-laf-bridge-styling.api b/platform/jewel/markdown/ide-laf-bridge-styling/api/ide-laf-bridge-styling.api index f6d7cfbc72f6b..c8fd643b876d4 100644 --- a/platform/jewel/markdown/ide-laf-bridge-styling/api/ide-laf-bridge-styling.api +++ b/platform/jewel/markdown/ide-laf-bridge-styling/api/ide-laf-bridge-styling.api @@ -66,3 +66,12 @@ public final class org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/ public static synthetic fun create-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Lorg/jetbrains/jewel/ui/icon/IconKey;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling; } +public final class org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/tables/BridgeGitHubTableStylingKt { + public static final fun create (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics;Landroidx/compose/ui/text/font/FontWeight;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling; + public static synthetic fun create$default (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics;Landroidx/compose/ui/text/font/FontWeight;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling; + public static final fun create-f1JAnFk (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors$Companion;JJJLorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors; + public static synthetic fun create-f1JAnFk$default (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors$Companion;JJJLorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors; + public static final fun create-rAjV9yQ (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics$Companion;FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Horizontal;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics; + public static synthetic fun create-rAjV9yQ$default (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics$Companion;FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Horizontal;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics; +} + diff --git a/platform/jewel/markdown/ide-laf-bridge-styling/build.gradle.kts b/platform/jewel/markdown/ide-laf-bridge-styling/build.gradle.kts index b421ece07a4a2..0763451712b5e 100644 --- a/platform/jewel/markdown/ide-laf-bridge-styling/build.gradle.kts +++ b/platform/jewel/markdown/ide-laf-bridge-styling/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { api(projects.markdown.core) api(projects.ideLafBridge) compileOnly(projects.markdown.extension.gfmAlerts) + compileOnly(projects.markdown.extension.gfmTables) intellijPlatform { intellijIdeaCommunity(libs.versions.idea) diff --git a/platform/jewel/markdown/ide-laf-bridge-styling/intellij.platform.jewel.markdown.ideLafBridgeStyling.iml b/platform/jewel/markdown/ide-laf-bridge-styling/intellij.platform.jewel.markdown.ideLafBridgeStyling.iml index 7b9297eb4ca61..fbf786b8db7b4 100644 --- a/platform/jewel/markdown/ide-laf-bridge-styling/intellij.platform.jewel.markdown.ideLafBridgeStyling.iml +++ b/platform/jewel/markdown/ide-laf-bridge-styling/intellij.platform.jewel.markdown.ideLafBridgeStyling.iml @@ -40,6 +40,7 @@ <orderEntry type="module" module-name="intellij.platform.jewel.foundation" /> <orderEntry type="module" module-name="intellij.platform.jewel.ideLafBridge" exported="" /> <orderEntry type="module" module-name="intellij.platform.jewel.markdown.extension.gfmAlerts" /> + <orderEntry type="module" module-name="intellij.platform.jewel.markdown.extension.gfmTables" /> <orderEntry type="module" module-name="intellij.platform.ide" /> <orderEntry type="module" module-name="intellij.platform.ide.core" /> <orderEntry type="module" module-name="intellij.platform.ide.core.impl" /> diff --git a/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/Utils.kt b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/Utils.kt new file mode 100644 index 0000000000000..64923dd1cdd73 --- /dev/null +++ b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/Utils.kt @@ -0,0 +1,5 @@ +package org.jetbrains.jewel.intui.markdown.bridge.styling + +import com.intellij.ui.JBColor + +internal val isLightTheme = JBColor.isBright() diff --git a/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/alerts/BridgeGitHubAlertStyling.kt b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/alerts/BridgeGitHubAlertStyling.kt index 9fcefc8236d44..89f626451107d 100644 --- a/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/alerts/BridgeGitHubAlertStyling.kt +++ b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/alerts/BridgeGitHubAlertStyling.kt @@ -8,7 +8,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.intellij.ui.JBColor +import org.jetbrains.jewel.intui.markdown.bridge.styling.isLightTheme import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertStyling import org.jetbrains.jewel.markdown.extensions.github.alerts.CautionAlertStyling import org.jetbrains.jewel.markdown.extensions.github.alerts.GitHubAlertIcons @@ -29,7 +29,7 @@ public fun AlertStyling.Companion.create( public fun NoteAlertStyling.Companion.create( padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), lineWidth: Dp = 3.dp, - lineColor: Color = if (JBColor.isBright()) Color(0xFF0969DA) else Color(0xFF1F6EEB), + lineColor: Color = if (isLightTheme) Color(0xFF0969DA) else Color(0xFF1F6EEB), pathEffect: PathEffect? = null, strokeCap: StrokeCap = StrokeCap.Square, titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), @@ -52,7 +52,7 @@ public fun NoteAlertStyling.Companion.create( public fun TipAlertStyling.Companion.create( padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), lineWidth: Dp = 3.dp, - lineColor: Color = if (JBColor.isBright()) Color(0xFF1F883D) else Color(0xFF238636), + lineColor: Color = if (isLightTheme) Color(0xFF1F883D) else Color(0xFF238636), pathEffect: PathEffect? = null, strokeCap: StrokeCap = StrokeCap.Square, titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), @@ -75,7 +75,7 @@ public fun TipAlertStyling.Companion.create( public fun ImportantAlertStyling.Companion.create( padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), lineWidth: Dp = 3.dp, - lineColor: Color = if (JBColor.isBright()) Color(0xFF8250DF) else Color(0xFF8957E5), + lineColor: Color = if (isLightTheme) Color(0xFF8250DF) else Color(0xFF8957E5), pathEffect: PathEffect? = null, strokeCap: StrokeCap = StrokeCap.Square, titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), @@ -98,7 +98,7 @@ public fun ImportantAlertStyling.Companion.create( public fun WarningAlertStyling.Companion.create( padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), lineWidth: Dp = 3.dp, - lineColor: Color = if (JBColor.isBright()) Color(0xFF9A6601) else Color(0xFF9E6A02), + lineColor: Color = if (isLightTheme) Color(0xFF9A6601) else Color(0xFF9E6A02), pathEffect: PathEffect? = null, strokeCap: StrokeCap = StrokeCap.Square, titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), @@ -121,7 +121,7 @@ public fun WarningAlertStyling.Companion.create( public fun CautionAlertStyling.Companion.create( padding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp), lineWidth: Dp = 3.dp, - lineColor: Color = if (JBColor.isBright()) Color(0xFFCF222E) else Color(0xFFDA3633), + lineColor: Color = if (isLightTheme) Color(0xFFCF222E) else Color(0xFFDA3633), pathEffect: PathEffect? = null, strokeCap: StrokeCap = StrokeCap.Square, titleTextStyle: TextStyle = TextStyle(fontWeight = FontWeight.Medium, color = lineColor), diff --git a/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/tables/BridgeGitHubTableStyling.kt b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/tables/BridgeGitHubTableStyling.kt new file mode 100644 index 0000000000000..e8a1009637cad --- /dev/null +++ b/platform/jewel/markdown/ide-laf-bridge-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/bridge/styling/extensions/github/tables/BridgeGitHubTableStyling.kt @@ -0,0 +1,35 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the +// Apache 2.0 license. +package org.jetbrains.jewel.intui.markdown.bridge.styling.extensions.github.tables + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.intui.markdown.bridge.styling.isLightTheme +import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableColors +import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableMetrics +import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableStyling +import org.jetbrains.jewel.markdown.extensions.github.tables.RowBackgroundStyle + +public fun GfmTableStyling.Companion.create( + colors: GfmTableColors = GfmTableColors.create(), + metrics: GfmTableMetrics = GfmTableMetrics.create(), + headerBaseFontWeight: FontWeight = FontWeight.SemiBold, +): GfmTableStyling = GfmTableStyling(colors, metrics, headerBaseFontWeight) + +public fun GfmTableColors.Companion.create( + borderColor: Color = if (isLightTheme) Color(0xffd1d9e0) else Color(0xff3d444d), + rowBackgroundColor: Color = Color.Unspecified, + alternateRowBackgroundColor: Color = if (isLightTheme) Color(0xfff6f8fa) else Color(0xff151b23), + rowBackgroundStyle: RowBackgroundStyle = RowBackgroundStyle.Striped, +) = GfmTableColors(borderColor, rowBackgroundColor, alternateRowBackgroundColor, rowBackgroundStyle) + +public fun GfmTableMetrics.Companion.create( + borderWidth: Dp = 1.dp, + cellPadding: PaddingValues = PaddingValues(horizontal = 13.dp, vertical = 6.dp), + defaultCellContentAlignment: Alignment.Horizontal = Alignment.Start, + headerDefaultCellContentAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, +) = GfmTableMetrics(borderWidth, cellPadding, defaultCellContentAlignment, headerDefaultCellContentAlignment) diff --git a/platform/jewel/markdown/int-ui-standalone-styling/api/int-ui-standalone-styling.api b/platform/jewel/markdown/int-ui-standalone-styling/api/int-ui-standalone-styling.api index 4e4693c68c7cd..4eacec1d82d24 100644 --- a/platform/jewel/markdown/int-ui-standalone-styling/api/int-ui-standalone-styling.api +++ b/platform/jewel/markdown/int-ui-standalone-styling/api/int-ui-standalone-styling.api @@ -118,3 +118,16 @@ public final class org/jetbrains/jewel/intui/markdown/standalone/styling/extensi public static synthetic fun light-gaOEZmc$default (Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling$Companion;Landroidx/compose/foundation/layout/PaddingValues;FJLandroidx/compose/ui/graphics/PathEffect;ILandroidx/compose/ui/text/TextStyle;Lorg/jetbrains/jewel/ui/icon/IconKey;JJILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/alerts/WarningAlertStyling; } +public final class org/jetbrains/jewel/intui/markdown/standalone/styling/extensions/github/tables/IntUiGitHubTableStylingKt { + public static final fun dark (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics;Landroidx/compose/ui/text/font/FontWeight;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling; + public static synthetic fun dark$default (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics;Landroidx/compose/ui/text/font/FontWeight;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling; + public static final fun dark-f1JAnFk (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors$Companion;JJJLorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors; + public static synthetic fun dark-f1JAnFk$default (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors$Companion;JJJLorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors; + public static final fun defaults-rAjV9yQ (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics$Companion;FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Horizontal;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics; + public static synthetic fun defaults-rAjV9yQ$default (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics$Companion;FLandroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Alignment$Horizontal;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics; + public static final fun light (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics;Landroidx/compose/ui/text/font/FontWeight;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling; + public static synthetic fun light$default (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling$Companion;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors;Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableMetrics;Landroidx/compose/ui/text/font/FontWeight;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableStyling; + public static final fun light-f1JAnFk (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors$Companion;JJJLorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors; + public static synthetic fun light-f1JAnFk$default (Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors$Companion;JJJLorg/jetbrains/jewel/markdown/extensions/github/tables/RowBackgroundStyle;ILjava/lang/Object;)Lorg/jetbrains/jewel/markdown/extensions/github/tables/GfmTableColors; +} + diff --git a/platform/jewel/markdown/int-ui-standalone-styling/build.gradle.kts b/platform/jewel/markdown/int-ui-standalone-styling/build.gradle.kts index b80d7926a182c..15e3061835ad4 100644 --- a/platform/jewel/markdown/int-ui-standalone-styling/build.gradle.kts +++ b/platform/jewel/markdown/int-ui-standalone-styling/build.gradle.kts @@ -10,6 +10,7 @@ dependencies { api(projects.markdown.core) api(projects.intUi.intUiStandalone) compileOnly(projects.markdown.extension.gfmAlerts) + compileOnly(projects.markdown.extension.gfmTables) testImplementation(compose.desktop.uiTestJUnit4) } diff --git a/platform/jewel/markdown/int-ui-standalone-styling/intellij.platform.jewel.markdown.intUiStandaloneStyling.iml b/platform/jewel/markdown/int-ui-standalone-styling/intellij.platform.jewel.markdown.intUiStandaloneStyling.iml index 58a41f6722bac..ce302247fc570 100644 --- a/platform/jewel/markdown/int-ui-standalone-styling/intellij.platform.jewel.markdown.intUiStandaloneStyling.iml +++ b/platform/jewel/markdown/int-ui-standalone-styling/intellij.platform.jewel.markdown.intUiStandaloneStyling.iml @@ -40,6 +40,7 @@ <orderEntry type="module" module-name="intellij.platform.jewel.foundation" /> <orderEntry type="module" module-name="intellij.platform.jewel.intUi.standalone" /> <orderEntry type="module" module-name="intellij.platform.jewel.markdown.extension.gfmAlerts" /> + <orderEntry type="module" module-name="intellij.platform.jewel.markdown.extension.gfmTables" /> <orderEntry type="module-library" scope="TEST"> <library name="org.jetbrains.compose.ui.ui.test.junit4" type="repository"> <properties include-transitive-deps="false" maven-id="org.jetbrains.compose.ui:ui-test-junit4:1.7.1"> diff --git a/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/styling/extensions/github/tables/IntUiGitHubTableStyling.kt b/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/styling/extensions/github/tables/IntUiGitHubTableStyling.kt new file mode 100644 index 0000000000000..7c6667c2e6d56 --- /dev/null +++ b/platform/jewel/markdown/int-ui-standalone-styling/src/main/kotlin/org/jetbrains/jewel/intui/markdown/standalone/styling/extensions/github/tables/IntUiGitHubTableStyling.kt @@ -0,0 +1,45 @@ +package org.jetbrains.jewel.intui.markdown.standalone.styling.extensions.github.tables + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableColors +import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableMetrics +import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableStyling +import org.jetbrains.jewel.markdown.extensions.github.tables.RowBackgroundStyle + +public fun GfmTableStyling.Companion.light( + colors: GfmTableColors = GfmTableColors.light(), + metrics: GfmTableMetrics = GfmTableMetrics.defaults(), + headerBaseFontWeight: FontWeight = FontWeight.SemiBold, +): GfmTableStyling = GfmTableStyling(colors, metrics, headerBaseFontWeight) + +public fun GfmTableStyling.Companion.dark( + colors: GfmTableColors = GfmTableColors.dark(), + metrics: GfmTableMetrics = GfmTableMetrics.defaults(), + headerBaseFontWeight: FontWeight = FontWeight.SemiBold, +): GfmTableStyling = GfmTableStyling(colors, metrics, headerBaseFontWeight) + +public fun GfmTableColors.Companion.light( + borderColor: Color = Color(0xffd1d9e0), + rowBackgroundColor: Color = Color.Unspecified, + alternateRowBackgroundColor: Color = Color(0xfff6f8fa), + rowBackgroundStyle: RowBackgroundStyle = RowBackgroundStyle.Striped, +) = GfmTableColors(borderColor, rowBackgroundColor, alternateRowBackgroundColor, rowBackgroundStyle) + +public fun GfmTableColors.Companion.dark( + borderColor: Color = Color(0xff3d444d), + rowBackgroundColor: Color = Color.Unspecified, + alternateRowBackgroundColor: Color = Color(0xff151b23), + rowBackgroundStyle: RowBackgroundStyle = RowBackgroundStyle.Striped, +) = GfmTableColors(borderColor, rowBackgroundColor, alternateRowBackgroundColor, rowBackgroundStyle) + +public fun GfmTableMetrics.Companion.defaults( + borderWidth: Dp = 1.dp, + cellPadding: PaddingValues = PaddingValues(horizontal = 13.dp, vertical = 6.dp), + defaultCellContentAlignment: Alignment.Horizontal = Alignment.Start, + headerDefaultCellContentAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, +) = GfmTableMetrics(borderWidth, cellPadding, defaultCellContentAlignment, headerDefaultCellContentAlignment) diff --git a/platform/jewel/samples/standalone/build.gradle.kts b/platform/jewel/samples/standalone/build.gradle.kts index 92978173d8147..2328d7b48fb82 100644 --- a/platform/jewel/samples/standalone/build.gradle.kts +++ b/platform/jewel/samples/standalone/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { implementation(projects.markdown.intUiStandaloneStyling) implementation(projects.markdown.extension.gfmAlerts) implementation(projects.markdown.extension.autolink) + implementation(projects.markdown.extension.gfmTables) implementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") } implementation(compose.components.resources) implementation(project(":samples:showcase")) diff --git a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/IntUiThemes.kt b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/IntUiThemes.kt index 728f5e3ca5e84..7da03ca7a802f 100644 --- a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/IntUiThemes.kt +++ b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/IntUiThemes.kt @@ -14,6 +14,7 @@ public enum class IntUiThemes { public fun isLightHeader(): Boolean = this == LightWithLightHeader public companion object { - public fun fromSystemTheme(systemTheme: SystemTheme): IntUiThemes = if (systemTheme == SystemTheme.LIGHT) Light else Dark + public fun fromSystemTheme(systemTheme: SystemTheme): IntUiThemes = + if (systemTheme == SystemTheme.LIGHT) Light else Dark } } diff --git a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownEditor.kt b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownEditor.kt index f5940aae9431c..ac7903e94e67d 100644 --- a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownEditor.kt +++ b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownEditor.kt @@ -2,7 +2,6 @@ package org.jetbrains.jewel.samples.standalone.markdown import androidx.compose.foundation.background import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -45,16 +44,14 @@ public fun MarkdownEditor(state: TextFieldState, modifier: Modifier = Modifier) @Composable private fun ControlsRow(modifier: Modifier = Modifier, onLoadMarkdown: (String) -> Unit) { - Row( - modifier.horizontalScroll(rememberScrollState()), - horizontalArrangement = Arrangement.spacedBy(16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { + Row(modifier.horizontalScroll(rememberScrollState()), verticalAlignment = Alignment.CenterVertically) { var showFilePicker by remember { mutableStateOf(false) } OutlinedButton(onClick = { showFilePicker = true }, modifier = Modifier.padding(start = 2.dp)) { Text("Load file...") } + Spacer(Modifier.width(10.dp)) + FilePicker(show = showFilePicker, fileExtensions = listOf("md")) { platformFile -> showFilePicker = false diff --git a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownPreview.kt b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownPreview.kt index 5558ac8f3c1b7..52a5c70fef82a 100644 --- a/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownPreview.kt +++ b/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/markdown/MarkdownPreview.kt @@ -24,14 +24,18 @@ import org.jetbrains.jewel.intui.markdown.standalone.light import org.jetbrains.jewel.intui.markdown.standalone.styling.dark import org.jetbrains.jewel.intui.markdown.standalone.styling.extensions.github.alerts.dark import org.jetbrains.jewel.intui.markdown.standalone.styling.extensions.github.alerts.light +import org.jetbrains.jewel.intui.markdown.standalone.styling.extensions.github.tables.dark +import org.jetbrains.jewel.intui.markdown.standalone.styling.extensions.github.tables.light import org.jetbrains.jewel.intui.markdown.standalone.styling.light import org.jetbrains.jewel.markdown.LazyMarkdown import org.jetbrains.jewel.markdown.MarkdownBlock import org.jetbrains.jewel.markdown.extension.autolink.AutolinkProcessorExtension -import org.jetbrains.jewel.markdown.extensions.LocalMarkdownMode import org.jetbrains.jewel.markdown.extensions.github.alerts.AlertStyling import org.jetbrains.jewel.markdown.extensions.github.alerts.GitHubAlertProcessorExtension import org.jetbrains.jewel.markdown.extensions.github.alerts.GitHubAlertRendererExtension +import org.jetbrains.jewel.markdown.extensions.github.tables.GfmTableStyling +import org.jetbrains.jewel.markdown.extensions.github.tables.GitHubTableProcessorExtension +import org.jetbrains.jewel.markdown.extensions.github.tables.GitHubTableRendererExtension import org.jetbrains.jewel.markdown.processing.MarkdownProcessor import org.jetbrains.jewel.markdown.rendering.MarkdownBlockRenderer import org.jetbrains.jewel.markdown.rendering.MarkdownStyling @@ -45,12 +49,15 @@ public fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeque val markdownStyling = remember(isDark) { if (isDark) MarkdownStyling.dark() else MarkdownStyling.light() } var markdownBlocks by remember { mutableStateOf(emptyList<MarkdownBlock>()) } - val extensions = remember { listOf(GitHubAlertProcessorExtension, AutolinkProcessorExtension) } - val markdownMode = LocalMarkdownMode.current + // We are doing this here for the sake of simplicity. // In a real-world scenario you would be doing this outside your Composables, // potentially involving ViewModels, dependency injection, etc. - val processor = remember { MarkdownProcessor(extensions, markdownMode = markdownMode) } + val processor = remember { + MarkdownProcessor( + listOf(GitHubAlertProcessorExtension, AutolinkProcessorExtension, GitHubTableProcessorExtension) + ) + } LaunchedEffect(rawMarkdown) { // TODO you may want to debounce or drop on backpressure, in real usages. You should also @@ -61,18 +68,24 @@ public fun MarkdownPreview(modifier: Modifier = Modifier, rawMarkdown: CharSeque } val blockRenderer = - remember(markdownStyling, extensions) { + remember(markdownStyling) { if (isDark) { MarkdownBlockRenderer.dark( styling = MarkdownStyling.dark(), rendererExtensions = - listOf(GitHubAlertRendererExtension(AlertStyling.dark(), MarkdownStyling.dark())), + listOf( + GitHubAlertRendererExtension(AlertStyling.dark(), MarkdownStyling.dark()), + GitHubTableRendererExtension(GfmTableStyling.dark(), markdownStyling), + ), ) } else { MarkdownBlockRenderer.light( styling = MarkdownStyling.light(), rendererExtensions = - listOf(GitHubAlertRendererExtension(AlertStyling.light(), MarkdownStyling.light())), + listOf( + GitHubAlertRendererExtension(AlertStyling.light(), MarkdownStyling.light()), + GitHubTableRendererExtension(GfmTableStyling.light(), markdownStyling), + ), ) } } diff --git a/platform/jewel/settings.gradle.kts b/platform/jewel/settings.gradle.kts index 27bb9050ceb0c..fc9fa69cb0611 100644 --- a/platform/jewel/settings.gradle.kts +++ b/platform/jewel/settings.gradle.kts @@ -12,6 +12,9 @@ pluginManagement { gradlePluginPortal() mavenCentral() } + plugins { + kotlin("jvm") version "2.1.0" + } } dependencyResolutionManagement { @@ -28,6 +31,7 @@ dependencyResolutionManagement { plugins { id("com.gradle.enterprise") version "3.15.1" id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0" + id("org.danilopianini.gradle-pre-commit-git-hooks") version "2.0.9" } include( @@ -40,6 +44,7 @@ include( ":markdown:core", ":markdown:extension:autolink", ":markdown:extension:gfm-alerts", + ":markdown:extension:gfm-tables", ":markdown:int-ui-standalone-styling", ":markdown:ide-laf-bridge-styling", ":samples:ide-plugin", @@ -49,8 +54,6 @@ include( ":ui-test", ) -project(":ide-laf-bridge-tests").projectDir = file("ide-laf-bridge/ide-laf-bridge-tests") - gradleEnterprise { buildScan { publishAlwaysIf(System.getenv("CI") == "true") @@ -59,4 +62,46 @@ gradleEnterprise { } } -include("ui-tests") +val isWindows + get() = System.getProperty("os.name").contains("win", true) + +val gradleCommand: String by +lazy(LazyThreadSafetyMode.NONE) { + val gradlewFilename = + if (isWindows) { + "gradlew.bat" + } else { + "gradlew" + } + + val gradlew = File(rootProject.projectDir, gradlewFilename) + if (gradlew.exists() && gradlew.isFile && gradlew.canExecute()) { + logger.info("Using gradlew wrapper at ${gradlew.invariantSeparatorsPath}") + gradlew.invariantSeparatorsPath + } else { + "gradle" + } +} + +val shebang = if (isWindows) "" else "#!/bin/sh" + +gitHooks { + hook("pre-push") { + from(shebang) { + // language=Shell Script + """ + |#### Note: this hook was autogenerated. You can edit it in settings.gradle.kts + |GRADLEW=$gradleCommand + |if ! ${'$'}GRADLEW ktfmtCheck ; then + | ${'$'}GRADLEW ktfmtFormat + | echo 1>&2 "\nktfmt found problems; commit the result and re-push" + | exit 1 + |fi + | + """ + .trimMargin() + } + } + + createHooks(overwriteExisting = true) +} diff --git a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Chip.kt b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Chip.kt index 10a4a65a9354c..9a2e6ee7ad9ad 100644 --- a/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Chip.kt +++ b/platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Chip.kt @@ -42,6 +42,46 @@ import org.jetbrains.jewel.ui.component.styling.ChipStyle import org.jetbrains.jewel.ui.focusOutline import org.jetbrains.jewel.ui.theme.chipStyle +/** + * A lightweight, button-like component used to represent actions, attributes, or filters. + * + * The `Chip` component is a compact, clickable element that provides visual feedback for various interaction states + * (hover, focus, press, etc.). It is versatile and customizable, allowing developers to define its style, behavior, and + * content. + * + * **Guidelines:** Reference Compose Chips for usage suggestions: + * [Compose Chips Documentation](https://developer.android.com/develop/ui/compose/components/chip) + * + * **Usage example:** + * + * ```kotlin + * var clicks by remember { mutableStateOf(0) } + * Chip( + * onClick = { clicks++ }, + * enabled = true + * ) { + * Text("Clicked: $clicks times!") + * } + * ``` + * + * **Key Features:** + * - Small footprint, ideal for short text, icons, or a combination. + * - Fully customizable using [ChipStyle], enabling tailored shapes, paddings, and colors. + * - Adapts dynamically to user interactions with built-in state management. + * + * **When to use:** + * - To suggest or apply filters (e.g., user-selected tags). + * - To surface commonly used, secondary actions. + * - To display compact attributes or classifications. + * + * @param modifier Modifier to customize the layout and visual appearance. + * @param interactionSource Custom [MutableInteractionSource] for observing chip interaction events. + * @param enabled Controls interactivity. When `false`, the chip is non-clickable and rendered visually as disabled. + * @param selected Tracks the visual and semantic selection state of the chip. + * @param style Defines the visual styling of the chip (via [ChipStyle]). + * @param onClick Action to perform when the chip is clicked. + * @param content The composable content displayed inside the chip (e.g., text, icons, or a combination). + */ @Composable public fun Chip( modifier: Modifier = Modifier, @@ -69,6 +109,46 @@ public fun Chip( ) } +/** + * A toggleable version of [Chip], representing a switchable on/off state. + * + * `ToggleableChip` provides a compact UI element that can toggle between two states: checked and unchecked. This + * component is suitable for enabling/disabling an option or toggling a feature. + * + * **Usage example:** + * + * ```kotlin + * var isChecked by remember { mutableStateOf(false) } + * ToggleableChip( + * checked = isChecked, + * onClick = { isChecked = it }, + * enabled = true + * ) { + * Text(if (isChecked) "Enabled" else "Disabled") + * } + * ``` + * + * **Key Features:** + * - Represents a binary (checked/unchecked) state, similar to a checkbox. + * - Fully customizable using [ChipStyle], enabling tailored shapes, paddings, and colors. + * - Automatically adapts visual appearance based on toggle states. + * + * **When to use:** + * - To create toggleable filters or options. + * - To represent binary states that persist or control a feature. + * - As a compact alternative to a checkbox. + * + * **State Management:** The `checked` parameter controls the current state of the chip, while `onClick` defines the + * behavior when the chip is toggled. + * + * @param checked Indicates whether the chip is checked (true) or unchecked (false). + * @param onClick Action to perform when the chip is toggled. The new toggle state is passed to this lambda. + * @param modifier Modifier to customize chip layout and visuals. + * @param interactionSource Custom [MutableInteractionSource] for observing chip interaction events. + * @param enabled Controls interactivity. When `false`, the chip cannot be toggled. + * @param style Defines the visual styling of the chip (via [ChipStyle]). + * @param content The composable content displayed inside the chip (e.g., text, icons, or a combination). + */ @Composable public fun ToggleableChip( checked: Boolean, @@ -97,6 +177,52 @@ public fun ToggleableChip( ) } +/** + * A chip component designed for mutually exclusive selection within a group. + * + * `RadioButtonChip` provides a compact UI element for representing an option in a single-selection group. When + * selected, it automatically deselects other chips in the group. + * + * **Usage example:** + * + * ```kotlin + * var selectedOption by remember { mutableStateOf(0) } + * Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + * RadioButtonChip( + * selected = selectedOption == 0, + * onClick = { selectedOption = 0 } + * ) { Text("Option 1") } + * RadioButtonChip( + * selected = selectedOption == 1, + * onClick = { selectedOption = 1 } + * ) { Text("Option 2") } + * RadioButtonChip( + * selected = selectedOption == 2, + * onClick = { selectedOption = 2 } + * ) { Text("Option 3") } + * } + * ``` + * + * **Key Features:** + * - Represents mutually exclusive states in a single-selection group. + * - Fully customizable using [ChipStyle], enabling tailored shapes, paddings, and colors. + * - Automatically adapts visual appearance depending on whether the chip is selected or not. + * + * **When to use:** + * - To represent options in a single-selection choice group (e.g., forms, filters). + * - For small, space-efficient alternatives to radio buttons. + * + * **State Management:** The `selected` parameter controls whether the chip is active, while `onClick` handles selection + * actions. + * + * @param selected Indicates whether the chip is selected in the group. + * @param onClick Action to perform when the chip is selected. + * @param modifier Modifier to customize chip layout and visuals. + * @param interactionSource Custom [MutableInteractionSource] for observing chip interaction events. + * @param enabled Controls interactivity. When `false`, the chip cannot be selected or clicked. + * @param style Defines the visual styling of the chip (via [ChipStyle]). + * @param content The composable content displayed inside the chip (e.g., text, icons, or a combination). + */ @Composable public fun RadioButtonChip( selected: Boolean,