@@ -67,7 +67,6 @@ class CodeView extends StatefulWidget {
67
67
Key ('debuggerCodeViewVerticalScrollbarKey' );
68
68
69
69
static double get rowHeight => scaleByFontFactor (20.0 );
70
- static double get assumedCharacterWidth => scaleByFontFactor (16.0 );
71
70
72
71
final CodeViewController codeViewController;
73
72
final DebuggerController ? debuggerController;
@@ -116,6 +115,9 @@ class _CodeViewState extends State<CodeView>
116
115
verticalController = LinkedScrollControllerGroup ();
117
116
gutterController = verticalController.addAndGet ();
118
117
textController = verticalController.addAndGet ();
118
+ if (widget.codeViewController.showProfileInformation.value) {
119
+ profileController = verticalController.addAndGet ();
120
+ }
119
121
horizontalController = ScrollController ();
120
122
_lastScriptRef = widget.scriptRef;
121
123
@@ -238,14 +240,6 @@ class _CodeViewState extends State<CodeView>
238
240
_lastScriptRef = scriptRef;
239
241
}
240
242
241
- void _onPressed (int line) {
242
- final onSelected = widget.onSelected;
243
- final script = scriptRef;
244
- if (onSelected != null && script != null ) {
245
- onSelected (script, line);
246
- }
247
- }
248
-
249
243
@override
250
244
Widget build (BuildContext context) {
251
245
if (parsedScript == null ) {
@@ -334,13 +328,6 @@ class _CodeViewState extends State<CodeView>
334
328
}
335
329
}
336
330
337
- // Apply the log change-of-base formula, then add 16dp padding for every
338
- // digit in the maximum number of lines.
339
- final gutterWidth = CodeView .assumedCharacterWidth * 1.5 +
340
- CodeView .assumedCharacterWidth *
341
- (defaultEpsilon + math.log (math.max (lines.length, 100 )) / math.ln10)
342
- .truncateToDouble ();
343
-
344
331
_updateScrollPosition (animate: false );
345
332
346
333
final contentBuilder = (context, ScriptRef ? script) {
@@ -364,60 +351,24 @@ class _CodeViewState extends State<CodeView>
364
351
final pausedFrame = frame == null
365
352
? null
366
353
: (frame.scriptRef == scriptRef ? frame : null );
367
- final currentParsedScript = parsedScript;
368
- final lineCount = widget.lineRange? .size ?? lines.length;
369
- final lineOffset = (widget.lineRange? .begin ?? 1 ) - 1 ;
370
- final sourceReport = currentParsedScript? .sourceReport ??
371
- const ProcessedSourceReport .empty ();
372
354
return Row (
373
355
children: [
374
- DualValueListenableBuilder <
375
- List <BreakpointAndSourcePosition >, bool >(
376
- firstListenable:
377
- breakpointManager.breakpointsWithLocation,
378
- secondListenable:
379
- widget.codeViewController.showCodeCoverage,
380
- builder: (context, breakpoints, showCodeCoverage, _) {
381
- return Gutter (
382
- gutterWidth: gutterWidth,
383
- scrollController: gutterController,
384
- lineCount: lineCount,
385
- lineOffset: lineOffset,
386
- pausedFrame: pausedFrame,
387
- breakpoints: breakpoints
388
- .where ((bp) => bp.scriptRef == scriptRef)
389
- .toList (),
390
- executableLines:
391
- currentParsedScript? .executableLines ??
392
- const < int > {},
393
- sourceReport: sourceReport,
394
- onPressed: _onPressed,
395
- // Disable dots for possible breakpoint locations.
396
- allowInteraction:
397
- ! (widget.debuggerController? .isSystemIsolate ??
398
- false ),
399
- showCodeCoverage: showCodeCoverage,
400
- );
401
- },
402
- ),
403
- const SizedBox (width: denseSpacing),
404
356
ValueListenableBuilder <bool >(
405
357
valueListenable:
406
358
widget.codeViewController.showProfileInformation,
407
359
builder: (context, showProfileInformation, _) {
408
- if (! showProfileInformation) {
409
- return const SizedBox ();
410
- }
411
- return Row (
412
- children: [
413
- ProfileInformationGutter (
414
- scrollController: profileController! ,
415
- lineCount: lineCount,
416
- lineOffset: lineOffset,
417
- sourceReport: sourceReport,
418
- ),
419
- const SizedBox (width: denseSpacing),
420
- ],
360
+ return Gutters (
361
+ scriptRef: script,
362
+ gutterController: gutterController,
363
+ profileController: profileController,
364
+ codeViewController: widget.codeViewController,
365
+ debuggerController: widget.debuggerController,
366
+ lines: lines,
367
+ lineRange: widget.lineRange,
368
+ onSelected: widget.onSelected,
369
+ pausedFrame: pausedFrame,
370
+ parsedScript: parsedScript,
371
+ showProfileInformation: showProfileInformation,
421
372
);
422
373
},
423
374
),
@@ -603,71 +554,98 @@ class ProfileInformationGutter extends StatelessWidget {
603
554
604
555
@override
605
556
Widget build (BuildContext context) {
606
- final theme = Theme .of (context);
607
- final gutterWidth = CodeView .assumedCharacterWidth * 10 + denseSpacing;
608
- return Container (
609
- width: gutterWidth,
610
- decoration: BoxDecoration (
611
- border: Border (right: defaultBorderSide (theme)),
612
- color: Theme .of (context).titleSolidBackgroundColor,
557
+ // Gutter width accounts for:
558
+ // - a maximum of 16 characters of text (e.g., '100.00 %' x 2)
559
+ // - Spacing for the vertical divider
560
+ final gutterWidth = assumedMonospaceCharacterWidth * 16 + denseSpacing;
561
+ return OutlineDecoration .onlyRight (
562
+ child: Container (
563
+ width: gutterWidth,
564
+ decoration: BoxDecoration (
565
+ color: Theme .of (context).titleSolidBackgroundColor,
566
+ ),
567
+ child: Stack (
568
+ children: [
569
+ Column (
570
+ children: [
571
+ const _ProfileInformationGutterHeader (
572
+ totalTimeTooltip: totalTimeTooltip,
573
+ selfTimeTooltip: selfTimeTooltip,
574
+ ),
575
+ Expanded (
576
+ child: ListView .builder (
577
+ controller: scrollController,
578
+ itemExtent: CodeView .rowHeight,
579
+ itemCount: lineCount,
580
+ itemBuilder: (context, index) {
581
+ final lineNum = lineOffset + index + 1 ;
582
+ final data = sourceReport.profilerEntries[lineNum];
583
+ if (data == null ) {
584
+ return const SizedBox ();
585
+ }
586
+ return ProfileInformationGutterItem (
587
+ lineNumber: lineNum,
588
+ profilerData: data,
589
+ );
590
+ },
591
+ ),
592
+ ),
593
+ ],
594
+ ),
595
+ const Center (
596
+ child: VerticalDivider (),
597
+ ),
598
+ ],
599
+ ),
613
600
),
614
- child: Stack (
601
+ );
602
+ }
603
+ }
604
+
605
+ class _ProfileInformationGutterHeader extends StatelessWidget {
606
+ const _ProfileInformationGutterHeader ({
607
+ required this .totalTimeTooltip,
608
+ required this .selfTimeTooltip,
609
+ });
610
+
611
+ final String totalTimeTooltip;
612
+ final String selfTimeTooltip;
613
+
614
+ @override
615
+ Widget build (BuildContext context) {
616
+ return Container (
617
+ height: CodeView .rowHeight,
618
+ child: Column (
615
619
children: [
616
- Column (
617
- children: [
618
- Container (
619
- height: CodeView .rowHeight,
620
- child: Row (
621
- crossAxisAlignment: CrossAxisAlignment .stretch,
622
- mainAxisAlignment: MainAxisAlignment .spaceAround,
623
- children: const [
624
- Expanded (
625
- child: DevToolsTooltip (
626
- message: totalTimeTooltip,
627
- child: Text (
628
- 'Total %' ,
629
- textAlign: TextAlign .center,
630
- ),
631
- ),
632
- ),
633
- SizedBox (width: denseSpacing),
634
- Expanded (
635
- child: DevToolsTooltip (
636
- message: selfTimeTooltip,
637
- child: Text (
638
- 'Self %' ,
639
- textAlign: TextAlign .center,
640
- ),
641
- ),
620
+ Expanded (
621
+ child: Row (
622
+ crossAxisAlignment: CrossAxisAlignment .stretch,
623
+ mainAxisAlignment: MainAxisAlignment .spaceAround,
624
+ children: [
625
+ Expanded (
626
+ child: DevToolsTooltip (
627
+ message: totalTimeTooltip,
628
+ child: const Text (
629
+ 'Total %' ,
630
+ textAlign: TextAlign .center,
642
631
),
643
- ] ,
632
+ ) ,
644
633
),
645
- ),
646
- const Divider (
647
- height: 0 ,
648
- ),
649
- Expanded (
650
- child: ListView .builder (
651
- controller: scrollController,
652
- itemExtent: CodeView .rowHeight,
653
- itemCount: lineCount,
654
- itemBuilder: (context, index) {
655
- final lineNum = lineOffset + index + 1 ;
656
- final data = sourceReport.profilerEntries[lineNum];
657
- if (data == null ) {
658
- return const SizedBox ();
659
- }
660
- return ProfileInformationGutterItem (
661
- lineNumber: lineNum,
662
- profilerData: data,
663
- );
664
- },
634
+ const SizedBox (width: denseSpacing),
635
+ Expanded (
636
+ child: DevToolsTooltip (
637
+ message: selfTimeTooltip,
638
+ child: const Text (
639
+ 'Self %' ,
640
+ textAlign: TextAlign .center,
641
+ ),
642
+ ),
665
643
),
666
- ) ,
667
- ] ,
644
+ ] ,
645
+ ) ,
668
646
),
669
- const Center (
670
- child : VerticalDivider () ,
647
+ const Divider (
648
+ height : 0 ,
671
649
),
672
650
],
673
651
),
@@ -735,14 +713,12 @@ class ProfilePercentageItem extends StatelessWidget {
735
713
message: hoverText,
736
714
child: Container (
737
715
color: color,
738
- child: Container (
739
- margin: const EdgeInsets .symmetric (
740
- horizontal: densePadding,
741
- ),
742
- child: Text (
743
- '${percentage .toStringAsFixed (2 )} %' ,
744
- textAlign: TextAlign .end,
745
- ),
716
+ padding: const EdgeInsets .symmetric (
717
+ horizontal: densePadding,
718
+ ),
719
+ child: Text (
720
+ '${percentage .toStringAsFixed (2 )} %' ,
721
+ textAlign: TextAlign .end,
746
722
),
747
723
),
748
724
);
@@ -751,6 +727,98 @@ class ProfilePercentageItem extends StatelessWidget {
751
727
752
728
typedef IntCallback = void Function (int value);
753
729
730
+ class Gutters extends StatelessWidget {
731
+ const Gutters ({
732
+ required this .scriptRef,
733
+ this .debuggerController,
734
+ required this .codeViewController,
735
+ required this .lines,
736
+ required this .lineRange,
737
+ required this .gutterController,
738
+ required this .showProfileInformation,
739
+ required this .profileController,
740
+ required this .parsedScript,
741
+ this .pausedFrame,
742
+ this .onSelected,
743
+ });
744
+
745
+ final ScriptRef ? scriptRef;
746
+ final DebuggerController ? debuggerController;
747
+ final CodeViewController codeViewController;
748
+ final ScrollController gutterController;
749
+ final ScrollController ? profileController;
750
+ final StackFrameAndSourcePosition ? pausedFrame;
751
+ final List <TextSpan > lines;
752
+ final LineRange ? lineRange;
753
+ final ParsedScript ? parsedScript;
754
+ final void Function (ScriptRef scriptRef, int line)? onSelected;
755
+ final bool showProfileInformation;
756
+
757
+ @override
758
+ Widget build (BuildContext context) {
759
+ final lineCount = lineRange? .size ?? lines.length;
760
+ final lineOffset = (lineRange? .begin ?? 1 ) - 1 ;
761
+ final sourceReport =
762
+ parsedScript? .sourceReport ?? const ProcessedSourceReport .empty ();
763
+
764
+ // Apply the log change-of-base formula to get the max number of digits in
765
+ // a line number. Add a character width space for:
766
+ // - each character in the longest line number
767
+ // - one for the breakpoint dot
768
+ // - two for the paused arrow
769
+ final gutterWidth = assumedMonospaceCharacterWidth * 4 +
770
+ assumedMonospaceCharacterWidth *
771
+ (defaultEpsilon + math.log (math.max (lines.length, 100 )) / math.ln10)
772
+ .truncateToDouble ();
773
+
774
+ return Row (
775
+ children: [
776
+ DualValueListenableBuilder <List <BreakpointAndSourcePosition >, bool >(
777
+ firstListenable: breakpointManager.breakpointsWithLocation,
778
+ secondListenable: codeViewController.showCodeCoverage,
779
+ builder: (context, breakpoints, showCodeCoverage, _) {
780
+ return Gutter (
781
+ gutterWidth: gutterWidth,
782
+ scrollController: gutterController,
783
+ lineCount: lineCount,
784
+ lineOffset: lineOffset,
785
+ pausedFrame: pausedFrame,
786
+ breakpoints:
787
+ breakpoints.where ((bp) => bp.scriptRef == scriptRef).toList (),
788
+ executableLines: parsedScript? .executableLines ?? const < int > {},
789
+ sourceReport: sourceReport,
790
+ onPressed: _onPressed,
791
+ // Disable dots for possible breakpoint locations.
792
+ allowInteraction: ! (debuggerController? .isSystemIsolate ?? false ),
793
+ showCodeCoverage: showCodeCoverage,
794
+ );
795
+ },
796
+ ),
797
+ const SizedBox (width: denseSpacing),
798
+ ! showProfileInformation
799
+ ? const SizedBox ()
800
+ : Padding (
801
+ padding: const EdgeInsets .only (right: denseSpacing),
802
+ child: ProfileInformationGutter (
803
+ scrollController: profileController! ,
804
+ lineCount: lineCount,
805
+ lineOffset: lineOffset,
806
+ sourceReport: sourceReport,
807
+ ),
808
+ ),
809
+ ],
810
+ );
811
+ }
812
+
813
+ void _onPressed (int line) {
814
+ final onSelectedLocal = onSelected! ;
815
+ final script = scriptRef;
816
+ if (onSelected != null && script != null ) {
817
+ onSelectedLocal (script, line);
818
+ }
819
+ }
820
+ }
821
+
754
822
class Gutter extends StatelessWidget {
755
823
const Gutter ({
756
824
required this .gutterWidth,
0 commit comments