@@ -26,7 +26,8 @@ import org.intellij.markdown.parser.MarkdownParser
26
26
* @param flavour The [MarkdownFlavourDescriptor] to use for parsing.
27
27
* @param parser The [MarkdownParser] to use for parsing.
28
28
* @param referenceLinkHandler The [ReferenceLinkHandler] to use for storing links.
29
- * @param immediate Whether to parse the content immediately or not. (WARNING: This is not advices, as it will block the composition!)
29
+ * @param immediate Whether to parse the content immediately or not. (WARNING: This is not advised, as it will block the composition!)
30
+ * @return A [MarkdownState] instance that will parse the content and emit the result to its [MarkdownState.state] flow.
30
31
*/
31
32
@Composable
32
33
fun rememberMarkdownState (
@@ -37,42 +38,141 @@ fun rememberMarkdownState(
37
38
referenceLinkHandler : ReferenceLinkHandler = ReferenceLinkHandlerImpl (),
38
39
immediate : Boolean = LocalInspectionMode .current,
39
40
): MarkdownState {
40
- val input = Input (
41
- content = content,
42
- lookupLinks = lookupLinks,
43
- flavour = flavour,
44
- parser = parser,
45
- referenceLinkHandler = referenceLinkHandler,
46
- )
47
- val state = remember(input) { MarkdownState (input) }
41
+ val input = remember(content, lookupLinks, flavour, parser, referenceLinkHandler) {
42
+ Input (
43
+ content = content,
44
+ lookupLinks = lookupLinks,
45
+ flavour = flavour,
46
+ parser = parser,
47
+ referenceLinkHandler = referenceLinkHandler,
48
+ )
49
+ }
50
+ val state = remember(input) { MarkdownStateImpl (input) }
51
+
48
52
if (immediate) {
53
+ // In immediate mode, parse synchronously but be aware this blocks the UI thread
49
54
state.parseBlocking()
50
55
} else {
56
+ // Otherwise, parse asynchronously in a coroutine
51
57
LaunchedEffect (state) {
52
58
state.parse()
53
59
}
54
60
}
61
+
55
62
return state
56
63
}
57
64
58
65
/* *
59
- * A [MarkdownState] that that executes the parsing of the markdown content with the [MarkdownParser] asynchronously.
66
+ * A [MarkdownState] that executes the parsing of the markdown content with the [MarkdownParser] asynchronously.
67
+ * This version accepts a suspend function block that returns the markdown content, allowing for dynamic loading.
68
+ *
69
+ * @param block A suspend function that returns the markdown content to parse.
70
+ * @param lookupLinks Whether to lookup links in the parsed tree or not.
71
+ * @param flavour The [MarkdownFlavourDescriptor] to use for parsing.
72
+ * @param parser The [MarkdownParser] to use for parsing.
73
+ * @param referenceLinkHandler The [ReferenceLinkHandler] to use for storing links.
74
+ * @return A [MarkdownState] instance that will parse the content and emit the result to its [MarkdownState.state] flow.
75
+ */
76
+ @Composable
77
+ fun rememberMarkdownState (
78
+ lookupLinks : Boolean = true,
79
+ flavour : MarkdownFlavourDescriptor = GFMFlavourDescriptor (),
80
+ parser : MarkdownParser = MarkdownParser (flavour),
81
+ referenceLinkHandler : ReferenceLinkHandler = ReferenceLinkHandlerImpl (),
82
+ block : suspend () -> String ,
83
+ ): MarkdownState {
84
+ // Create an initial state with empty content
85
+ val initialInput = remember(lookupLinks, flavour, parser, referenceLinkHandler) {
86
+ Input (
87
+ content = " " ,
88
+ lookupLinks = lookupLinks,
89
+ flavour = flavour,
90
+ parser = parser,
91
+ referenceLinkHandler = referenceLinkHandler,
92
+ )
93
+ }
94
+ val state = remember(block, initialInput) {
95
+ MarkdownStateImpl (initialInput)
96
+ }
97
+
98
+ // Launch a coroutine to fetch the content and update the state
99
+ LaunchedEffect (state) {
100
+ try {
101
+ val content = block()
102
+ val input = Input (
103
+ content = content,
104
+ lookupLinks = lookupLinks,
105
+ flavour = flavour,
106
+ parser = parser,
107
+ referenceLinkHandler = referenceLinkHandler,
108
+ )
109
+ state.updateInput(input)
110
+ state.parse()
111
+ } catch (e: Throwable ) {
112
+ state.setError(e)
113
+ }
114
+ }
115
+
116
+ return state
117
+ }
118
+
119
+ /* *
120
+ * Interface for a state that handles the parsing of markdown content with the [MarkdownParser].
121
+ */
122
+ @Stable
123
+ interface MarkdownState {
124
+ /* * The current state of the markdown parsing */
125
+ val state: StateFlow <State >
126
+
127
+ /* * The links found in the markdown content */
128
+ val links: StateFlow <Map <String , String ?>>
129
+
130
+ /* *
131
+ * Parses the markdown content asynchronously using the Default dispatcher.
132
+ * When a result is available it will be emitted to the [state] flow.
133
+ */
134
+ suspend fun parse (): State
135
+ }
136
+
137
+ /* *
138
+ * Implementation of [MarkdownState] that executes the parsing of the markdown content with the [MarkdownParser] asynchronously.
60
139
*/
61
140
@Stable
62
- class MarkdownState (
63
- val input : Input ,
64
- ) {
141
+ internal class MarkdownStateImpl (
142
+ private var input : Input ,
143
+ ) : MarkdownState {
65
144
private val stateFlow: MutableStateFlow <State > = MutableStateFlow (State .Loading (input.referenceLinkHandler))
66
- val state: StateFlow <State > = stateFlow.asStateFlow()
145
+ override val state: StateFlow <State > = stateFlow.asStateFlow()
67
146
68
147
private val linkStateFlow: MutableStateFlow <Map <String , String ?>> = MutableStateFlow (emptyMap())
69
- val links: StateFlow <Map <String , String ?>> = linkStateFlow.asStateFlow()
148
+ override val links: StateFlow <Map <String , String ?>> = linkStateFlow.asStateFlow()
149
+
150
+ /* *
151
+ * Updates the input for this markdown state.
152
+ * This is used when loading content dynamically.
153
+ *
154
+ * @param newInput The new input to use for parsing.
155
+ */
156
+ internal fun updateInput (newInput : Input ) {
157
+ input = newInput
158
+ stateFlow.value = State .Loading (input.referenceLinkHandler)
159
+ }
160
+
161
+ /* *
162
+ * Sets an error state for this markdown state.
163
+ * This is used when an error occurs during dynamic content loading.
164
+ *
165
+ * @param error The error that occurred.
166
+ */
167
+ internal fun setError (error : Throwable ) {
168
+ stateFlow.value = State .Error (error, input.referenceLinkHandler)
169
+ }
70
170
71
171
/* *
72
172
* Parses the markdown content asynchronously using the Default dispatcher.
73
173
* When a result is available it will be emitted to the [state] flow.
74
174
*/
75
- suspend fun parse (): State = withContext(Dispatchers .Default ) {
175
+ override suspend fun parse (): State = withContext(Dispatchers .Default ) {
76
176
parseBlocking()
77
177
}
78
178
@@ -117,7 +217,7 @@ fun parseMarkdownFlow(
117
217
referenceLinkHandler : ReferenceLinkHandler = ReferenceLinkHandlerImpl (),
118
218
) = flow {
119
219
emit(State .Loading (referenceLinkHandler))
120
- val markdownState = MarkdownState (
220
+ val markdownState = MarkdownStateImpl (
121
221
Input (
122
222
content = content,
123
223
lookupLinks = lookupLinks,
0 commit comments