Skip to content

Commit fb44680

Browse files
content: Add styles for each token type
1 parent 739faee commit fb44680

File tree

3 files changed

+291
-2
lines changed

3 files changed

+291
-2
lines changed

lib/widgets/code_block.dart

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import 'package:flutter/material.dart';
2+
3+
import '../model/code_block.dart';
4+
5+
// Highlighted code block styles adapted from:
6+
// https://github.com/zulip/zulip/blob/213387249e7ba7772084411b22d8cef64b135dd0/web/styles/pygments.css
7+
8+
// .hll { background-color: hsl(60deg 100% 90%); }
9+
final _kCodeBlockStyleHll = TextStyle(backgroundColor: const HSLColor.fromAHSL(1, 60, 1, 0.90).toColor());
10+
11+
// .c { color: hsl(180deg 33% 37%); font-style: italic; }
12+
final _kCodeBlockStyleC = TextStyle(color: const HSLColor.fromAHSL(1, 180, 0.33, 0.37).toColor(), fontStyle: FontStyle.italic);
13+
14+
// TODO: Borders are hard in TextSpan, see the comment in `_buildInlineCode`
15+
// So, using a lighter background color for now (precisely it's
16+
// the text color used in web app in `.err` class in dark mode)
17+
//
18+
// .err { border: 1px solid hsl(0deg 100% 50%); }
19+
const _kCodeBlockStyleErr = TextStyle(backgroundColor: Color(0xffe2706e));
20+
21+
// .k { color: hsl(332deg 70% 38%); }
22+
final _kCodeBlockStyleK = TextStyle(color: const HSLColor.fromAHSL(1, 332, 0.7, 0.38).toColor());
23+
24+
// .o { color: hsl(332deg 70% 38%); }
25+
final _kCodeBlockStyleO = TextStyle(color: const HSLColor.fromAHSL(1, 332, 0.7, 0.38).toColor());
26+
27+
// .cm { color: hsl(180deg 33% 37%); font-style: italic; }
28+
final _kCodeBlockStyleCm = TextStyle(color: const HSLColor.fromAHSL(1, 180, 0.33, 0.37).toColor(), fontStyle: FontStyle.italic);
29+
30+
// .cp { color: hsl(38deg 100% 36%); }
31+
final _kCodeBlockStyleCp = TextStyle(color: const HSLColor.fromAHSL(1, 38, 1, 0.36).toColor());
32+
33+
// .c1 { color: hsl(0deg 0% 67%); font-style: italic; }
34+
final _kCodeBlockStyleC1 = TextStyle(color: const HSLColor.fromAHSL(1, 0, 0, 0.67).toColor(), fontStyle: FontStyle.italic);
35+
36+
// .cs { color: hsl(180deg 33% 37%); font-style: italic; }
37+
final _kCodeBlockStyleCs = TextStyle(color: const HSLColor.fromAHSL(1, 180, 0.33, 0.37).toColor(), fontStyle: FontStyle.italic);
38+
39+
// .gd { color: hsl(0deg 100% 31%); }
40+
final _kCodeBlockStyleGd = TextStyle(color: const HSLColor.fromAHSL(1, 0, 1, 0.31).toColor());
41+
42+
// .ge { font-style: italic; }
43+
const _kCodeBlockStyleGe = TextStyle(fontStyle: FontStyle.italic);
44+
45+
// .gr { color: hsl(0deg 100% 50%); }
46+
final _kCodeBlockStyleGr = TextStyle(color: const HSLColor.fromAHSL(1, 0, 1, 0.50).toColor());
47+
48+
// .gh { color: hsl(240deg 100% 25%); font-weight: bold; }
49+
final _kCodeBlockStyleGh = TextStyle(color: const HSLColor.fromAHSL(1, 240, 1, 0.25).toColor(), fontWeight: FontWeight.bold);
50+
51+
// .gi { color: hsl(120deg 100% 31%); }
52+
final _kCodeBlockStyleGi = TextStyle(color: const HSLColor.fromAHSL(1, 120, 1, 0.31).toColor());
53+
54+
// .go { color: hsl(0deg 0% 50%); }
55+
final _kCodeBlockStyleGo = TextStyle(color: const HSLColor.fromAHSL(1, 0, 0, 0.50).toColor());
56+
57+
// .gp { color: hsl(240deg 100% 25%); font-weight: bold; }
58+
final _kCodeBlockStyleGp = TextStyle(color: const HSLColor.fromAHSL(1, 240, 1, 0.25).toColor(), fontWeight: FontWeight.bold);
59+
60+
// .gs { font-weight: bold; }
61+
const _kCodeBlockStyleGs = TextStyle(fontWeight: FontWeight.bold);
62+
63+
// .gu { color: hsl(300deg 100% 25%); font-weight: bold; }
64+
final _kCodeBlockStyleGu = TextStyle(color: const HSLColor.fromAHSL(1, 300, 1, 0.25).toColor(), fontWeight: FontWeight.bold);
65+
66+
// .gt { color: hsl(221deg 100% 40%); }
67+
final _kCodeBlockStyleGt = TextStyle(color: const HSLColor.fromAHSL(1, 221, 1, 0.40).toColor());
68+
69+
// .kc { color: hsl(332deg 70% 38%); font-weight: bold; }
70+
final _kCodeBlockStyleKc = TextStyle(color: const HSLColor.fromAHSL(1, 332, 0.70, 0.38).toColor(), fontWeight: FontWeight.bold);
71+
72+
// .kd { color: hsl(332deg 70% 38%); }
73+
final _kCodeBlockStyleKd = TextStyle(color: const HSLColor.fromAHSL(1, 332, 0.70, 0.38).toColor());
74+
75+
// .kn { color: hsl(332deg 70% 38%); font-weight: bold; }
76+
final _kCodeBlockStyleKn = TextStyle(color: const HSLColor.fromAHSL(1, 332, 0.70, 0.38).toColor(), fontWeight: FontWeight.bold);
77+
78+
// .kp { color: hsl(332deg 70% 38%); }
79+
final _kCodeBlockStyleKp = TextStyle(color: const HSLColor.fromAHSL(1, 332, 0.70, 0.38).toColor());
80+
81+
// .kr { color: hsl(332deg 70% 38%); font-weight: bold; }
82+
final _kCodeBlockStyleKr = TextStyle(color: const HSLColor.fromAHSL(1, 332, 0.70, 0.38).toColor(), fontWeight: FontWeight.bold);
83+
84+
// .kt { color: hsl(332deg 70% 38%); }
85+
final _kCodeBlockStyleKt = TextStyle(color: const HSLColor.fromAHSL(1, 332, 0.70, 0.38).toColor());
86+
87+
// .m { color: hsl(0deg 0% 40%); }
88+
final _kCodeBlockStyleM = TextStyle(color: const HSLColor.fromAHSL(1, 0, 0, 0.40).toColor());
89+
90+
// .s { color: hsl(86deg 57% 40%); }
91+
final _kCodeBlockStyleS = TextStyle(color: const HSLColor.fromAHSL(1, 86, 0.57, 0.40).toColor());
92+
93+
// .na { color: hsl(71deg 55% 36%); }
94+
final _kCodeBlockStyleNa = TextStyle(color: const HSLColor.fromAHSL(1, 71, 0.55, 0.36).toColor());
95+
96+
// .nb { color: hsl(195deg 100% 35%); }
97+
final _kCodeBlockStyleNb = TextStyle(color: const HSLColor.fromAHSL(1, 195, 1, 0.35).toColor());
98+
99+
// .nc { color: hsl(264deg 27% 50%); font-weight: bold; }
100+
final _kCodeBlockStyleNc = TextStyle(color: const HSLColor.fromAHSL(1, 264, 0.27, 0.50).toColor(), fontWeight: FontWeight.bold);
101+
102+
// .no { color: hsl(0deg 100% 26%); }
103+
final _kCodeBlockStyleNo = TextStyle(color: const HSLColor.fromAHSL(1, 0, 1, 0.26).toColor());
104+
105+
// .nd { color: hsl(276deg 100% 56%); }
106+
final _kCodeBlockStyleNd = TextStyle(color: const HSLColor.fromAHSL(1, 276, 1, 0.56).toColor());
107+
108+
// .ni { color: hsl(0deg 0% 60%); font-weight: bold; }
109+
final _kCodeBlockStyleNi = TextStyle(color: const HSLColor.fromAHSL(1, 0, 0, 0.60).toColor(), fontWeight: FontWeight.bold);
110+
111+
// .ne { color: hsl(2deg 62% 52%); font-weight: bold; }
112+
final _kCodeBlockStyleNe = TextStyle(color: const HSLColor.fromAHSL(1, 2, 0.62, 0.52).toColor(), fontWeight: FontWeight.bold);
113+
114+
// .nf { color: hsl(264deg 27% 50%); }
115+
final _kCodeBlockStyleNf = TextStyle(color: const HSLColor.fromAHSL(1, 264, 0.27, 0.50).toColor());
116+
117+
// .nl { color: hsl(60deg 100% 31%); }
118+
final _kCodeBlockStyleNl = TextStyle(color: const HSLColor.fromAHSL(1, 60, 1, 0.31).toColor());
119+
120+
// .nn { color: hsl(264deg 27% 50%); font-weight: bold; }
121+
final _kCodeBlockStyleNn = TextStyle(color: const HSLColor.fromAHSL(1, 264, 0.27, 0.50).toColor(), fontWeight: FontWeight.bold);
122+
123+
// .nt { color: hsl(120deg 100% 25%); font-weight: bold; }
124+
final _kCodeBlockStyleNt = TextStyle(color: const HSLColor.fromAHSL(1, 120, 1, 0.25).toColor(), fontWeight: FontWeight.bold);
125+
126+
// .nv { color: hsl(241deg 68% 28%); }
127+
final _kCodeBlockStyleNv = TextStyle(color: const HSLColor.fromAHSL(1, 241, 0.68, 0.28).toColor());
128+
129+
// .nx { color: hsl(0deg 0% 26%); }
130+
final _kCodeBlockStyleNx = TextStyle(color: const HSLColor.fromAHSL(1, 0, 0, 0.26).toColor());
131+
132+
// .ow { color: hsl(276deg 100% 56%); font-weight: bold; }
133+
final _kCodeBlockStyleOw = TextStyle(color: const HSLColor.fromAHSL(1, 276, 1, 0.56).toColor(), fontWeight: FontWeight.bold);
134+
135+
// .w { color: hsl(0deg 0% 73%); }
136+
final _kCodeBlockStyleW = TextStyle(color: const HSLColor.fromAHSL(1, 0, 0, 0.73).toColor());
137+
138+
// .mf { color: hsl(195deg 100% 35%); }
139+
final _kCodeBlockStyleMf = TextStyle(color: const HSLColor.fromAHSL(1, 195, 1, 0.35).toColor());
140+
141+
// .mh { color: hsl(195deg 100% 35%); }
142+
final _kCodeBlockStyleMh = TextStyle(color: const HSLColor.fromAHSL(1, 195, 1, 0.35).toColor());
143+
144+
// .mi { color: hsl(195deg 100% 35%); }
145+
final _kCodeBlockStyleMi = TextStyle(color: const HSLColor.fromAHSL(1, 195, 1, 0.35).toColor());
146+
147+
// .mo { color: hsl(195deg 100% 35%); }
148+
final _kCodeBlockStyleMo = TextStyle(color: const HSLColor.fromAHSL(1, 195, 1, 0.35).toColor());
149+
150+
// .sb { color: hsl(86deg 57% 40%); }
151+
final _kCodeBlockStyleSb = TextStyle(color: const HSLColor.fromAHSL(1, 86, 0.57, 0.40).toColor());
152+
153+
// .sc { color: hsl(86deg 57% 40%); }
154+
final _kCodeBlockStyleSc = TextStyle(color: const HSLColor.fromAHSL(1, 86, 0.57, 0.40).toColor());
155+
156+
// .sd { color: hsl(86deg 57% 40%); font-style: italic; }
157+
final _kCodeBlockStyleSd = TextStyle(color: const HSLColor.fromAHSL(1, 86, 0.57, 0.40).toColor(), fontStyle: FontStyle.italic);
158+
159+
// .s2 { color: hsl(225deg 71% 33%); }
160+
final _kCodeBlockStyleS2 = TextStyle(color: const HSLColor.fromAHSL(1, 225, 0.71, 0.33).toColor());
161+
162+
// .se { color: hsl(26deg 69% 43%); font-weight: bold; }
163+
final _kCodeBlockStyleSe = TextStyle(color: const HSLColor.fromAHSL(1, 26, 0.69, 0.43).toColor(), fontWeight: FontWeight.bold);
164+
165+
// .sh { color: hsl(86deg 57% 40%); }
166+
final _kCodeBlockStyleSh = TextStyle(color: const HSLColor.fromAHSL(1, 86, 0.57, 0.40).toColor());
167+
168+
// .si { color: hsl(336deg 38% 56%); font-weight: bold; }
169+
final _kCodeBlockStyleSi = TextStyle(color: const HSLColor.fromAHSL(1, 336, 0.38, 0.56).toColor(), fontWeight: FontWeight.bold);
170+
171+
// .sx { color: hsl(120deg 100% 25%); }
172+
final _kCodeBlockStyleSx = TextStyle(color: const HSLColor.fromAHSL(1, 120, 1, 0.25).toColor());
173+
174+
// .sr { color: hsl(189deg 54% 49%); }
175+
final _kCodeBlockStyleSr = TextStyle(color: const HSLColor.fromAHSL(1, 189, 0.54, 0.49).toColor());
176+
177+
// .s1 { color: hsl(86deg 57% 40%); }
178+
final _kCodeBlockStyleS1 = TextStyle(color: const HSLColor.fromAHSL(1, 86, 0.57, 0.40).toColor());
179+
180+
// .ss { color: hsl(241deg 68% 28%); }
181+
final _kCodeBlockStyleSs = TextStyle(color: const HSLColor.fromAHSL(1, 241, 0.68, 0.28).toColor());
182+
183+
// .bp { color: hsl(120deg 100% 25%); }
184+
final _kCodeBlockStyleBp = TextStyle(color: const HSLColor.fromAHSL(1, 120, 1, 0.25).toColor());
185+
186+
// .vc { color: hsl(241deg 68% 28%); }
187+
final _kCodeBlockStyleVc = TextStyle(color: const HSLColor.fromAHSL(1, 241, 0.68, 0.28).toColor());
188+
189+
// .vg { color: hsl(241deg 68% 28%); }
190+
final _kCodeBlockStyleVg = TextStyle(color: const HSLColor.fromAHSL(1, 241, 0.68, 0.28).toColor());
191+
192+
// .vi { color: hsl(241deg 68% 28%); }
193+
final _kCodeBlockStyleVi = TextStyle(color: const HSLColor.fromAHSL(1, 241, 0.68, 0.28).toColor());
194+
195+
// .il { color: hsl(0deg 0% 40%); }
196+
final _kCodeBlockStyleIl = TextStyle(color: const HSLColor.fromAHSL(1, 0, 0, 0.40).toColor());
197+
198+
TextStyle? codeBlockTextStyle(CodeBlockSpanType type) {
199+
return switch (type) {
200+
CodeBlockSpanType.text => null, // A span with type of text is always unstyled.
201+
CodeBlockSpanType.highlightedLines => _kCodeBlockStyleHll,
202+
CodeBlockSpanType.comment => _kCodeBlockStyleC,
203+
CodeBlockSpanType.error => _kCodeBlockStyleErr,
204+
CodeBlockSpanType.keyword => _kCodeBlockStyleK,
205+
CodeBlockSpanType.operator => _kCodeBlockStyleO,
206+
CodeBlockSpanType.commentMultiline => _kCodeBlockStyleCm,
207+
CodeBlockSpanType.commentPreproc => _kCodeBlockStyleCp,
208+
CodeBlockSpanType.commentSingle => _kCodeBlockStyleC1,
209+
CodeBlockSpanType.commentSpecial => _kCodeBlockStyleCs,
210+
CodeBlockSpanType.genericDeleted => _kCodeBlockStyleGd,
211+
CodeBlockSpanType.genericEmph => _kCodeBlockStyleGe,
212+
CodeBlockSpanType.genericError => _kCodeBlockStyleGr,
213+
CodeBlockSpanType.genericHeading => _kCodeBlockStyleGh,
214+
CodeBlockSpanType.genericInserted => _kCodeBlockStyleGi,
215+
CodeBlockSpanType.genericOutput => _kCodeBlockStyleGo,
216+
CodeBlockSpanType.genericPrompt => _kCodeBlockStyleGp,
217+
CodeBlockSpanType.genericStrong => _kCodeBlockStyleGs,
218+
CodeBlockSpanType.genericSubheading => _kCodeBlockStyleGu,
219+
CodeBlockSpanType.genericTraceback => _kCodeBlockStyleGt,
220+
CodeBlockSpanType.keywordConstant => _kCodeBlockStyleKc,
221+
CodeBlockSpanType.keywordDeclaration => _kCodeBlockStyleKd,
222+
CodeBlockSpanType.keywordNamespace => _kCodeBlockStyleKn,
223+
CodeBlockSpanType.keywordPseudo => _kCodeBlockStyleKp,
224+
CodeBlockSpanType.keywordReserved => _kCodeBlockStyleKr,
225+
CodeBlockSpanType.keywordType => _kCodeBlockStyleKt,
226+
CodeBlockSpanType.number => _kCodeBlockStyleM,
227+
CodeBlockSpanType.string => _kCodeBlockStyleS,
228+
CodeBlockSpanType.nameAttribute => _kCodeBlockStyleNa,
229+
CodeBlockSpanType.nameBuiltin => _kCodeBlockStyleNb,
230+
CodeBlockSpanType.nameClass => _kCodeBlockStyleNc,
231+
CodeBlockSpanType.nameConstant => _kCodeBlockStyleNo,
232+
CodeBlockSpanType.nameDecorator => _kCodeBlockStyleNd,
233+
CodeBlockSpanType.nameEntity => _kCodeBlockStyleNi,
234+
CodeBlockSpanType.nameException => _kCodeBlockStyleNe,
235+
CodeBlockSpanType.nameFunction => _kCodeBlockStyleNf,
236+
CodeBlockSpanType.nameLabel => _kCodeBlockStyleNl,
237+
CodeBlockSpanType.nameNamespace => _kCodeBlockStyleNn,
238+
CodeBlockSpanType.nameTag => _kCodeBlockStyleNt,
239+
CodeBlockSpanType.nameVariable => _kCodeBlockStyleNv,
240+
CodeBlockSpanType.nameOther => _kCodeBlockStyleNx,
241+
CodeBlockSpanType.operatorWord => _kCodeBlockStyleOw,
242+
CodeBlockSpanType.whitespace => _kCodeBlockStyleW,
243+
CodeBlockSpanType.numberFloat => _kCodeBlockStyleMf,
244+
CodeBlockSpanType.numberHex => _kCodeBlockStyleMh,
245+
CodeBlockSpanType.numberInteger => _kCodeBlockStyleMi,
246+
CodeBlockSpanType.numberOct => _kCodeBlockStyleMo,
247+
CodeBlockSpanType.stringBacktick => _kCodeBlockStyleSb,
248+
CodeBlockSpanType.stringChar => _kCodeBlockStyleSc,
249+
CodeBlockSpanType.stringDoc => _kCodeBlockStyleSd,
250+
CodeBlockSpanType.stringDouble => _kCodeBlockStyleS2,
251+
CodeBlockSpanType.stringEscape => _kCodeBlockStyleSe,
252+
CodeBlockSpanType.stringHeredoc => _kCodeBlockStyleSh,
253+
CodeBlockSpanType.stringInterpol => _kCodeBlockStyleSi,
254+
CodeBlockSpanType.stringOther => _kCodeBlockStyleSx,
255+
CodeBlockSpanType.stringRegex => _kCodeBlockStyleSr,
256+
CodeBlockSpanType.stringSingle => _kCodeBlockStyleS1,
257+
CodeBlockSpanType.stringSymbol => _kCodeBlockStyleSs,
258+
CodeBlockSpanType.nameBuiltinPseudo => _kCodeBlockStyleBp,
259+
CodeBlockSpanType.nameVariableClass => _kCodeBlockStyleVc,
260+
CodeBlockSpanType.nameVariableGlobal => _kCodeBlockStyleVg,
261+
CodeBlockSpanType.nameVariableInstance => _kCodeBlockStyleVi,
262+
CodeBlockSpanType.numberIntegerLong => _kCodeBlockStyleIl,
263+
_ => null, // not every token is styled
264+
};
265+
}

lib/widgets/content.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import '../api/model/model.dart';
88
import '../model/binding.dart';
99
import '../model/content.dart';
1010
import '../model/store.dart';
11+
import 'code_block.dart';
1112
import 'dialog.dart';
12-
import 'store.dart';
1313
import 'lightbox.dart';
14+
import 'store.dart';
1415
import 'text.dart';
1516

1617
/// The font size for message content in a plain unstyled paragraph.
@@ -274,7 +275,7 @@ class CodeBlock extends StatelessWidget {
274275
}
275276

276277
InlineSpan _buildNode(CodeBlockSpanNode node) {
277-
return TextSpan(text: node.text);
278+
return TextSpan(text: node.text, style: codeBlockTextStyle(node.type));
278279
}
279280
}
280281

test/widgets/content_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,29 @@ import 'dialog_checks.dart';
1818
void main() {
1919
TestZulipBinding.ensureInitialized();
2020

21+
group("CodeBlock", () {
22+
Future<void> prepareContent(WidgetTester tester, String html) async {
23+
await tester.pumpWidget(MaterialApp(home: BlockContentList(nodes: parseContent(html).nodes)));
24+
}
25+
26+
testWidgets('without syntax highlighting', (WidgetTester tester) async {
27+
// "```\nverb\natim\n```"
28+
await prepareContent(tester,
29+
'<div class="codehilite"><pre><span></span><code>verb\natim\n</code></pre></div>');
30+
tester.widget(find.text('verb\natim'));
31+
});
32+
33+
testWidgets('with syntax highlighting', (WidgetTester tester) async {
34+
// "```dart\nclass A {}\n```"
35+
await prepareContent(tester,
36+
'<div class="codehilite" data-code-language="Dart"><pre>'
37+
'<span></span><code><span class="kd">class</span><span class="w"> </span>'
38+
'<span class="nc">A</span><span class="w"> </span><span class="p">{}</span>'
39+
'\n</code></pre></div>');
40+
tester.widget(find.text('class A {}'));
41+
});
42+
});
43+
2144
group('LinkNode interactions', () {
2245
const expectedModeAndroid = LaunchMode.externalApplication;
2346

0 commit comments

Comments
 (0)