Skip to content

Commit 5fb877b

Browse files
authored
feat: make it possible to create a custom Markdown code theme (#4343)
* parseMarkdownCodeTheme util * MarkdownCustomCodeTheme dataclass * enhance the handling of special cases
1 parent 4fb3cc1 commit 5fb877b

File tree

4 files changed

+103
-18
lines changed

4 files changed

+103
-18
lines changed

packages/flet/lib/src/controls/markdown.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'dart:convert';
22

33
import 'package:flutter/material.dart';
4-
import 'package:flutter_highlight/theme_map.dart';
54
import 'package:flutter_markdown/flutter_markdown.dart';
65
import 'package:markdown/markdown.dart' as md;
76

@@ -63,6 +62,8 @@ class MarkdownControl extends StatelessWidget with FletStoreMixin {
6362
.copyWith(fontFamily: "monospace"));
6463
var mdStyleSheet = parseMarkdownStyleSheet(
6564
control, "mdStyleSheet", Theme.of(context), pageArgs);
65+
var codeTheme =
66+
parseMarkdownCodeTheme(control, "codeTheme", Theme.of(context));
6667
Widget markdown = MarkdownBody(
6768
data: value,
6869
selectable: selectable,
@@ -71,8 +72,7 @@ class MarkdownControl extends StatelessWidget with FletStoreMixin {
7172
: getBaseUri(pageArgs.pageUri!).toString(),
7273
extensionSet: extensionSet,
7374
builders: {
74-
'code': CodeElementBuilder(
75-
codeTheme.toLowerCase(), codeStyleSheet, selectable),
75+
'code': CodeElementBuilder(codeTheme, codeStyleSheet, selectable),
7676
},
7777
styleSheet: mdStyleSheet,
7878
imageBuilder: (Uri uri, String? title, String? alt) {
@@ -142,7 +142,7 @@ class MarkdownControl extends StatelessWidget with FletStoreMixin {
142142
}
143143

144144
class CodeElementBuilder extends MarkdownElementBuilder {
145-
final String codeTheme;
145+
final Map<String, TextStyle> codeTheme;
146146
final MarkdownStyleSheet mdStyleSheet;
147147
final bool selectable;
148148

@@ -175,7 +175,7 @@ class CodeElementBuilder extends MarkdownElementBuilder {
175175

176176
// Specify highlight theme
177177
// All available themes are listed in `themes` folder
178-
theme: themeMap[codeTheme] ?? {},
178+
theme: codeTheme,
179179

180180
// Specify padding
181181
padding: mdStyleSheet.codeblockPadding,

packages/flet/lib/src/utils/markdown.dart

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:convert';
22

33
import 'package:flutter/material.dart';
4+
import 'package:flutter_highlight/theme_map.dart';
45
import 'package:flutter_markdown/flutter_markdown.dart';
56
import 'package:markdown/markdown.dart' as md;
67

@@ -29,19 +30,51 @@ md.ExtensionSet? parseMarkdownExtensionSet(String? value,
2930
}
3031
}
3132

33+
Map<String, TextStyle> parseMarkdownCodeTheme(
34+
Control control,
35+
String propName,
36+
ThemeData theme,
37+
) {
38+
final v = control.attrString(propName);
39+
if (v == null) {
40+
return {};
41+
}
42+
dynamic j = json.decode(v);
43+
if (j is String) {
44+
return themeMap[j.toLowerCase()] ?? {};
45+
} else if (j is Map<String, dynamic>) {
46+
String transformKey(String key) {
47+
switch (key) {
48+
case 'class_name':
49+
return 'class';
50+
case 'built_in':
51+
return key;
52+
default:
53+
return key.replaceAll('_', '-');
54+
}
55+
}
56+
57+
final resultMap =
58+
j.map((key, value) => MapEntry(key, textStyleFromJson(theme, value)));
59+
resultMap.removeWhere(
60+
(key, value) => value == null); // remove entries with null values
61+
return resultMap.map((key, value) => MapEntry(transformKey(key), value!));
62+
}
63+
return {};
64+
}
65+
3266
MarkdownStyleSheet? parseMarkdownStyleSheet(Control control, String propName,
3367
ThemeData theme, PageArgsModel? pageArgs) {
34-
dynamic j;
35-
var v = control.attrString(propName, null);
68+
var v = control.attrString(propName);
3669
if (v == null) {
3770
return null;
3871
}
39-
j = json.decode(v);
72+
dynamic j = json.decode(v);
4073
return markdownStyleSheetFromJson(theme, j, pageArgs);
4174
}
4275

43-
MarkdownStyleSheet markdownStyleSheetFromJson(ThemeData theme,
44-
Map<String, dynamic> j, PageArgsModel? pageArgs) {
76+
MarkdownStyleSheet markdownStyleSheetFromJson(
77+
ThemeData theme, Map<String, dynamic> j, PageArgsModel? pageArgs) {
4578
TextStyle? parseTextStyle(String propName) {
4679
return j[propName] != null ? textStyleFromJson(theme, j[propName]) : null;
4780
}
@@ -119,13 +152,13 @@ MarkdownStyleSheet markdownStyleSheetFromJson(ThemeData theme,
119152
horizontalRuleDecoration: boxDecorationFromJSON(
120153
theme, j["horizontal_rule_decoration"], pageArgs) ??
121154
BoxDecoration(
122-
border: Border(
123-
top: BorderSide(
124-
width: 5.0,
125-
color: theme.dividerColor,
126-
),
127-
),
155+
border: Border(
156+
top: BorderSide(
157+
width: 5.0,
158+
color: theme.dividerColor,
128159
),
160+
),
161+
),
129162
blockquoteAlign:
130163
parseWrapAlignment(j["blockquote_alignment"], WrapAlignment.start)!,
131164
codeblockAlign:

sdk/python/packages/flet/src/flet/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@
226226
from flet.core.markdown import (
227227
Markdown,
228228
MarkdownCodeTheme,
229+
MarkdownCustomCodeTheme,
229230
MarkdownExtensionSet,
230231
MarkdownSelectionChangeCause,
231232
MarkdownSelectionChangeEvent,

sdk/python/packages/flet/src/flet/core/markdown.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,57 @@ class MarkdownCodeTheme(Enum):
225225
ZENBURN = "zenburn"
226226

227227

228+
@dataclass
229+
class MarkdownCustomCodeTheme:
230+
addition: Optional[TextStyle] = None
231+
attr: Optional[TextStyle] = None
232+
attribute: Optional[TextStyle] = None
233+
built_in: Optional[TextStyle] = None
234+
builtin_name: Optional[TextStyle] = None
235+
bullet: Optional[TextStyle] = None
236+
class_name: Optional[TextStyle] = None
237+
code: Optional[TextStyle] = None
238+
comment: Optional[TextStyle] = None
239+
deletion: Optional[TextStyle] = None
240+
doctag: Optional[TextStyle] = None
241+
emphasis: Optional[TextStyle] = None
242+
formula: Optional[TextStyle] = None
243+
function: Optional[TextStyle] = None
244+
keyword: Optional[TextStyle] = None
245+
link: Optional[TextStyle] = None
246+
link_label: Optional[TextStyle] = None
247+
literal: Optional[TextStyle] = None
248+
meta: Optional[TextStyle] = None
249+
meta_keyword: Optional[TextStyle] = None
250+
meta_string: Optional[TextStyle] = None
251+
name: Optional[TextStyle] = None
252+
number: Optional[TextStyle] = None
253+
operator: Optional[TextStyle] = None
254+
params: Optional[TextStyle] = None
255+
pattern_match: Optional[TextStyle] = None
256+
quote: Optional[TextStyle] = None
257+
regexp: Optional[TextStyle] = None
258+
root: Optional[TextStyle] = None
259+
section: Optional[TextStyle] = None
260+
selector_attr: Optional[TextStyle] = None
261+
selector_class: Optional[TextStyle] = None
262+
selector_id: Optional[TextStyle] = None
263+
selector_pseudo: Optional[TextStyle] = None
264+
selector_tag: Optional[TextStyle] = None
265+
string: Optional[TextStyle] = None
266+
strong: Optional[TextStyle] = None
267+
stronge: Optional[TextStyle] = None
268+
subst: Optional[TextStyle] = None
269+
subtr: Optional[TextStyle] = None
270+
symbol: Optional[TextStyle] = None
271+
tag: Optional[TextStyle] = None
272+
template_tag: Optional[TextStyle] = None
273+
template_variable: Optional[TextStyle] = None
274+
title: Optional[TextStyle] = None
275+
type: Optional[TextStyle] = None
276+
variable: Optional[TextStyle] = None
277+
278+
228279
class Markdown(ConstrainedControl):
229280
"""
230281
Control for rendering text in markdown format.
@@ -239,7 +290,7 @@ def __init__(
239290
value: Optional[str] = None,
240291
selectable: Optional[bool] = None,
241292
extension_set: Optional[MarkdownExtensionSet] = None,
242-
code_theme: Optional[MarkdownCodeTheme] = None,
293+
code_theme: Optional[Union[MarkdownCodeTheme, MarkdownCustomCodeTheme]] = None,
243294
code_style: Optional[TextStyle] = None,
244295
auto_follow_links: Optional[bool] = None,
245296
shrink_wrap: Optional[bool] = None,
@@ -349,6 +400,7 @@ def before_update(self):
349400
self._set_attr_json("codeStyle", self.__code_style)
350401
self._set_attr_json("codeStyleSheet", self.__code_style_sheet)
351402
self._set_attr_json("mdStyleSheet", self.__md_style_sheet)
403+
self._set_attr_json("codeTheme", self.__code_theme)
352404

353405
def _get_children(self):
354406
if self.__img_error_content is not None:
@@ -437,7 +489,6 @@ def code_theme(self) -> Optional[MarkdownCodeTheme]:
437489
@code_theme.setter
438490
def code_theme(self, value: Optional[MarkdownCodeTheme]):
439491
self.__code_theme = value
440-
self._set_enum_attr("codeTheme", value, MarkdownCodeTheme)
441492

442493
# code_style
443494
@property

0 commit comments

Comments
 (0)