diff --git a/markdown/README.md b/markdown/README.md index 95da6b37a..c41644a86 100644 --- a/markdown/README.md +++ b/markdown/README.md @@ -11,6 +11,7 @@ Currently supports the [CommonMark 0.31.2](https://spec.commonmark.org/0.31.2/) 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) [alerts-specs]: https://github.com/orgs/community/discussions/16925 @@ -19,7 +20,6 @@ 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/)) -* Auto-linking ([GitHub Flavored Markdown](https://github.github.com/gfm/#autolinks-extension-)) * Task list items ([GitHub Flavored Markdown](https://github.github.com/gfm/#task-list-items-extension-)) * Keyboard shortcuts highlighting (specialized HTML handling) * Collapsing sections ([GitHub Flavored Markdown][details-specs]) @@ -37,7 +37,7 @@ On the roadmap, but not currently supported — in no particular order: Not supported, and not on the roadmap: -* Inline HTML rendering +* Inline HTML rendering (except keyboard shortcuts) * Mermaid diagrams (GitHub Flavored Markdown) * LaTeX rendering, both inline and not (GitHub Flavored Markdown) * topoJSON/geoJSON rendering (GitHub Flavored Markdown) @@ -46,10 +46,11 @@ Not supported, and not on the roadmap: ## Add the Markdown renderer to your project -The Jewel Markdown renderer is designed to be run in a project that already has a `jewel-standalone` or +The Jewel Markdown renderer is designed to be run in a project that already has a `jewel-int-ui-standalone-*` or `jewel-ide-laf-bridge-*` dependency. The `core` module doesn't contain any styling, and you're supposed to use either -the `jewel-markdown-int-ui-standalone-styling` or `jewel-markdown-ide-laf-bridge-styling` instead. They will carry the -necessary dependencies. +the `jewel-markdown-int-ui-standalone-styling-*` or `jewel-markdown-ide-laf-bridge-styling-*` instead. They will carry +the necessary dependencies. Make sure to use an artifact with the matching IJP version to what you already have in your +project. > [!CAUTION] > Don't use the standalone artifact in an IDE plugin, and don't use the bridge artifact in a standalone project! @@ -69,54 +70,63 @@ dependencies { The process that leads to rendering Markdown in a native UI is two-pass. -The first pass is an upfront rendering that pre-processes blocks into `MarkdownBlock`s, but doesn't touch the inline -Markdown. It's recommended to run this outside of the composition, since it has no dependencies on it. +The first pass is an upfront rendering that pre-processes blocks into `MarkdownBlock`s, that contain nested blocks and/ +or `InlineMarkdown`. It's recommended to run this outside of the composition, since it has no dependencies on it, and +it can be slow, depending on the amount of Markdown to process. ```kotlin -// Somewhere outside of composition... +// Somewhere outside of composition — e.g., in a viewmodel or service val processor = MarkdownProcessor() val rawMarkdown = "..." -var markdownBlocks: List = processor.processMarkdownDocument(rawMarkdown) +val markdownBlocks: List = processor.processMarkdownDocument(rawMarkdown) ``` -Once you have your list of `MarkdownBlock`s, you can do the second step in the composition: -render a series of `MarkdownBlock`s into native Jewel UI. - -Here is an example: +Once you have your list of `MarkdownBlock`s, you can do the second step in the composition: render a series of +`MarkdownBlock`s into native Jewel UI. The easiest way is by using +the [`Markdown`](core/src/main/kotlin/org/jetbrains/jewel/markdown/Markdown.kt) composable: ```kotlin -@Composable -fun Markdown(blocks: List) { - val isDark = JewelTheme.isDark - val markdownStyling = - remember(isDark) { if (isDark) MarkdownStyling.dark() else MarkdownStyling.light() } - val blockRenderer = remember(markdownStyling, isDark) { - if (isDark) MarkdownBlockRenderer.dark() else MarkdownBlockRenderer.light() - } - - SelectionContainer(Modifier.fillMaxSize()) { - Column( - modifier = Modifier.verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.spacedBy(markdownStyling.blockVerticalSpacing), - ) { - blockRenderer.render(blocks) - } - } +// We recommend having the Provide call at the theme level +ProvideMarkdownStyling { + Markdown(markdownBlocks) } ``` -If you expect long Markdown documents, you can also use a `LazyMarkdown` to get better performances. -You can find an example in [`MarkdownPreview`](samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/MarkdownPreview.kt). +For your convenience, Jewel also provides a variant of the `Markdown` composable that accepts a string instead of the +list of blocks; this is an easier-to-use API, but comes with the compromise of running the processing in the composition +and is as such only advisable for small, never-changing runs of Markdown. + +For large Markdown documents, you should use the `LazyMarkdown` composable, which is backed by a `LazyColumn` instead of +a regular `Column`. This should provide better performances, but it is not recommended for small documents, as its +overhead is not justifiable. + +### Editor mode + +If you're using the Markdown functionality to generate a real-time preview of some raw Markdown in an editor, we +recommend enabling the `editorMode` in the `MarkdownProcessor`, and using a `LazyMarkdown` composable. + +A `MarkdownProcessor` in editor mode is optimized for small, incremental edits, such as the ones that happen when the +user is typing. The processor, in this mode, tries to figure out which block(s) have been modified, and only recomputes +those. + +> [!CAUTION] +> Never share an instance of `MarkdownProcessor` in editor mode across multiple places! Editor mode is a stateful mode +> that caches its computations, and using it in multiple places will bust the cache and leave you with a worse +> performance. ### Using extensions 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. +> See https://github.com/JetBrains/Jewel/issues/472 for status updates. Extensions are composed of two parts: a parsing and a rendering part. The two parts need to be passed to the -`MarkdownProcessor` and `MarkdownBlockRenderer`, respectively: +`MarkdownProcessor` and `MarkdownBlockRenderer`, respectively. For example, in a standalone project: ```kotlin // Where the parsing happens... @@ -125,16 +135,12 @@ val processor = MarkdownProcessor(parsingExtensions) // Where the rendering happens... val blockRenderer = remember(markdownStyling, isDark) { + val rendererExtensions = listOf(/*...*/) + if (isDark) { - MarkdownBlockRenderer.dark( - rendererExtensions = listOf(/*...*/), - inlineRenderer = InlineMarkdownRenderer.default(parsingExtensions), - ) + MarkdownBlockRenderer.dark(rendererExtensions) } else { - MarkdownBlockRenderer.light( - rendererExtensions = listOf(/*...*/), - inlineRenderer = InlineMarkdownRenderer.default(parsingExtensions), - ) + MarkdownBlockRenderer.light(rendererExtensions) } } ``` @@ -142,13 +148,13 @@ val blockRenderer = remember(markdownStyling, isDark) { It is strongly recommended to use the corresponding set of rendering extensions as the ones used for parsing, otherwise the custom blocks will be parsed but not rendered. -Note that you should create an `InlineMarkdownRenderer` with the same list of extensions that was used to build the -processor, as even though inline rendering extensions are not supported yet, they will be in the future. +Note that you should create `InlineMarkdownRenderer`s with the same list of extensions that was used to build the +block renderer. The `MarkdownBlockRenderer` factory functions take care of this for you, but if you want to provide your +own inline renderer, this is something to be careful about. ### Showcase 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. ![Image showing the Markdown showcase from the Jewel standalone sample](https://github.com/JetBrains/jewel/assets/19003/67e2cc4e-c9b8-454b-884a-bba526ad2fe4)