Skip to content

Commit

Permalink
Add support for system isolates to isolate selector while in VM devel…
Browse files Browse the repository at this point in the history
…oper mode (#2947)

- Removed 'Isolate' from the isolate selector, replaced with 'forking'
  icon for isolates and 'gear' for system isolates.
- Added tooltip for the isolate selector: 'Selected Isolate'.
- Added 'No source available' path for CodeView.
- Disable various debugger controls for system isolates.
  • Loading branch information
bkonyi authored Apr 28, 2021
1 parent e00b2e2 commit 3fee03d
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 108 deletions.
196 changes: 110 additions & 86 deletions packages/devtools_app/lib/src/debugger/codeview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -194,42 +194,44 @@ class _CodeViewState extends State<CodeView>

// Ensure the syntax highlighter has been initialized.
// TODO(bkonyi): process source for highlighting on a separate thread.
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,
),
],
);
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
Expand All @@ -245,62 +247,75 @@ class _CodeViewState extends State<CodeView>
child: Column(
children: [
buildCodeviewTitle(theme),
DefaultTextStyle(
style: theme.fixedFontStyle,
child: Expanded(
child: Scrollbar(
controller: textController,
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,
);
},
),
const SizedBox(width: denseSpacing),
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return Lines(
constraints: constraints,
scrollController: textController,
lines: lines,
if (lines.isNotEmpty)
DefaultTextStyle(
style: theme.fixedFontStyle,
child: Expanded(
child: Scrollbar(
controller: textController,
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,
searchMatchesNotifier:
widget.controller.searchMatches,
activeSearchMatchNotifier:
widget.controller.activeSearchMatch,
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) {
return Lines(
constraints: constraints,
scrollController: textController,
lines: lines,
pausedFrame: pausedFrame,
searchMatchesNotifier:
widget.controller.searchMatches,
activeSearchMatchNotifier:
widget.controller.activeSearchMatch,
);
},
),
),
],
);
},
),
),
),
)
else
Expanded(
child: Center(
child: Text(
'No source available',
style: theme.textTheme.subtitle1,
),
),
),
),
],
),
);
Expand Down Expand Up @@ -418,6 +433,7 @@ class Gutter extends StatelessWidget {
@required this.breakpoints,
@required this.executableLines,
@required this.onPressed,
@required this.allowInteraction,
});

final double gutterWidth;
Expand All @@ -427,6 +443,7 @@ class Gutter extends StatelessWidget {
final List<BreakpointAndSourcePosition> breakpoints;
final Set<int> executableLines;
final IntCallback onPressed;
final bool allowInteraction;

@override
Widget build(BuildContext context) {
Expand All @@ -447,6 +464,7 @@ class Gutter extends StatelessWidget {
isBreakpoint: bpLineSet.contains(lineNum),
isExecutable: executableLines.contains(lineNum),
isPausedHere: pausedFrame?.line == lineNum,
allowInteraction: allowInteraction,
);
},
),
Expand All @@ -462,6 +480,7 @@ class GutterItem extends StatelessWidget {
@required this.isExecutable,
@required this.isPausedHere,
@required this.onPressed,
@required this.allowInteraction,
}) : super(key: key);

final int lineNumber;
Expand All @@ -470,6 +489,8 @@ class GutterItem extends StatelessWidget {

final bool isExecutable;

final bool allowInteraction;

/// Whether the execution point is currently paused here.
final bool isPausedHere;

Expand All @@ -489,14 +510,17 @@ class GutterItem extends StatelessWidget {

return InkWell(
onTap: onPressed,
// Force usage of default mouse pointer when gutter interaction is
// disabled.
mouseCursor: allowInteraction ? null : SystemMouseCursors.basic,
child: Container(
height: CodeView.rowHeight,
padding: const EdgeInsets.only(right: 4.0),
child: Stack(
alignment: AlignmentDirectional.centerStart,
fit: StackFit.expand,
children: [
if (isExecutable || isBreakpoint)
if (allowInteraction && (isExecutable || isBreakpoint))
Align(
alignment: Alignment.centerLeft,
child: SizedBox(
Expand Down
22 changes: 16 additions & 6 deletions packages/devtools_app/lib/src/debugger/controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class _DebuggingControlsState extends State<DebuggingControls>
final isPaused = controller.isPaused.value;
final resuming = controller.resuming.value;
final hasStackFrames = controller.stackFramesWithLocation.value.isNotEmpty;
final canStep = isPaused && !resuming && hasStackFrames;
final isSystemIsolate = controller.isSystemIsolate;
final canStep = isPaused && !resuming && hasStackFrames && !isSystemIsolate;
return SizedBox(
height: defaultButtonHeight,
child: Row(
Expand All @@ -60,20 +61,26 @@ class _DebuggingControlsState extends State<DebuggingControls>
@required bool isPaused,
@required bool resuming,
}) {
final isSystemIsolate = controller.isSystemIsolate;
return RoundedOutlinedBorder(
child: Row(
children: [
DebuggerButton(
title: 'Pause',
icon: Codicons.debugPause,
autofocus: true,
onPressed: isPaused ? null : controller.pause,
// Disable when paused or selected isolate is a system isolate.
onPressed: (isPaused || isSystemIsolate) ? null : controller.pause,
),
LeftBorder(
child: DebuggerButton(
title: 'Resume',
icon: Codicons.debugContinue,
onPressed: (isPaused && !resuming) ? controller.resume : null,
// Enable while paused + not resuming and selected isolate is not
// a system isolate.
onPressed: ((isPaused && !resuming) && !isSystemIsolate)
? controller.resume
: null,
),
),
],
Expand Down Expand Up @@ -143,9 +150,12 @@ class BreakOnExceptionsControl extends StatelessWidget {
builder: (BuildContext context, String modeId, _) {
return RoundedDropDownButton<ExceptionMode>(
value: ExceptionMode.from(modeId),
onChanged: (ExceptionMode mode) {
controller.setExceptionPauseMode(mode.id);
},
// Cannot set exception pause mode for system isolates.
onChanged: controller.isSystemIsolate
? null
: (ExceptionMode mode) {
controller.setExceptionPauseMode(mode.id);
},
isDense: true,
items: [
for (var mode in ExceptionMode.modes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ class DebuggerController extends DisposableController
ValueListenable<List<ConsoleLine>> get stdio => _stdio;

IsolateRef isolateRef;
bool get isSystemIsolate => isolateRef?.isSystemIsolate ?? false;

/// Clears the contents of stdio.
void clearStdio() {
Expand Down Expand Up @@ -1339,7 +1340,7 @@ class ParsedScript {
@required this.highlighter,
@required this.executableLines,
}) : assert(script != null),
lines = script.source.split('\n').toList();
lines = (script.source?.split('\n') ?? const []).toList();

final Script script;

Expand Down
8 changes: 8 additions & 0 deletions packages/devtools_app/lib/src/debugger/debugger_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import '../common_widgets.dart';
import '../config_specific/host_platform/host_platform.dart';
import '../dialogs.dart';
import '../flex_split_column.dart';
import '../globals.dart';
import '../listenable.dart';
import '../screen.dart';
import '../split.dart';
Expand Down Expand Up @@ -107,6 +108,13 @@ class DebuggerScreenBodyState extends State<DebuggerScreenBody>
}

Future<void> _toggleBreakpoint(ScriptRef script, int line) async {
// The VM doesn't support debugging for system isolates and will crash on
// a failed assert in debug mode. Disable the toggle breakpoint
// functionality for system isolates.
if (serviceManager.isolateManager.selectedIsolate.isSystemIsolate) {
return;
}

final bp = controller.breakpointsWithLocation.value.firstWhere((bp) {
return bp.scriptRef == script && bp.line == line;
}, orElse: () => null);
Expand Down
25 changes: 24 additions & 1 deletion packages/devtools_app/lib/src/service_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,12 @@ class ServiceConnectionManager {

_connectedState.value = const ConnectedState(true);

await _isolateManager._initIsolates(vm.isolates);
final isolates = [
...vm.isolates,
if (preferences.vmDeveloperModeEnabled.value) ...vm.systemIsolates,
];

await _isolateManager.init(isolates);

final streamIds = [
EventStreams.kDebug,
Expand Down Expand Up @@ -441,6 +446,24 @@ class IsolateManager extends Disposer {
final _mainIsolate = ValueNotifier<IsolateRef>(null);
ValueListenable<IsolateRef> get mainIsolate => _mainIsolate;

Future<void> init(List<IsolateRef> isolates) async {
// Re-initialize isolates when VM developer mode is enabled/disabled to
// display/hide system isolates.
addAutoDisposeListener(preferences.vmDeveloperModeEnabled, () async {
final vmDeveloperModeEnabled = preferences.vmDeveloperModeEnabled.value;
final vm = await serviceManager.service.getVM();
final isolates = [
...vm.isolates,
if (vmDeveloperModeEnabled) ...vm.systemIsolates,
];
if (selectedIsolate.isSystemIsolate && !vmDeveloperModeEnabled) {
selectIsolate(_isolates.first.id);
}
await _initIsolates(isolates);
});
await _initIsolates(isolates);
}

/// Return a unique, monotonically increasing number for this Isolate.
int isolateIndex(IsolateRef isolateRef) {
if (!_isolateIndexMap.containsKey(isolateRef.id)) {
Expand Down
Loading

0 comments on commit 3fee03d

Please sign in to comment.