in packages/devtools_app/lib/src/debugger/codeview.dart [232:400]
Widget buildCodeArea(BuildContext context) {
final theme = Theme.of(context);
final lines = <TextSpan>[];
// Ensure the syntax highlighter has been initialized.
// TODO(bkonyi): process source for highlighting on a separate thread.
if (parsedScript.script.source != null) {
if (parsedScript.script.source.length < 500000 &&
parsedScript.highlighter != null) {
final highlighted = parsedScript.highlighter.highlight(context);
// Look for [TextSpan]s which only contain '\n' to manually break the
// output from the syntax highlighter into individual lines.
var currentLine = <TextSpan>[];
highlighted.visitChildren((span) {
currentLine.add(span);
if (span.toPlainText() == '\n') {
lines.add(
TextSpan(
style: theme.fixedFontStyle,
children: currentLine,
),
);
currentLine = <TextSpan>[];
}
return true;
});
lines.add(
TextSpan(
style: theme.fixedFontStyle,
children: currentLine,
),
);
} else {
lines.addAll(
[
for (final line in parsedScript.script.source.split('\n'))
TextSpan(
style: theme.fixedFontStyle,
text: line,
),
],
);
}
}
// Apply the log change-of-base formula, then add 16dp padding for every
// digit in the maximum number of lines.
final gutterWidth = CodeView.assumedCharacterWidth * 1.5 +
CodeView.assumedCharacterWidth *
(defaultEpsilon + math.log(math.max(lines.length, 100)) / math.ln10)
.truncateToDouble();
_updateScrollPosition(animate: false);
return HistoryViewport(
history: widget.controller.scriptsHistory,
generateTitle: (script) => script.uri,
onTitleTap: () => widget.controller.toggleFileOpenerVisibility(true),
controls: [
ScriptPopupMenu(widget.controller),
ScriptHistoryPopupMenu(
itemBuilder: _buildScriptMenuFromHistory,
onSelected: (scriptRef) {
widget.controller.showScriptLocation(ScriptLocation(scriptRef));
},
enabled: widget.controller.scriptsHistory.hasScripts,
),
],
contentBuilder: (context, script) {
if (lines.isNotEmpty) {
return DefaultTextStyle(
style: theme.fixedFontStyle,
child: Expanded(
child: Scrollbar(
key: CodeView.debuggerCodeViewVerticalScrollbarKey,
controller: textController,
isAlwaysShown: true,
// Only listen for vertical scroll notifications (ignore those
// from the nested horizontal SingleChildScrollView):
notificationPredicate: (ScrollNotification notification) =>
notification.depth == 1,
child: ValueListenableBuilder<StackFrameAndSourcePosition>(
valueListenable: widget.controller.selectedStackFrame,
builder: (context, frame, _) {
final pausedFrame = frame == null
? null
: (frame.scriptRef == scriptRef ? frame : null);
return Row(
children: [
ValueListenableBuilder<
List<BreakpointAndSourcePosition>>(
valueListenable:
widget.controller.breakpointsWithLocation,
builder: (context, breakpoints, _) {
return Gutter(
gutterWidth: gutterWidth,
scrollController: gutterController,
lineCount: lines.length,
pausedFrame: pausedFrame,
breakpoints: breakpoints
.where((bp) => bp.scriptRef == scriptRef)
.toList(),
executableLines: parsedScript.executableLines,
onPressed: _onPressed,
// Disable dots for possible breakpoint locations.
allowInteraction:
!widget.controller.isSystemIsolate,
);
},
),
const SizedBox(width: denseSpacing),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
final double fileWidth = calculateTextSpanWidth(
findLongestTextSpan(lines),
);
return Scrollbar(
key: CodeView
.debuggerCodeViewHorizontalScrollbarKey,
isAlwaysShown: true,
controller: horizontalController,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: horizontalController,
child: SizedBox(
height: constraints.maxHeight,
width: fileWidth,
child: Lines(
height: constraints.maxHeight,
debugController: widget.controller,
scrollController: textController,
lines: lines,
pausedFrame: pausedFrame,
searchMatchesNotifier:
widget.controller.searchMatches,
activeSearchMatchNotifier:
widget.controller.activeSearchMatch,
),
),
),
);
},
),
),
],
);
},
),
),
),
);
} else {
return Expanded(
child: Center(
child: Text(
'No source available',
style: theme.textTheme.subtitle1,
),
),
);
}
},
);
}