@@ -255,23 +255,294 @@ class QuotationNode extends BlockContentNode {
255
255
}
256
256
257
257
class CodeBlockNode extends BlockContentNode {
258
- // TODO(#191) represent the code-highlighting style spans in CodeBlockNode
259
- const CodeBlockNode ({super .debugHtmlNode, required this .text});
258
+ const CodeBlockNode ({super .debugHtmlNode, required this .spans});
259
+
260
+ final List <CodeBlockSpanNode > spans;
261
+
262
+ @override
263
+ List <DiagnosticsNode > debugDescribeChildren () {
264
+ return spans
265
+ .map ((node) => node.toDiagnosticsNode ())
266
+ .toList ();
267
+ }
268
+ }
269
+
270
+ // List of all the tokens that pygments can emit for syntax highlighting
271
+ // https://github.com/pygments/pygments/blob/d0acfff1121f9ee3696b01a9077ebe9990216634/pygments/token.py#L123-L214
272
+ //
273
+ // Note: If you update this list make sure to update the permalink
274
+ // and the `tryFromString` function below.
275
+ enum CodeBlockSpanToken {
276
+ /// No styles applied
277
+ text,
278
+ // 'hll'
279
+ highlightedLines,
280
+ /// 'w'
281
+ whitespace,
282
+ /// 'esc'
283
+ escape,
284
+ /// 'err'
285
+ error,
286
+ /// 'x'
287
+ other,
288
+ /// 'k'
289
+ keyword,
290
+ /// 'kc'
291
+ keywordConstant,
292
+ /// 'kd'
293
+ keywordDeclaration,
294
+ /// 'kn'
295
+ keywordNamespace,
296
+ /// 'kp'
297
+ keywordPseudo,
298
+ /// 'kr'
299
+ keywordReserved,
300
+ /// 'kt'
301
+ keywordType,
302
+ /// 'n'
303
+ name,
304
+ /// 'na'
305
+ nameAttribute,
306
+ /// 'nb'
307
+ nameBuiltin,
308
+ /// 'bp'
309
+ nameBuiltinPseudo,
310
+ /// 'nc'
311
+ nameClass,
312
+ /// 'no'
313
+ nameConstant,
314
+ /// 'nd'
315
+ nameDecorator,
316
+ /// 'ni'
317
+ nameEntity,
318
+ /// 'ne'
319
+ nameException,
320
+ /// 'nf'
321
+ nameFunction,
322
+ /// 'fm'
323
+ nameFunctionMagic,
324
+ /// 'py'
325
+ nameProperty,
326
+ /// 'nl'
327
+ nameLabel,
328
+ /// 'nn'
329
+ nameNamespace,
330
+ /// 'nx'
331
+ nameOther,
332
+ /// 'nt'
333
+ nameTag,
334
+ /// 'nv'
335
+ nameVariable,
336
+ /// 'vc'
337
+ nameVariableClass,
338
+ /// 'vg'
339
+ nameVariableGlobal,
340
+ /// 'vi'
341
+ nameVariableInstance,
342
+ /// 'vm'
343
+ nameVariableMagic,
344
+ /// 'l'
345
+ literal,
346
+ /// 'ld'
347
+ literalDate,
348
+ /// 's'
349
+ string,
350
+ /// 'sa'
351
+ stringAffix,
352
+ /// 'sb'
353
+ stringBacktick,
354
+ /// 'sc'
355
+ stringChar,
356
+ /// 'dl'
357
+ stringDelimiter,
358
+ /// 'sd'
359
+ stringDoc,
360
+ /// 's2'
361
+ stringDouble,
362
+ /// 'se'
363
+ stringEscape,
364
+ /// 'sh'
365
+ stringHeredoc,
366
+ /// 'si'
367
+ stringInterpol,
368
+ /// 'sx'
369
+ stringOther,
370
+ /// 'sr'
371
+ stringRegex,
372
+ /// 's1'
373
+ stringSingle,
374
+ /// 'ss'
375
+ stringSymbol,
376
+ /// 'm'
377
+ number,
378
+ /// 'mb'
379
+ numberBin,
380
+ /// 'mf'
381
+ numberFloat,
382
+ /// 'mh'
383
+ numberHex,
384
+ /// 'mi'
385
+ numberInteger,
386
+ /// 'il'
387
+ numberIntegerLong,
388
+ /// 'mo'
389
+ numberOct,
390
+ /// 'o'
391
+ operator ,
392
+ /// 'ow'
393
+ operatorWord,
394
+ /// 'p'
395
+ punctuation,
396
+ /// 'pm'
397
+ punctuationMarker,
398
+ /// 'c'
399
+ comment,
400
+ /// 'ch'
401
+ commentHashbang,
402
+ /// 'cm'
403
+ commentMultiline,
404
+ /// 'cp'
405
+ commentPreproc,
406
+ /// 'cpf'
407
+ commentPreprocFile,
408
+ /// 'c1'
409
+ commentSingle,
410
+ /// 'cs'
411
+ commentSpecial,
412
+ /// 'g'
413
+ generic,
414
+ /// 'gd'
415
+ genericDeleted,
416
+ /// 'ge'
417
+ genericEmph,
418
+ /// 'gr'
419
+ genericError,
420
+ /// 'gh'
421
+ genericHeading,
422
+ /// 'gi'
423
+ genericInserted,
424
+ /// 'go'
425
+ genericOutput,
426
+ /// 'gp'
427
+ genericPrompt,
428
+ /// 'gs'
429
+ genericStrong,
430
+ /// 'gu'
431
+ genericSubheading,
432
+ /// 'ges'
433
+ genericEmphStrong,
434
+ /// 'gt'
435
+ genericTraceback,
436
+ }
437
+
438
+ extension CodeBlockSpanTokenExt on CodeBlockSpanToken {
439
+ static CodeBlockSpanToken tryFromClassName (String className) {
440
+ return switch (className) {
441
+ 'hll' => CodeBlockSpanToken .highlightedLines,
442
+ 'w' => CodeBlockSpanToken .whitespace,
443
+ 'esc' => CodeBlockSpanToken .escape,
444
+ 'err' => CodeBlockSpanToken .error,
445
+ 'x' => CodeBlockSpanToken .other,
446
+ 'k' => CodeBlockSpanToken .keyword,
447
+ 'kc' => CodeBlockSpanToken .keywordConstant,
448
+ 'kd' => CodeBlockSpanToken .keywordDeclaration,
449
+ 'kn' => CodeBlockSpanToken .keywordNamespace,
450
+ 'kp' => CodeBlockSpanToken .keywordPseudo,
451
+ 'kr' => CodeBlockSpanToken .keywordReserved,
452
+ 'kt' => CodeBlockSpanToken .keywordType,
453
+ 'n' => CodeBlockSpanToken .name,
454
+ 'na' => CodeBlockSpanToken .nameAttribute,
455
+ 'nb' => CodeBlockSpanToken .nameBuiltin,
456
+ 'bp' => CodeBlockSpanToken .nameBuiltinPseudo,
457
+ 'nc' => CodeBlockSpanToken .nameClass,
458
+ 'no' => CodeBlockSpanToken .nameConstant,
459
+ 'nd' => CodeBlockSpanToken .nameDecorator,
460
+ 'ni' => CodeBlockSpanToken .nameEntity,
461
+ 'ne' => CodeBlockSpanToken .nameException,
462
+ 'nf' => CodeBlockSpanToken .nameFunction,
463
+ 'fm' => CodeBlockSpanToken .nameFunctionMagic,
464
+ 'py' => CodeBlockSpanToken .nameProperty,
465
+ 'nl' => CodeBlockSpanToken .nameLabel,
466
+ 'nn' => CodeBlockSpanToken .nameNamespace,
467
+ 'nx' => CodeBlockSpanToken .nameOther,
468
+ 'nt' => CodeBlockSpanToken .nameTag,
469
+ 'nv' => CodeBlockSpanToken .nameVariable,
470
+ 'vc' => CodeBlockSpanToken .nameVariableClass,
471
+ 'vg' => CodeBlockSpanToken .nameVariableGlobal,
472
+ 'vi' => CodeBlockSpanToken .nameVariableInstance,
473
+ 'vm' => CodeBlockSpanToken .nameVariableMagic,
474
+ 'l' => CodeBlockSpanToken .literal,
475
+ 'ld' => CodeBlockSpanToken .literalDate,
476
+ 's' => CodeBlockSpanToken .string,
477
+ 'sa' => CodeBlockSpanToken .stringAffix,
478
+ 'sb' => CodeBlockSpanToken .stringBacktick,
479
+ 'sc' => CodeBlockSpanToken .stringChar,
480
+ 'dl' => CodeBlockSpanToken .stringDelimiter,
481
+ 'sd' => CodeBlockSpanToken .stringDoc,
482
+ 's2' => CodeBlockSpanToken .stringDouble,
483
+ 'se' => CodeBlockSpanToken .stringEscape,
484
+ 'sh' => CodeBlockSpanToken .stringHeredoc,
485
+ 'si' => CodeBlockSpanToken .stringInterpol,
486
+ 'sx' => CodeBlockSpanToken .stringOther,
487
+ 'sr' => CodeBlockSpanToken .stringRegex,
488
+ 's1' => CodeBlockSpanToken .stringSingle,
489
+ 'ss' => CodeBlockSpanToken .stringSymbol,
490
+ 'm' => CodeBlockSpanToken .number,
491
+ 'mb' => CodeBlockSpanToken .numberBin,
492
+ 'mf' => CodeBlockSpanToken .numberFloat,
493
+ 'mh' => CodeBlockSpanToken .numberHex,
494
+ 'mi' => CodeBlockSpanToken .numberInteger,
495
+ 'il' => CodeBlockSpanToken .numberIntegerLong,
496
+ 'mo' => CodeBlockSpanToken .numberOct,
497
+ 'o' => CodeBlockSpanToken .operator ,
498
+ 'ow' => CodeBlockSpanToken .operatorWord,
499
+ 'p' => CodeBlockSpanToken .punctuation,
500
+ 'pm' => CodeBlockSpanToken .punctuationMarker,
501
+ 'c' => CodeBlockSpanToken .comment,
502
+ 'ch' => CodeBlockSpanToken .commentHashbang,
503
+ 'cm' => CodeBlockSpanToken .commentMultiline,
504
+ 'cp' => CodeBlockSpanToken .commentPreproc,
505
+ 'cpf' => CodeBlockSpanToken .commentPreprocFile,
506
+ 'c1' => CodeBlockSpanToken .commentSingle,
507
+ 'cs' => CodeBlockSpanToken .commentSpecial,
508
+ 'g' => CodeBlockSpanToken .generic,
509
+ 'gd' => CodeBlockSpanToken .genericDeleted,
510
+ 'ge' => CodeBlockSpanToken .genericEmph,
511
+ 'gr' => CodeBlockSpanToken .genericError,
512
+ 'gh' => CodeBlockSpanToken .genericHeading,
513
+ 'gi' => CodeBlockSpanToken .genericInserted,
514
+ 'go' => CodeBlockSpanToken .genericOutput,
515
+ 'gp' => CodeBlockSpanToken .genericPrompt,
516
+ 'gs' => CodeBlockSpanToken .genericStrong,
517
+ 'gu' => CodeBlockSpanToken .genericSubheading,
518
+ 'ges' => CodeBlockSpanToken .genericEmphStrong,
519
+ 'gt' => CodeBlockSpanToken .genericTraceback,
520
+ _ => CodeBlockSpanToken .text,
521
+ };
522
+ }
523
+ }
524
+
525
+ class CodeBlockSpanNode extends BlockContentNode {
526
+ const CodeBlockSpanNode ({super .debugHtmlNode, required this .text, required this .tokenType});
260
527
261
528
final String text;
529
+ final CodeBlockSpanToken tokenType;
262
530
263
531
@override
264
532
bool operator == (Object other) {
265
- return other is CodeBlockNode && other.text == text;
533
+ return other is CodeBlockSpanNode
534
+ && other.text == text
535
+ && other.tokenType == tokenType;
266
536
}
267
537
268
538
@override
269
- int get hashCode => Object .hash ('CodeBlockNode ' , text);
539
+ int get hashCode => Object .hash ('CodeBlockSpanNode ' , text, tokenType );
270
540
271
541
@override
272
542
void debugFillProperties (DiagnosticPropertiesBuilder properties) {
273
543
super .debugFillProperties (properties);
274
544
properties.add (StringProperty ('text' , text));
545
+ properties.add (EnumProperty ('tokenType' , tokenType));
275
546
}
276
547
}
277
548
@@ -658,7 +929,7 @@ class _ZulipContentParser {
658
929
return UnimplementedBlockContentNode (htmlNode: divElement);
659
930
}
660
931
661
- final buffer = StringBuffer () ;
932
+ final spans = < CodeBlockSpanNode > [] ;
662
933
for (int i = 0 ; i < mainElement.nodes.length; i++ ) {
663
934
final child = mainElement.nodes[i];
664
935
if (child is dom.Text ) {
@@ -668,17 +939,28 @@ class _ZulipContentParser {
668
939
// [Text] widget, that would make a trailing blank line. So cut it out.
669
940
text = text.replaceFirst (RegExp (r'\n$' ), '' );
670
941
}
671
- buffer.write (text);
942
+ if (text.isNotEmpty) {
943
+ spans.add (CodeBlockSpanNode (text: text, tokenType: CodeBlockSpanToken .text));
944
+ }
672
945
} else if (child is dom.Element && child.localName == 'span' ) {
673
- // TODO(#191) parse the code-highlighting spans, to style them
674
- buffer.write (child.text);
946
+ if (child.className.isEmpty) {
947
+ spans.add (CodeBlockSpanNode (text: child.text, tokenType: CodeBlockSpanToken .text));
948
+ } else {
949
+ // Further code is based on an assumption that there will
950
+ // be only single class associated with the span element.
951
+ if (child.className.split (' ' ).length > 1 ) {
952
+ return UnimplementedBlockContentNode (htmlNode: divElement);
953
+ }
954
+
955
+ final spanClass = CodeBlockSpanTokenExt .tryFromClassName (child.className);
956
+ spans.add (CodeBlockSpanNode (text: child.text, tokenType: spanClass));
957
+ }
675
958
} else {
676
959
return UnimplementedBlockContentNode (htmlNode: divElement);
677
960
}
678
961
}
679
- final text = buffer.toString ();
680
962
681
- return CodeBlockNode (text : text , debugHtmlNode: debugHtmlNode);
963
+ return CodeBlockNode (spans : spans , debugHtmlNode: debugHtmlNode);
682
964
}
683
965
684
966
BlockContentNode parseImageNode (dom.Element divElement) {
0 commit comments