diff --git a/packages/devtools_app/lib/devtools_app.dart b/packages/devtools_app/lib/devtools_app.dart index a4fca7b44a6..732742b5ba1 100644 --- a/packages/devtools_app/lib/devtools_app.dart +++ b/packages/devtools_app/lib/devtools_app.dart @@ -9,7 +9,6 @@ export 'src/charts/treemap.dart'; export 'src/connected_app.dart'; export 'src/console_service.dart'; export 'src/debugger/debugger_controller.dart'; -export 'src/debugger/program_explorer_controller.dart'; export 'src/debugger/span_parser.dart'; export 'src/debugger/syntax_highlighter.dart'; export 'src/error_badge_manager.dart'; diff --git a/packages/devtools_app/lib/src/common_widgets.dart b/packages/devtools_app/lib/src/common_widgets.dart index 6e33b6db7cb..26c18531102 100644 --- a/packages/devtools_app/lib/src/common_widgets.dart +++ b/packages/devtools_app/lib/src/common_widgets.dart @@ -676,7 +676,6 @@ class AreaPaneHeader extends StatelessWidget implements PreferredSizeWidget { this.needsLeftBorder = false, this.leftActions = const [], this.rightActions = const [], - this.leftPadding = defaultSpacing, this.rightPadding = densePadding, this.tall = false, }) : super(key: key); @@ -688,7 +687,6 @@ class AreaPaneHeader extends StatelessWidget implements PreferredSizeWidget { final bool needsLeftBorder; final List leftActions; final List rightActions; - final double leftPadding; final double rightPadding; final bool tall; @@ -707,7 +705,7 @@ class AreaPaneHeader extends StatelessWidget implements PreferredSizeWidget { ), color: theme.titleSolidBackgroundColor, ), - padding: EdgeInsets.only(left: leftPadding, right: rightPadding), + padding: EdgeInsets.only(left: defaultSpacing, right: rightPadding), alignment: Alignment.centerLeft, child: Row( children: [ diff --git a/packages/devtools_app/lib/src/debugger/codeview.dart b/packages/devtools_app/lib/src/debugger/codeview.dart index 673e1a0e25d..c6f2e8e2294 100644 --- a/packages/devtools_app/lib/src/debugger/codeview.dart +++ b/packages/devtools_app/lib/src/debugger/codeview.dart @@ -42,7 +42,6 @@ class CodeView extends StatefulWidget { const CodeView({ Key key, this.controller, - this.initialPosition, this.scriptRef, this.parsedScript, this.onSelected, @@ -58,7 +57,6 @@ class CodeView extends StatefulWidget { static double get assumedCharacterWidth => scaleByFontFactor(16.0); final DebuggerController controller; - final ScriptLocation initialPosition; final ScriptRef scriptRef; final ParsedScript parsedScript; @@ -82,10 +80,6 @@ class _CodeViewState extends State ParsedScript get parsedScript => widget.parsedScript; - // Used to ensure we don't update the scroll position when expanding or - // collapsing the file explorer. - ScriptRef _lastScriptRef; - @override void initState() { super.initState(); @@ -94,16 +88,6 @@ class _CodeViewState extends State gutterController = verticalController.addAndGet(); textController = verticalController.addAndGet(); horizontalController = ScrollController(); - _lastScriptRef = widget.scriptRef; - - if (widget.initialPosition != null) { - final location = widget.initialPosition.location; - // Lines are 1-indexed. Scrolling to line 1 required a scroll position of - // 0. - final lineIndex = location.line - 1; - final scrollPosition = lineIndex * CodeView.rowHeight; - verticalController.jumpTo(scrollPosition); - } addAutoDisposeListener( widget.controller.scriptLocation, @@ -140,8 +124,12 @@ class _CodeViewState extends State } void _updateScrollPosition({bool animate = true}) { - if (widget.controller.scriptLocation.value?.scriptRef?.uri != - scriptRef?.uri) { + if (widget.controller.scriptLocation.value?.scriptRef != scriptRef) { + return; + } + + final location = widget.controller.scriptLocation.value?.location; + if (location?.line == null) { return; } @@ -150,25 +138,6 @@ class _CodeViewState extends State log('LinkedScrollControllerGroup has no attached controllers'); return; } - final location = widget.controller.scriptLocation.value?.location; - if (location?.line == null) { - // Don't scroll to top if we're just rebuilding the code view for the - // same script. - if (_lastScriptRef?.uri != scriptRef?.uri) { - // Default to scrolling to the top of the script. - if (animate) { - verticalController.animateTo( - 0, - duration: longDuration, - curve: defaultCurve, - ); - } else { - verticalController.jumpTo(0); - } - _lastScriptRef = scriptRef; - } - return; - } final position = verticalController.position; final extent = position.extentInside; @@ -176,11 +145,10 @@ class _CodeViewState extends State // TODO(devoncarew): Adjust this so we don't scroll if we're already in the // middle third of the screen. if (parsedScript.lineCount * CodeView.rowHeight > extent) { + // Scroll to the middle of the screen. final lineIndex = location.line - 1; - final scrollPosition = lineIndex * CodeView.rowHeight - - (widget.controller.shouldCenterScrollLocation - ? ((extent - CodeView.rowHeight) / 2) - : 0); + final scrollPosition = + lineIndex * CodeView.rowHeight - (extent - CodeView.rowHeight) / 2; if (animate) { verticalController.animateTo( scrollPosition, @@ -191,7 +159,6 @@ class _CodeViewState extends State verticalController.jumpTo(scrollPosition); } } - _lastScriptRef = scriptRef; } void _onPressed(int line) { @@ -673,7 +640,7 @@ class _LinesState extends State with AutoDisposeMixin { if (isOutOfViewTop || isOutOfViewBottom) { // Scroll this search token to the middle of the view. - final targetOffset = math.max( + final targetOffset = math.max( activeSearch.position.line * CodeView.rowHeight - widget.height / 2, 0.0, ); diff --git a/packages/devtools_app/lib/src/debugger/controls.dart b/packages/devtools_app/lib/src/debugger/controls.dart index 03012e93d1c..0e35670ec44 100644 --- a/packages/devtools_app/lib/src/debugger/controls.dart +++ b/packages/devtools_app/lib/src/debugger/controls.dart @@ -12,6 +12,7 @@ import '../common_widgets.dart'; import '../theme.dart'; import '../ui/label.dart'; import 'debugger_controller.dart'; +import 'scripts.dart'; class DebuggingControls extends StatefulWidget { const DebuggingControls({Key key}) : super(key: key); @@ -117,14 +118,13 @@ class _DebuggingControlsState extends State Widget _librariesButton() { return ValueListenableBuilder( - valueListenable: controller.fileExplorerVisible, + valueListenable: controller.librariesVisible, builder: (context, visible, _) { - const libraryIcon = Icons.insert_chart; return RoundedOutlinedBorder( child: Container( color: visible ? Theme.of(context).highlightColor : null, child: DebuggerButton( - title: 'File Explorer', + title: 'Libraries', icon: libraryIcon, onPressed: controller.toggleLibrariesVisible, ), diff --git a/packages/devtools_app/lib/src/debugger/debugger_controller.dart b/packages/devtools_app/lib/src/debugger/debugger_controller.dart index dabb293c281..8562ad95718 100644 --- a/packages/devtools_app/lib/src/debugger/debugger_controller.dart +++ b/packages/devtools_app/lib/src/debugger/debugger_controller.dart @@ -20,7 +20,6 @@ import '../ui/search.dart'; import '../utils.dart'; import '../vm_service_wrapper.dart'; import 'debugger_model.dart'; -import 'program_explorer_controller.dart'; import 'syntax_highlighter.dart'; // TODO(devoncarew): Add some delayed resume value notifiers (to be used to @@ -41,14 +40,6 @@ class DebuggerController extends DisposableController _showScriptLocation(ScriptLocation(scriptsHistory.current.value)); }; scriptsHistory.current.addListener(_scriptHistoryListener); - addAutoDisposeListener(currentScriptRef, () async { - if (!programExplorerController.initialized.value) { - await programExplorerController.initialize(); - } - if (currentScriptRef.value != null) { - programExplorerController.selectScriptNode(currentScriptRef.value); - } - }); if (_service != null) { initialize(); @@ -129,8 +120,6 @@ class DebuggerController extends DisposableController Map>> libraryMemberAndImportsAutocompleteCache = {}; - final programExplorerController = ProgramExplorerController(); - final ScriptCache _scriptCache = ScriptCache(); final ScriptsHistory scriptsHistory = ScriptsHistory(); @@ -172,11 +161,8 @@ class DebuggerController extends DisposableController final _showFileOpener = ValueNotifier(false); /// Jump to the given ScriptRef and optional SourcePosition. - void showScriptLocation( - ScriptLocation scriptLocation, { - bool centerLocation = true, - }) { - _showScriptLocation(scriptLocation, centerLocation: centerLocation); + void showScriptLocation(ScriptLocation scriptLocation) { + _showScriptLocation(scriptLocation); // Update the scripts history (and make sure we don't react to the // subsequent event). @@ -187,11 +173,7 @@ class DebuggerController extends DisposableController /// Show the given script location (without updating the script navigation /// history). - void _showScriptLocation( - ScriptLocation scriptLocation, { - bool centerLocation = true, - }) { - _shouldCenterScrollLocation = centerLocation; + void _showScriptLocation(ScriptLocation scriptLocation) { _currentScriptRef.value = scriptLocation?.scriptRef; _parseCurrentScript(); @@ -300,9 +282,6 @@ class DebuggerController extends DisposableController /// Return the sorted list of ScriptRefs active in the current isolate. ValueListenable> get sortedScripts => _sortedScripts; - bool get shouldCenterScrollLocation => _shouldCenterScrollLocation; - bool _shouldCenterScrollLocation = true; - final _breakpoints = ValueNotifier>([]); ValueListenable> get breakpoints => _breakpoints; @@ -325,7 +304,7 @@ class DebuggerController extends DisposableController final _librariesVisible = ValueNotifier(false); - ValueListenable get fileExplorerVisible => _librariesVisible; + ValueListenable get librariesVisible => _librariesVisible; /// Make the 'Libraries' view on the right-hand side of the screen visible or /// hidden. @@ -629,6 +608,7 @@ class DebuggerController extends DisposableController void _handleIsolateEvent(Event event) { if (event.isolate.id != isolateRef?.id) return; + switch (event.kind) { case EventKind.kIsolateReload: _updateAfterIsolateReload(event); @@ -883,12 +863,25 @@ class DebuggerController extends DisposableController _populateScriptAndShowLocation(mainScriptRef); } + SourcePosition calculatePosition(Script script, int tokenPos) { + final List> table = script.tokenPosTable; + if (table == null) { + return null; + } + + return SourcePosition( + line: script.getLineNumberFromTokenPos(tokenPos), + column: script.getColumnNumberFromTokenPos(tokenPos), + tokenPos: tokenPos, + ); + } + Future _createBreakpointWithLocation( Breakpoint breakpoint) async { if (breakpoint.resolved) { final bp = BreakpointAndSourcePosition.create(breakpoint); return getScript(bp.scriptRef).then((Script script) { - final pos = SourcePosition.calculatePosition(script, bp.tokenPos); + final pos = calculatePosition(script, bp.tokenPos); return BreakpointAndSourcePosition.create(breakpoint, pos); }); } else { @@ -905,8 +898,7 @@ class DebuggerController extends DisposableController } final script = await getScript(location.script); - final position = - SourcePosition.calculatePosition(script, location.tokenPos); + final position = calculatePosition(script, location.tokenPos); return StackFrameAndSourcePosition(frame, position: position); } @@ -1011,7 +1003,7 @@ class DebuggerController extends DisposableController for (SourceReportRange range in report.ranges) { if (range.possibleBreakpoints != null) { for (int tokenPos in range.possibleBreakpoints) { - positions.add(SourcePosition.calculatePosition(script, tokenPos)); + positions.add(calculatePosition(script, tokenPos)); } } } diff --git a/packages/devtools_app/lib/src/debugger/debugger_model.dart b/packages/devtools_app/lib/src/debugger/debugger_model.dart index 9a45daa5cb7..aa200babd9e 100644 --- a/packages/devtools_app/lib/src/debugger/debugger_model.dart +++ b/packages/devtools_app/lib/src/debugger/debugger_model.dart @@ -94,27 +94,10 @@ class ScriptLocation { String toString() => '${scriptRef.uri} $location'; } +/// A line, column, and an optional tokenPos. class SourcePosition { - const SourcePosition({ - @required this.line, - @required this.column, - this.file, - this.tokenPos, - }); - - factory SourcePosition.calculatePosition(Script script, int tokenPos) { - if (script.tokenPosTable == null) { - return null; - } - - return SourcePosition( - line: script.getLineNumberFromTokenPos(tokenPos), - column: script.getColumnNumberFromTokenPos(tokenPos), - tokenPos: tokenPos, - ); - } + SourcePosition({@required this.line, @required this.column, this.tokenPos}); - final String file; final int line; final int column; final int tokenPos; @@ -530,9 +513,7 @@ List _createVariablesForAssociations( isolateRef: isolateRef, ); variables.add( - Variable.text('[Entry $i]') - ..addChild(key) - ..addChild(value), + Variable.text('[Entry $i]')..addChild(key)..addChild(value), ); } return variables; diff --git a/packages/devtools_app/lib/src/debugger/debugger_screen.dart b/packages/devtools_app/lib/src/debugger/debugger_screen.dart index d643398a837..155cf81941c 100644 --- a/packages/devtools_app/lib/src/debugger/debugger_screen.dart +++ b/packages/devtools_app/lib/src/debugger/debugger_screen.dart @@ -26,7 +26,7 @@ import 'controls.dart'; import 'debugger_controller.dart'; import 'debugger_model.dart'; import 'key_sets.dart'; -import 'program_explorer.dart'; +import 'scripts.dart'; import 'variables.dart'; class DebuggerScreen extends Screen { @@ -102,10 +102,7 @@ class DebuggerScreenBodyState extends State void _onLocationSelected(ScriptLocation location) { if (location != null) { - controller.showScriptLocation( - location, - centerLocation: location.location == null, - ); + controller.showScriptLocation(location); } } @@ -140,7 +137,7 @@ class DebuggerScreenBodyState extends State ); final codeArea = ValueListenableBuilder( - valueListenable: controller.fileExplorerVisible, + valueListenable: controller.librariesVisible, builder: (context, visible, _) { if (visible) { // TODO(devoncarew): Animate this opening and closing. @@ -149,9 +146,16 @@ class DebuggerScreenBodyState extends State initialFractions: const [0.70, 0.30], children: [ codeView, - ProgramExplorer( - debugController: controller, - onSelected: _onLocationSelected, + ValueListenableBuilder( + valueListenable: controller.sortedScripts, + builder: (context, scripts, _) { + return ScriptPicker( + key: DebuggerScreenBody.scriptViewKey, + controller: controller, + scripts: scripts, + onSelected: _onLocationSelected, + ); + }, ), ], ); @@ -389,7 +393,7 @@ class _DebuggerStatusState extends State with AutoDisposeMixin { final fileName = ' at ' + frame.location.script.uri.split('/').last; final script = await widget.controller.getScript(frame.location.script); final pos = - SourcePosition.calculatePosition(script, frame.location.tokenPos); + widget.controller.calculatePosition(script, frame.location.tokenPos); return 'paused$reason$fileName $pos'; } diff --git a/packages/devtools_app/lib/src/debugger/program_explorer.dart b/packages/devtools_app/lib/src/debugger/program_explorer.dart deleted file mode 100644 index 5df2cfeb9e8..00000000000 --- a/packages/devtools_app/lib/src/debugger/program_explorer.dart +++ /dev/null @@ -1,457 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/material.dart'; -import 'package:vm_service/vm_service.dart' hide Stack; - -import '../common_widgets.dart'; -import '../flex_split_column.dart'; -import '../theme.dart'; -import '../tree.dart'; -import '../utils.dart'; -import 'debugger_controller.dart'; -import 'debugger_model.dart'; -import 'program_explorer_controller.dart'; -import 'program_explorer_model.dart'; - -const containerIcon = Icons.folder; -const libraryIcon = Icons.insert_drive_file; -const listItemHeight = 40.0; - -double get _programExplorerRowHeight => scaleByFontFactor(22.0); - -class _ProgramExplorerRow extends StatelessWidget { - const _ProgramExplorerRow({ - @required this.controller, - @required this.node, - this.onTap, - }); - - final ProgramExplorerController controller; - final VMServiceObjectNode node; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - String text = node.name; - final toolTip = _tooltipForNode(); - - if (node.object is ClassRef || - node.object is Func || - node.object is Field) { - text = toolTip; - } - - return DevToolsTooltip( - tooltip: toolTip ?? node.name, - textStyle: theme.toolTipFixedFontStyle, - child: InkWell( - onTap: onTap, - child: Row( - children: [ - ProgramStructureIcon( - object: node.object, - ), - const SizedBox(width: densePadding), - Flexible( - child: Text( - text, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.fixedFontStyle, - ), - ), - ], - ), - ), - ); - } - - String _tooltipForNode() { - String toolTip; - if (node.object is ClassRef) { - final clazz = node.object as ClassRef; - toolTip = '${clazz.name}'; - if (clazz.typeParameters != null) { - toolTip += - '<' + clazz.typeParameters.map((e) => e.name).join(', ') + '>'; - } - } else if (node.object is Func) { - final func = node.object as Func; - final isInstanceMethod = func.owner is ClassRef; - final subtext = _buildFunctionTypeText( - func.name, - func.signature, - isInstanceMethod: isInstanceMethod, - ); - toolTip = '${func.name}$subtext'; - } else if (node.object is Field) { - final field = node.object as Field; - final subtext = _buildFieldTypeText(field); - toolTip = '$subtext ${field.name}'; - } else if (node.script != null) { - toolTip = node.script.uri; - } - return toolTip; - } - - /// Builds a string representation of a field declaration. - /// - /// Examples: - /// - final String - /// - static const int - /// - List - String _buildFieldTypeText(Field field) { - final buffer = StringBuffer(); - if (field.isStatic) { - buffer.write('static '); - } - if (field.isConst) { - buffer.write('const '); - } - if (field.isFinal && !field.isConst) { - buffer.write('final '); - } - buffer.write(field.declaredType.name); - return buffer.toString(); - } - - /// Builds a string representation of a function signature - /// - /// Examples: - /// - Foo(T) -> dynamic - /// - Bar(String, int) -> void - /// - Baz(String, [int]) -> void - /// - Faz(String, {String? bar, required int baz}) -> int - String _buildFunctionTypeText( - String functionName, - InstanceRef signature, { - bool isInstanceMethod = false, - }) { - final buffer = StringBuffer(); - if (signature.typeParameters != null) { - final typeParams = signature.typeParameters; - buffer.write('<'); - for (int i = 0; i < typeParams.length; ++i) { - buffer.write(typeParams[i].name); - if (i + 1 != typeParams.length) { - buffer.write(', '); - } - } - buffer.write('>'); - } - buffer.write('('); - String closingTag; - for (int i = isInstanceMethod ? 1 : 0; - i < signature.parameters.length; - ++i) { - final param = signature.parameters[i]; - if (!param.fixed && closingTag == null) { - if (param.name == null) { - closingTag = ']'; - buffer.write('['); - } else { - closingTag = '}'; - buffer.write('{'); - } - } - if (param.required != null && param.required) { - buffer.write('required '); - } - if (param.parameterType.name == null) { - buffer.write(_buildFunctionTypeText('Function', param.parameterType)); - } else { - buffer.write(param.parameterType.name); - } - if (param.name != null) { - buffer.write(' ${param.name}'); - } - if (i + 1 != signature.parameters.length) { - buffer.write(', '); - } else if (closingTag != null) { - buffer.write(closingTag); - } - } - buffer.write(') → '); - if (signature.returnType.name == null) { - buffer.write(_buildFunctionTypeText('Function', signature.returnType)); - } else { - buffer.write(signature.returnType.name); - } - - return buffer.toString(); - } -} - -class ProgramStructureIcon extends StatelessWidget { - const ProgramStructureIcon({ - @required this.object, - }); - - final ObjRef object; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final colorScheme = theme.colorScheme; - IconData icon; - String character; - Color color = colorScheme.functionSyntaxColor; - bool isShortCharacter; - if (object is ClassRef) { - character = 'c'; - isShortCharacter = true; - color = colorScheme.declarationsSyntaxColor; - } else if (object is FuncRef) { - character = 'm'; - isShortCharacter = true; - color = colorScheme.functionSyntaxColor; - } else if (object is FieldRef) { - character = 'f'; - isShortCharacter = false; - color = colorScheme.variableSyntaxColor; - } else if (object is LibraryRef) { - icon = Icons.book; - color = colorScheme.modifierSyntaxColor; - } else if (object is ScriptRef) { - icon = libraryIcon; - color = colorScheme.stringSyntaxColor; - } else { - icon = containerIcon; - } - - assert((icon == null && character != null && isShortCharacter != null) || - (icon != null && character == null && isShortCharacter == null)); - - return SizedBox( - height: defaultIconSize, - width: defaultIconSize, - child: Container( - decoration: icon == null - ? BoxDecoration( - color: color, - shape: BoxShape.circle, - ) - : null, - child: icon == null - ? Center( - child: Text( - character, - style: TextStyle( - height: 1, - fontFamily: theme.fixedFontStyle.fontFamily, - color: theme.colorScheme.defaultBackgroundColor, - fontSize: chartFontSizeSmall, - ), - // Required to center the individual character within the - // shape. Since letters like 'm' are shorter than letters - // like 'f', there's padding applied to the top of shorter - // characters in order for everything to align properly. - // Since we're only dealing with individual characters, we - // want to disable this behavior so shorter characters don't - // appear to be slightly below center. - textHeightBehavior: TextHeightBehavior( - applyHeightToFirstAscent: isShortCharacter, - applyHeightToLastDescent: false, - ), - ), - ) - : Icon( - icon, - size: defaultIconSize, - color: color, - ), - ), - ); - } -} - -class _FileExplorer extends StatelessWidget { - const _FileExplorer({ - @required this.controller, - @required this.onItemSelected, - @required this.onItemExpanded, - }); - - final ProgramExplorerController controller; - final Function(VMServiceObjectNode) onItemSelected; - final Function(VMServiceObjectNode) onItemExpanded; - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder>( - valueListenable: controller.rootObjectNodes, - builder: (context, nodes, _) { - return TreeView( - itemExtent: _programExplorerRowHeight, - dataRoots: nodes, - onItemSelected: onItemSelected, - onItemExpanded: onItemExpanded, - dataDisplayProvider: (node, onTap) { - return _ProgramExplorerRow( - controller: controller, - node: node, - onTap: () { - controller.selectNode(node); - onTap(); - }, - ); - }, - ); - }, - ); - } -} - -class _ProgramOutlineView extends StatelessWidget { - const _ProgramOutlineView({ - @required this.controller, - @required this.onItemSelected, - @required this.onItemExpanded, - }); - - final ProgramExplorerController controller; - final Function(VMServiceObjectNode) onItemSelected; - final Function(VMServiceObjectNode) onItemExpanded; - - @override - Widget build(BuildContext context) { - print(controller.isLoadingOutline); - return ValueListenableBuilder( - valueListenable: controller.isLoadingOutline, - builder: (context, isLoadingOutline, _) { - if (isLoadingOutline) { - return const CenteredCircularProgressIndicator(); - } - return ValueListenableBuilder>( - valueListenable: controller.outlineNodes, - builder: (context, nodes, _) { - if (nodes == null || nodes.isEmpty) { - return const Center( - child: Text('Nothing to inspect'), - ); - } - return TreeView( - itemExtent: _programExplorerRowHeight, - dataRoots: nodes, - onItemSelected: onItemSelected, - onItemExpanded: onItemExpanded, - dataDisplayProvider: (node, onTap) { - return _ProgramExplorerRow( - controller: controller, - node: node, - onTap: onTap, - ); - }, - ); - }, - ); - }, - ); - } -} - -/// Picker that displays the program's structure, allowing for navigation and -/// filtering. -class ProgramExplorer extends StatelessWidget { - ProgramExplorer({ - Key key, - @required this.debugController, - @required this.onSelected, - }) : controller = debugController.programExplorerController, - super(key: key); - - final ProgramExplorerController controller; - final DebuggerController debugController; - final void Function(ScriptLocation) onSelected; - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: controller.initialized, - builder: (context, initialized, _) { - Widget body; - if (!initialized) { - body = const CenteredCircularProgressIndicator(); - } else { - body = LayoutBuilder( - builder: (context, constraints) { - return FlexSplitColumn( - totalHeight: constraints.maxHeight, - initialFractions: const [0.7, 0.3], - minSizes: const [0.0, 0.0], - headers: const [ - AreaPaneHeader( - title: Text('File Explorer'), - needsTopBorder: false, - ), - AreaPaneHeader(title: Text('Outline')), - ], - children: [ - _FileExplorer( - controller: controller, - onItemExpanded: onItemExpanded, - onItemSelected: onItemSelected, - ), - _ProgramOutlineView( - controller: controller, - onItemExpanded: onItemExpanded, - onItemSelected: onItemSelected, - ), - ], - ); - }, - ); - } - return OutlineDecoration( - child: body, - ); - }, - ); - } - - void onItemSelected(VMServiceObjectNode node) async { - if (!node.isSelectable) { - node.toggleExpansion(); - return; - } - - if (node.object != null && node.object is! Obj) { - await controller.populateNode(node); - } - - // If the node is collapsed and we select it, we'll always want to expand - // to display the children. - if (!node.isExpanded) { - node.expand(); - } - - ScriptRef script = node.script; - int tokenPos = 0; - if (node.object != null && - (node.object is FieldRef || - node.object is FuncRef || - node.object is ClassRef)) { - final location = (node.object as dynamic).location; - tokenPos = location.tokenPos; - script = location.script; - } - - script = await debugController.getScript(script); - final location = tokenPos == 0 - ? null - : SourcePosition.calculatePosition(script, tokenPos); - onSelected( - ScriptLocation(script, location: location), - ); - } - - void onItemExpanded(VMServiceObjectNode node) async { - if (node.object != null && node.object is! Obj) { - await controller.populateNode(node); - } - } -} diff --git a/packages/devtools_app/lib/src/debugger/program_explorer_controller.dart b/packages/devtools_app/lib/src/debugger/program_explorer_controller.dart deleted file mode 100644 index a6c1e2f491c..00000000000 --- a/packages/devtools_app/lib/src/debugger/program_explorer_controller.dart +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:vm_service/vm_service.dart'; - -import '../auto_dispose.dart'; -import '../globals.dart'; -import '../utils.dart'; -import 'program_explorer_model.dart'; - -class ProgramExplorerController extends DisposableController - with AutoDisposeControllerMixin { - ProgramExplorerController() { - addAutoDisposeListener( - serviceManager.isolateManager.selectedIsolate, - refresh, - ); - } - - /// A list of objects containing the contents of each library. - final _programStructure = []; - - /// The outline view nodes for the currently selected library. - ValueListenable> get outlineNodes => _outlineNodes; - final _outlineNodes = ListValueNotifier([]); - - ValueListenable get isLoadingOutline => _isLoadingOutline; - final _isLoadingOutline = ValueNotifier(false); - - /// The currently selected node. - VMServiceObjectNode _selected; - - /// The processed roots of the tree. - ValueListenable> get rootObjectNodes => - _rootObjectNodes; - final _rootObjectNodes = ListValueNotifier([]); - - /// Cache of object IDs to their containing library to allow for easier - /// refreshing of library content, particularly for scripts. - final _objectIdToLibrary = {}; - - IsolateRef _isolate; - - /// Notifies that the controller has finished initializing. - ValueListenable get initialized => _initialized; - final _initialized = ValueNotifier(false); - bool _initializing = false; - - /// Returns true if [function] is a getter or setter that was not explicitly - /// defined (e.g., `int foo` creates `int get foo` and `set foo(int)`). - static bool _isSyntheticAccessor(FuncRef function, List fields) { - for (final field in fields) { - if (function.name == field.name || function.name == '${field.name}=') { - return true; - } - } - return false; - } - - /// Initializes the program structure. - // TODO(bkonyi): reinitialize after hot reload. - Future initialize() async { - if (_initializing) { - return; - } - _initializing = true; - _isolate = serviceManager.isolateManager.selectedIsolate.value; - final libraries = _isolate != null - ? await Future.wait( - serviceManager.isolateManager - .isolateDebuggerState(_isolate) - .isolateNow - .libraries - .map( - (lib) => VMServiceLibraryContents.getLibraryContents(lib), - ), - ) - : []; - - void mapIdsToLibrary(LibraryRef lib, Iterable objs) { - for (final e in objs) { - _objectIdToLibrary[e.id] = lib; - } - } - - for (final libContents in libraries) { - final lib = libContents.lib; - mapIdsToLibrary(lib, [lib]); - mapIdsToLibrary(lib, lib.scripts); - mapIdsToLibrary(lib, libContents.classes); - mapIdsToLibrary(lib, libContents.fields); - - // Filter out synthetic getters/setters - final filteredFunctions = libContents.functions - .where( - (e) => !_isSyntheticAccessor(e, libContents.fields), - ) - .toList(); - libContents.functions.clear(); - libContents.functions.addAll(filteredFunctions); - mapIdsToLibrary(lib, libContents.functions); - - // Account for entries in library parts. - mapIdsToLibrary(lib, libContents.functions.map((e) => e.location.script)); - mapIdsToLibrary(lib, libContents.classes.map((e) => e.location.script)); - mapIdsToLibrary(lib, libContents.fields.map((e) => e.location.script)); - } - - _programStructure.addAll(libraries); - - // Build the initial tree. - final nodes = VMServiceObjectNode.createRootsFrom( - this, - _programStructure, - ); - _rootObjectNodes.addAll(nodes); - _initialized.value = true; - } - - void selectScriptNode(ScriptRef script) { - if (!initialized.value) { - return; - } - _selectScriptNode(script, _rootObjectNodes.value); - _rootObjectNodes.notifyListeners(); - } - - void _selectScriptNode( - ScriptRef script, - List nodes, - ) { - for (final node in nodes) { - final result = node.firstChildWithCondition( - (node) => node.script?.uri == script.uri, - ); - if (result != null) { - selectNode(result); - result.expandAscending(); - return; - } - } - } - - /// Clears controller state and re-initializes. - Future refresh() async { - _objectIdToLibrary.clear(); - _selected = null; - _isLoadingOutline.value = true; - _outlineNodes.clear(); - _initialized.value = false; - _initializing = false; - _programStructure.clear(); - return await initialize(); - } - - /// Marks [node] as the currently selected node, clearing the selection state - /// of any currently selected node. - void selectNode(VMServiceObjectNode node) async { - if (!node.isSelectable) { - return; - } - if (_selected != node) { - node.select(); - _selected?.unselect(); - _selected = node; - _isLoadingOutline.value = true; - _outlineNodes - ..clear() - ..addAll(await _selected.outline); - _isLoadingOutline.value = false; - } - } - - /// Updates `node` with a fully populated VM service [Obj]. - /// - /// If `node.object` is already an instance of [Obj], this function - /// immediately returns. - Future populateNode(VMServiceObjectNode node) async { - final object = node.object; - if (object == null || object is Obj) { - return; - } - final id = node.object.id; - final service = serviceManager.service; - - // We don't know if the object ID is still valid. Re-request the library - // and find the object again. - final lib = _objectIdToLibrary[id]; - - final refreshedLib = - await service.getObject(_isolate.id, lib.id) as Library; - dynamic updatedObj; - - // Find the relevant object reference in the refreshed library. - if (object is ClassRef) { - updatedObj = refreshedLib.classes.firstWhere( - (k) => k.name == object.name, - ); - } else if (object is FieldRef) { - updatedObj = refreshedLib.variables.firstWhere( - (v) => v.name == object.name, - ); - } else if (object is FuncRef) { - updatedObj = refreshedLib.functions.firstWhere( - (f) => f.name == object.name, - ); - } else if (object is ScriptRef) { - updatedObj = refreshedLib.scripts.firstWhere( - (s) => s.uri == object.uri, - ); - } else { - throw StateError('Unexpected type: ${object.runtimeType}'); - } - - // Request the full object for the node we're interested in and update all - // instances of the original reference object in the program structure with - // the full object. - updatedObj = await service.getObject(_isolate.id, updatedObj.id); - - final library = _programStructure.firstWhere( - (e) => e.lib.uri == lib.uri, - ); - if (updatedObj is Class) { - final clazz = updatedObj; - final i = library.classes.indexWhere( - (c) => c.name == clazz.name, - ); - library.classes[i] = clazz; - final fields = await Future.wait( - clazz.fields.map( - (e) => service.getObject(_isolate.id, e.id), - ), - ); - clazz.fields - ..clear() - ..addAll(fields.cast()); - - final functions = await Future.wait( - clazz.functions.map( - (e) => service.getObject(_isolate.id, e.id).then((e) => e as Func), - ), - ); - clazz.functions - ..clear() - ..addAll( - functions - .where((e) => !_isSyntheticAccessor(e, clazz.fields)) - .cast(), - ); - } else if (updatedObj is Field) { - final i = library.fields.indexWhere( - (f) => f.name == updatedObj.name, - ); - library.fields[i] = updatedObj; - } else if (object is Func) { - final i = library.functions.indexWhere( - (f) => f.name == updatedObj.name, - ); - library.functions[i] = updatedObj; - } - - _objectIdToLibrary.remove(id); - _objectIdToLibrary[updatedObj.id] = library.lib; - - // Sets the contents of the node to contain the full object. - node.updateObject(updatedObj); - } -} diff --git a/packages/devtools_app/lib/src/debugger/program_explorer_model.dart b/packages/devtools_app/lib/src/debugger/program_explorer_model.dart deleted file mode 100644 index b7c598c6c86..00000000000 --- a/packages/devtools_app/lib/src/debugger/program_explorer_model.dart +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright 2021 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:vm_service/vm_service.dart'; - -import '../globals.dart'; -import '../trees.dart'; -import '../version.dart'; -import 'program_explorer_controller.dart'; - -class VMServiceLibraryContents { - const VMServiceLibraryContents({ - this.lib, - this.classes, - this.functions, - this.fields, - }); - - final Library lib; - final List classes; - final List functions; - final List fields; - - static Future getLibraryContents( - LibraryRef libRef, - ) async { - final isolateId = serviceManager.isolateManager.selectedIsolate.value.id; - final service = serviceManager.service; - - final lib = await service.getObject(isolateId, libRef.id) as Library; - var classes = []; - var functions = []; - var fields = []; - - classes.addAll(lib.classes); - functions.addAll(lib.functions); - fields.addAll(lib.variables); - - // Before 3.46, ClassRef, FuncRef, and FieldRef didn't contain location - // information and couldn't be mapped to their parent scripts. For older - // versions of the protocol, we need to request the full objects for - // everything. We'll avoid doing this for versions >= 3.46 and lazily - // populate the tree with full instances as the user navigates. - if (!await service.isProtocolVersionSupported( - supportedVersion: SemanticVersion(major: 3, minor: 46), - )) { - final classesRequests = lib.classes.map( - (clazz) async => await service.getObject(isolateId, clazz.id) as Class, - ); - - classes = await Future.wait(classesRequests); - } - - final funcsRequests = lib.functions.map( - (func) async => await service.getObject(isolateId, func.id) as Func, - ); - functions = await Future.wait(funcsRequests); - - final fieldsRequests = lib.variables.map( - (field) async => await service.getObject(isolateId, field.id) as Field, - ); - fields = await Future.wait(fieldsRequests); - - // Remove scripts pulled into libraries via mixins. - lib.scripts.removeWhere((e) => !e.uri.contains(lib.uri)); - - return VMServiceLibraryContents( - lib: lib, - classes: classes, - functions: functions, - fields: fields, - ); - } -} - -/// A node in a tree of VM service objects. -/// -/// A node can represent one of the following: -/// - Directory -/// - Library (with or without a script) -/// - Script -/// - Class -/// - Field -/// - Function -class VMServiceObjectNode extends TreeNode { - VMServiceObjectNode( - this.controller, - this.name, - this.object, { - this.isSelectable = true, - }); - - static const dartPrefix = 'dart:'; - static const packagePrefix = 'package:'; - - final ProgramExplorerController controller; - final String name; - bool isSelectable; - - ObjRef object; - ScriptRef script; - - /// This exists to allow for O(1) lookup of children when building the tree. - final _childrenAsMap = {}; - - @override - bool shouldShow() { - return true; - } - - // TODO(bkonyi): handle empty classes - @override - bool get isExpandable => super.isExpandable || object is ClassRef; - - bool get isDirectory => script == null && object == null; - - List _outline; - Future> get outline async { - if (_outline != null) { - return _outline; - } - final root = VMServiceObjectNode( - controller, - '', - ObjRef(id: '0'), - ); - - String uri; - Library lib; - if (object is Library) { - lib = object as Library; - uri = lib.uri; - } else { - // Try to find the library in the tree. If the current node isn't a - // library node, it's likely one of its parents are. - VMServiceObjectNode libNode = this; - while (libNode != null && libNode.object is! Library) { - libNode = libNode.parent; - } - - // In the case of patch files, the parent nodes won't include a library. - // We'll need to search for the library URI that is a prefix of the - // script's URI. - if (libNode == null) { - final service = serviceManager.service; - final isolate = serviceManager.isolateManager.selectedIsolate.value; - final libRef = serviceManager.isolateManager - .isolateDebuggerState(isolate) - .isolateNow - .libraries - .firstWhere( - (lib) => script.uri.startsWith(lib.uri), - ); - lib = await service.getObject(isolate.id, libRef.id); - } else { - lib = libNode.object as Library; - } - final ScriptRef s = (object is ScriptRef) ? object : script; - uri = s.uri; - } - - for (final clazz in lib.classes) { - // Don't surface synthetic classes created by mixin applications. - if (clazz.name.contains('&')) { - continue; - } - if (clazz.location.script.uri == uri) { - final clazzNode = VMServiceObjectNode( - controller, - clazz.name, - clazz, - ); - if (clazz is Class) { - for (final function in clazz.functions) { - clazzNode._lookupOrCreateChild(function.name, function); - } - for (final field in clazz.fields) { - clazzNode._lookupOrCreateChild(field.name, field); - } - } - root.addChild(clazzNode); - } - } - - for (final function in lib.functions) { - if (function.location.script.uri == uri) { - final node = VMServiceObjectNode( - controller, - function.name, - function, - ); - await controller.populateNode(node); - root.addChild( - node, - ); - } - } - - for (final field in lib.variables) { - if (field.location.script.uri == uri) { - final node = VMServiceObjectNode( - controller, - field.name, - field, - ); - await controller.populateNode(node); - root.addChild( - VMServiceObjectNode( - controller, - field.name, - field, - ), - ); - } - } - - // Clear out the _childrenAsMap map. - root._trimChildrenAsMapEntries(); - - root._sortEntriesByType(); - _outline = root.children; - return _outline; - } - - /// Given a flat list of service protocol scripts, return a tree of scripts - /// representing the best hierarchical grouping. - static List createRootsFrom( - ProgramExplorerController controller, - List libs, - ) { - // The name of this node is not exposed to users. - final root = VMServiceObjectNode(controller, '', ObjRef(id: '0')); - - for (var lib in libs) { - for (final script in lib.lib.scripts) { - _buildScriptNode(root, script, lib: lib.lib); - } - } - - // Clear out the _childrenAsMap map. - root._trimChildrenAsMapEntries(); - - final processed = root.children - .map((e) => e._collapseSingleChildDirectoryNodes()) - .toList(); - root.children.clear(); - root.addAllChildren(processed); - - // Sort each subtree to use the following ordering: - // - Scripts - // - Classes - // - Functions - // - Variables - root._sortEntriesByType(); - - // Place the root library's parent node at the top of the explorer if it's - // part of a package. Otherwise, it's a file path and its directory should - // appear near the top of the list anyway. - final rootLib = - serviceManager.isolateManager.selectedIsolateState.isolateNow.rootLib; - if (rootLib.uri.startsWith('package:') || - rootLib.uri.startsWith('google3:')) { - final parts = rootLib.uri.split('/')..removeLast(); - final path = parts.join('/'); - for (int i = 0; i < root.children.length; ++i) { - if (root.children[i].name.startsWith(path)) { - final rootLibNode = root.removeChildAtIndex(i); - root.addChild(rootLibNode, index: 0); - break; - } - } - } - - return root.children; - } - - static VMServiceObjectNode _buildScriptNode( - VMServiceObjectNode node, - ScriptRef script, { - LibraryRef lib, - }) { - final parts = script.uri.split('/'); - final name = parts.removeLast(); - - for (final part in parts) { - // Directory nodes shouldn't be selectable unless they're a library node. - node = node._lookupOrCreateChild( - part, - null, - isSelectable: false, - ); - } - - node = node._lookupOrCreateChild(name, script); - if (!node.isSelectable) { - node.isSelectable = true; - } - node.script = script; - - // If this is a top-level node and a library is specified, this must be a - // library node. - if (parts.isEmpty && lib != null) { - node.object = lib; - } - return node; - } - - VMServiceObjectNode _lookupOrCreateChild( - String name, - ObjRef object, { - bool isSelectable = true, - }) { - return _childrenAsMap.putIfAbsent( - name, - () => _createChild(name, object, isSelectable: isSelectable), - ); - } - - VMServiceObjectNode _createChild( - String name, - ObjRef object, { - bool isSelectable = true, - }) { - final child = VMServiceObjectNode( - controller, - name, - object, - isSelectable: isSelectable, - ); - addChild(child); - return child; - } - - VMServiceObjectNode _collapseSingleChildDirectoryNodes() { - if (children.length == 1) { - final child = children.first; - if (child.isDirectory) { - final collapsed = VMServiceObjectNode( - controller, - '$name/${child.name}', - null, - isSelectable: false, - ); - collapsed.addAllChildren(child.children); - return collapsed._collapseSingleChildDirectoryNodes(); - } - return this; - } - final updated = children - .map( - (e) => e._collapseSingleChildDirectoryNodes(), - ) - .toList(); - children.clear(); - addAllChildren(updated); - return this; - } - - void updateObject(Obj object) { - if (this.object is! Class && object is Class) { - for (final function in object.functions) { - _createChild(function.name, function); - } - for (final field in object.fields) { - _createChild(field.name, field); - } - _sortEntriesByType(); - } - this.object = object; - } - - /// Clear the _childrenAsMap map recursively to save memory. - void _trimChildrenAsMapEntries() { - _childrenAsMap.clear(); - - for (var child in children) { - child._trimChildrenAsMapEntries(); - } - } - - void _sortEntriesByType() { - final userDirectoryNodes = []; - final userLibraryNodes = []; - final scriptNodes = []; - final classNodes = []; - final functionNodes = []; - final variableNodes = []; - - final packageAndCoreLibLibraryNodes = []; - final packageAndCoreLibDirectoryNodes = []; - - for (final child in children) { - if (child.object == null && child.script == null) { - // Child is a directory node. Treat it as if it were a library/script - // for sorting purposes. - if (child.name.startsWith(dartPrefix) || - child.name.startsWith(packagePrefix)) { - packageAndCoreLibDirectoryNodes.add(child); - } else { - userDirectoryNodes.add(child); - } - } else { - switch (child.object.runtimeType) { - case ScriptRef: - case Script: - scriptNodes.add(child); - break; - case LibraryRef: - case Library: - final obj = child.object as LibraryRef; - if (obj.uri.startsWith(dartPrefix) || - obj.uri.startsWith(packagePrefix)) { - packageAndCoreLibLibraryNodes.add(child); - } else { - userLibraryNodes.add(child); - } - break; - case ClassRef: - case Class: - classNodes.add(child); - break; - case FuncRef: - case Func: - functionNodes.add(child); - break; - case FieldRef: - case Field: - variableNodes.add(child); - break; - default: - throw StateError('Unexpected type: ${child.object.runtimeType}'); - } - } - child._sortEntriesByType(); - } - - userDirectoryNodes.sort((a, b) { - return a.name.compareTo(b.name); - }); - - scriptNodes.sort((a, b) { - return a.name.compareTo(b.name); - }); - - userLibraryNodes.sort((a, b) { - return a.name.compareTo(b.name); - }); - - classNodes.sort((a, b) { - final objA = a.object as ClassRef; - final objB = b.object as ClassRef; - return objA.name.compareTo(objB.name); - }); - - functionNodes.sort((a, b) { - final objA = a.object as FuncRef; - final objB = b.object as FuncRef; - return objA.name.compareTo(objB.name); - }); - - variableNodes.sort((a, b) { - final objA = a.object as FieldRef; - final objB = b.object as FieldRef; - return objA.name.compareTo(objB.name); - }); - - packageAndCoreLibDirectoryNodes.sort((a, b) { - return a.name.compareTo(b.name); - }); - - packageAndCoreLibLibraryNodes.sort((a, b) { - return a.name.compareTo(b.name); - }); - - children - ..clear() - ..addAll([ - ...userLibraryNodes, - ...userDirectoryNodes, - ...scriptNodes, - ...classNodes, - ...functionNodes, - ...variableNodes, - // We treat core libraries and packages as their own category as users - // are likely more interested in code within their own project. - // TODO(bkonyi): do we need custom google3 heuristics here? - ...packageAndCoreLibLibraryNodes, - ...packageAndCoreLibDirectoryNodes, - ]); - } - - @override - int get hashCode => script?.uri.hashCode ?? object?.hashCode ?? name.hashCode; - - @override - bool operator ==(Object other) { - if (other is! VMServiceObjectNode) return false; - final VMServiceObjectNode node = other; - - return node.name == name && - node.object == object && - node.script?.uri == script?.uri; - } - - @override - TreeNode shallowCopy() { - throw UnimplementedError( - 'This method is not implemented. Implement if you ' - 'need to call `shallowCopy` on an instance of this class.', - ); - } -} diff --git a/packages/devtools_app/lib/src/debugger/scripts.dart b/packages/devtools_app/lib/src/debugger/scripts.dart new file mode 100644 index 00000000000..9dd95f4129e --- /dev/null +++ b/packages/devtools_app/lib/src/debugger/scripts.dart @@ -0,0 +1,167 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:vm_service/vm_service.dart'; + +import '../common_widgets.dart'; +import '../theme.dart'; +import '../tree.dart'; +import '../utils.dart'; +import 'debugger_controller.dart'; +import 'debugger_model.dart'; + +const containerIcon = Icons.folder; +const libraryIcon = Icons.insert_chart; +double get listItemHeight => scaleByFontFactor(40.0); + +/// Picker that takes a list of scripts and allows selection of items. +class ScriptPicker extends StatefulWidget { + const ScriptPicker({ + Key key, + @required this.controller, + @required this.scripts, + @required this.onSelected, + }) : super(key: key); + + final DebuggerController controller; + final List scripts; + final void Function(ScriptLocation scriptRef) onSelected; + + @override + ScriptPickerState createState() => ScriptPickerState(); +} + +class ScriptPickerState extends State { + + List _items = []; + List _rootScriptNodes; + + @override + void initState() { + super.initState(); + + _updateScripts(); + } + + @override + void didUpdateWidget(ScriptPicker oldWidget) { + super.didUpdateWidget(oldWidget); + + updateScripts(); + } + + void updateScripts() { + setState(_updateScripts); + } + + @override + Widget build(BuildContext context) { + // Re-calculate the tree of scripts if necessary. + _rootScriptNodes ??= FileNode.createRootsFrom(_items); + + return OutlineDecoration( + child: Column( + children: [ + const AreaPaneHeader( + title: Text('Libraries'), + needsTopBorder: false, + ), + if (_isLoading) const CenteredCircularProgressIndicator(), + if (!_isLoading) + Expanded( + child: TreeView( + itemExtent: listItemHeight, + dataRoots: _rootScriptNodes, + dataDisplayProvider: (item, onTap) => + _displayProvider(context, item, onTap), + ), + ), + ], + ), + ); + } + + Widget _displayProvider( + BuildContext context, + FileNode node, + VoidCallback onTap, + ) { + return DevToolsTooltip( + tooltip: node.name, + child: Material( + child: InkWell( + onTap: () { + if (node.hasScript) { + _handleSelected(node.scriptRef); + } + onTap(); + }, + child: Row( + children: [ + Icon( + node.hasScript ? libraryIcon : containerIcon, + size: defaultIconSize, + ), + const SizedBox(width: densePadding), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!node.hasScript) + Text( + node.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ) + else ...[ + Text( + node.fileName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + node.scriptRef.uri, + style: Theme.of(context).subtleTextStyle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ] + ], + ), + ), + ], + ), + ), + ), + ); + } + + bool get _isLoading => widget.scripts.isEmpty; + + void _updateScripts() { + _items = widget.scripts; + + // Remove the cached value here; it'll be re-computed the next time we need + // it. + _rootScriptNodes = null; + } + + void _handleSelected(ObjRef ref) async { + if (ref is ScriptRef) { + widget.onSelected(ScriptLocation(ref)); + } else if (ref is ClassRef) { + final obj = await widget.controller.getObject(ref); + final location = (obj as Class).location; + final script = await widget.controller.getScript(location.script); + final pos = + widget.controller.calculatePosition(script, location.tokenPos); + + widget.onSelected(ScriptLocation(script, location: pos)); + } else { + assert(false, 'unexpected object reference: ${ref.type}'); + } + } +} diff --git a/packages/devtools_app/lib/src/debugger/variables.dart b/packages/devtools_app/lib/src/debugger/variables.dart index ba97a38ffe1..df5335392fe 100644 --- a/packages/devtools_app/lib/src/debugger/variables.dart +++ b/packages/devtools_app/lib/src/debugger/variables.dart @@ -36,7 +36,7 @@ class Variables extends StatelessWidget { dataRoots: variables, dataDisplayProvider: (variable, onPressed) => displayProvider(context, variable, onPressed, controller), - onItemSelected: (variable) => onItemPressed(variable, controller), + onItemPressed: (variable) => onItemPressed(variable, controller), ); }, ); @@ -65,7 +65,7 @@ class VariablesList extends StatelessWidget { dataRoots: lines, dataDisplayProvider: (variable, onPressed) => displayProvider(context, variable, onPressed, controller), - onItemSelected: (variable) => onItemPressed(variable, controller), + onItemPressed: (variable) => onItemPressed(variable, controller), ); } @@ -101,7 +101,7 @@ class ExpandableVariable extends StatelessWidget { shrinkWrap: true, dataDisplayProvider: (variable, onPressed) => displayProvider(context, variable, onPressed, debuggerController), - onItemSelected: (variable) => + onItemPressed: (variable) => onItemPressed(variable, debuggerController), ); }, diff --git a/packages/devtools_app/lib/src/history_manager.dart b/packages/devtools_app/lib/src/history_manager.dart index a2470251cec..0ce5c491ccc 100644 --- a/packages/devtools_app/lib/src/history_manager.dart +++ b/packages/devtools_app/lib/src/history_manager.dart @@ -52,11 +52,6 @@ class HistoryManager { _current.value = _history[_historyIndex]; } - /// Return the next value. - /// - /// Returns null if there is no next value. - T peekNext() => hasNext ? _history[_historyIndex + 1] : null; - /// Remove the most recent historical item on the stack. /// /// If [current] is the last item on the stack when this method is called, diff --git a/packages/devtools_app/lib/src/history_viewport.dart b/packages/devtools_app/lib/src/history_viewport.dart index bd3c88bd05d..94ae69cb341 100644 --- a/packages/devtools_app/lib/src/history_viewport.dart +++ b/packages/devtools_app/lib/src/history_viewport.dart @@ -29,16 +29,12 @@ class HistoryViewport extends StatelessWidget { @required this.contentBuilder, this.controls, this.generateTitle, - this.onChange, - this.historyEnabled = true, }); final HistoryManager history; final String Function(T) generateTitle; final List controls; final Widget Function(BuildContext, T) contentBuilder; - final void Function(T, T) onChange; - final bool historyEnabled; @override Widget build(BuildContext context) { @@ -63,40 +59,17 @@ class HistoryViewport extends StatelessWidget { theme, child: Row( children: [ - if (historyEnabled) ...[ - ToolbarAction( - icon: Icons.chevron_left, - onPressed: history.hasPrevious - ? () { - history.moveBack(); - if (onChange != null) { - onChange( - history.current.value, - history.peekNext(), - ); - } - } - : null, - ), - ToolbarAction( - icon: Icons.chevron_right, - onPressed: history.hasNext - ? () { - final current = history.current.value; - history.moveForward(); - if (onChange != null) { - onChange( - history.current.value, - current, - ); - } - } - : null, - ), - const SizedBox(width: denseSpacing), - const VerticalDivider(thickness: 1.0), - const SizedBox(width: defaultSpacing), - ], + ToolbarAction( + icon: Icons.chevron_left, + onPressed: history.hasPrevious ? history.moveBack : null, + ), + ToolbarAction( + icon: Icons.chevron_right, + onPressed: history.hasNext ? history.moveForward : null, + ), + const SizedBox(width: denseSpacing), + const VerticalDivider(thickness: 1.0), + const SizedBox(width: defaultSpacing), Expanded( child: Text( generateTitle == null diff --git a/packages/devtools_app/lib/src/inspector/diagnostics_node.dart b/packages/devtools_app/lib/src/inspector/diagnostics_node.dart index 24b726cd740..0e8a7c2e254 100644 --- a/packages/devtools_app/lib/src/inspector/diagnostics_node.dart +++ b/packages/devtools_app/lib/src/inspector/diagnostics_node.dart @@ -11,7 +11,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:vm_service/vm_service.dart'; -import '../debugger/debugger_model.dart'; import '../enum_utils.dart'; import '../ui/icons.dart'; import '../utils.dart'; @@ -719,3 +718,13 @@ class InspectorSourceLocation { return SourcePosition(file: file, line: line - 1, column: column - 1); } } + +// TODO(jacobr): rename this class or merge with SourcePosition class in +// debugger_model.dart +class SourcePosition { + const SourcePosition({this.file, this.line, this.column}); + + final String file; + final int line; + final int column; +} diff --git a/packages/devtools_app/lib/src/inspector/inspector_service.dart b/packages/devtools_app/lib/src/inspector/inspector_service.dart index 0140002efa0..36be37f03fd 100644 --- a/packages/devtools_app/lib/src/inspector/inspector_service.dart +++ b/packages/devtools_app/lib/src/inspector/inspector_service.dart @@ -17,7 +17,7 @@ import 'package:meta/meta.dart'; import 'package:vm_service/vm_service.dart'; import '../auto_dispose.dart'; -import '../debugger/debugger_model.dart'; +import '../debugger/debugger_model.dart' hide SourcePosition; import '../eval_on_dart_library.dart'; import '../globals.dart'; import '../utils.dart'; diff --git a/packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart b/packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart index b544f1852f7..75f743585a0 100644 --- a/packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart +++ b/packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart @@ -260,7 +260,7 @@ class InspectorTreeController extends Object { selection = root .getRow( - (root.getRowIndex(selection) + indexOffset).clamp(0, numRows - 1)) + (root.getRowIndex(selection) + indexOffset).clamp(0, numRows - 1)) ?.node; } @@ -405,7 +405,7 @@ class InspectorTreeController extends Object { if (row != null) { final rowRect = getBoundingBox(row); targetRect = - targetRect == null ? rowRect : targetRect.expandToInclude(rowRect); + targetRect == null ? rowRect : targetRect.expandToInclude(rowRect); } } @@ -439,11 +439,11 @@ class InspectorTreeController extends Object { } InspectorTreeNode setupInspectorTreeNode( - InspectorTreeNode node, - RemoteDiagnosticsNode diagnosticsNode, { - @required bool expandChildren, - @required bool expandProperties, - }) { + InspectorTreeNode node, + RemoteDiagnosticsNode diagnosticsNode, { + @required bool expandChildren, + @required bool expandProperties, + }) { assert(expandChildren != null); assert(expandProperties != null); node.diagnostic = diagnosticsNode; @@ -455,7 +455,7 @@ class InspectorTreeController extends Object { diagnosticsNode.inlineProperties.isNotEmpty) { if (diagnosticsNode.childrenReady || !diagnosticsNode.hasChildren) { final bool styleIsMultiline = - expandPropertiesByDefault(diagnosticsNode.style); + expandPropertiesByDefault(diagnosticsNode.style); setupChildren( diagnosticsNode, node, @@ -472,12 +472,12 @@ class InspectorTreeController extends Object { } void setupChildren( - RemoteDiagnosticsNode parent, - InspectorTreeNode treeNode, - List children, { - @required bool expandChildren, - @required bool expandProperties, - }) { + RemoteDiagnosticsNode parent, + InspectorTreeNode treeNode, + List children, { + @required bool expandChildren, + @required bool expandProperties, + }) { assert(expandChildren != null); assert(expandProperties != null); treeNode.isExpanded = expandChildren; diff --git a/packages/devtools_app/lib/src/performance/legacy/performance_model.dart b/packages/devtools_app/lib/src/performance/legacy/performance_model.dart index f69d5d917a9..51deaae0d59 100644 --- a/packages/devtools_app/lib/src/performance/legacy/performance_model.dart +++ b/packages/devtools_app/lib/src/performance/legacy/performance_model.dart @@ -681,8 +681,7 @@ abstract class LegacyTimelineEvent extends TreeNode } @override - void addChild(LegacyTimelineEvent child, {int index}) { - assert(index == null); + void addChild(LegacyTimelineEvent child) { void _putChildInTree(LegacyTimelineEvent root) { // [root] is a leaf. Add child here. if (root.children.isEmpty) { @@ -971,8 +970,7 @@ class LegacyAsyncTimelineEvent extends LegacyTimelineEvent { } @override - void addChild(LegacyTimelineEvent child, {int index}) { - assert(index == null); + void addChild(LegacyTimelineEvent child) { final LegacyAsyncTimelineEvent _child = child; // Short circuit if we are using an explicit parentId. if (_child.hasExplicitParent && diff --git a/packages/devtools_app/lib/src/performance/performance_model.dart b/packages/devtools_app/lib/src/performance/performance_model.dart index dddb019ddf3..ba34878012e 100644 --- a/packages/devtools_app/lib/src/performance/performance_model.dart +++ b/packages/devtools_app/lib/src/performance/performance_model.dart @@ -789,8 +789,7 @@ abstract class TimelineEvent extends TreeNode } @override - void addChild(TimelineEvent child, {int index}) { - assert(index == null); + void addChild(TimelineEvent child) { void _putChildInTree(TimelineEvent root) { // [root] is a leaf. Add child here. if (root.children.isEmpty) { @@ -1103,8 +1102,7 @@ class AsyncTimelineEvent extends TimelineEvent { } @override - void addChild(TimelineEvent child, {int index}) { - assert(index == null); + void addChild(TimelineEvent child) { final AsyncTimelineEvent _child = child; // Short circuit if we are using an explicit parentId. if (_child.hasExplicitParent && diff --git a/packages/devtools_app/lib/src/theme.dart b/packages/devtools_app/lib/src/theme.dart index dd185631607..0b78d2837d5 100644 --- a/packages/devtools_app/lib/src/theme.dart +++ b/packages/devtools_app/lib/src/theme.dart @@ -421,10 +421,6 @@ extension ThemeDataExtension on ThemeData { TextStyle get subtleFixedFontStyle => fixedFontStyle.copyWith(color: unselectedWidgetColor); - TextStyle get toolTipFixedFontStyle => fixedFontStyle.copyWith( - color: colorScheme.tooltipTextColor, - ); - TextStyle get devToolsTitleStyle => textTheme.headline6.copyWith(color: Colors.white); diff --git a/packages/devtools_app/lib/src/tree.dart b/packages/devtools_app/lib/src/tree.dart index 4108e1ccf76..7259f49096c 100644 --- a/packages/devtools_app/lib/src/tree.dart +++ b/packages/devtools_app/lib/src/tree.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:flutter/material.dart' hide Stack; import 'collapsible_mixin.dart'; @@ -14,8 +12,7 @@ class TreeView> extends StatefulWidget { const TreeView({ this.dataRoots, this.dataDisplayProvider, - this.onItemSelected, - this.onItemExpanded, + this.onItemPressed, this.shrinkWrap = false, this.itemExtent, this.onTraverse, @@ -33,14 +30,7 @@ class TreeView> extends StatefulWidget { final Widget Function(T, VoidCallback) dataDisplayProvider; - /// Invoked when a tree node is selected. If [onItemExpanded] is not - /// provided, this method will also be called when the expand button is - /// tapped. - final FutureOr Function(T) onItemSelected; - - /// If provided, this method will be called when the expand button is tapped. - /// Otherwise, [onItemSelected] will be invoked, if provided. - final FutureOr Function(T) onItemExpanded; + final void Function(T) onItemPressed; final double itemExtent; @@ -87,33 +77,20 @@ class _TreeViewState> extends State> item, buildDisplay: (onPressed) => widget.dataDisplayProvider(item, onPressed), - onItemSelected: _onItemSelected, - onItemExpanded: _onItemExpanded, + onItemPressed: _onItemPressed, ); }, ); } // TODO(kenz): animate expansions and collapses. - void _onItemSelected(T item) async { - // Order of execution matters for the below calls. - if (widget.onItemExpanded == null && item.isExpandable) { - item.toggleExpansion(); - } - if (widget.onItemSelected != null) { - await widget.onItemSelected(item); - } - _updateItems(); - } + void _onItemPressed(T item) { + if (!item.isExpandable) return; - void _onItemExpanded(T item) async { - if (item.isExpandable) { - item.toggleExpansion(); - } - if (widget.onItemExpanded != null) { - await widget.onItemExpanded(item); - } else if (widget.onItemSelected != null) { - await widget.onItemSelected(item); + // Order of execution matters for the below calls. + item.toggleExpansion(); + if (widget.onItemPressed != null) { + widget.onItemPressed(item); } _updateItems(); } @@ -129,19 +106,13 @@ class _TreeViewState> extends State> } class TreeViewItem> extends StatefulWidget { - const TreeViewItem( - this.data, { - this.buildDisplay, - this.onItemExpanded, - this.onItemSelected, - }); + const TreeViewItem(this.data, {this.buildDisplay, this.onItemPressed}); final T data; final Widget Function(VoidCallback onPressed) buildDisplay; - final void Function(T) onItemSelected; - final void Function(T) onItemExpanded; + final void Function(T) onItemPressed; @override _TreeViewItemState createState() => _TreeViewItemState(); @@ -153,27 +124,23 @@ class _TreeViewItemState> extends State> Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only(left: nodeIndent(widget.data)), - child: Container( - color: - widget.data.isSelected ? Theme.of(context).selectedRowColor : null, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - widget.data.isExpandable - ? InkWell( - onTap: _onExpanded, - child: RotationTransition( - turns: expandArrowAnimation, - child: Icon( - Icons.keyboard_arrow_down, - size: defaultIconSize, - ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + widget.data.isExpandable + ? InkWell( + onTap: _onPressed, + child: RotationTransition( + turns: expandArrowAnimation, + child: Icon( + Icons.arrow_drop_down, + size: defaultIconSize, ), - ) - : SizedBox(width: defaultIconSize), - Expanded(child: widget.buildDisplay(_onSelected)), - ], - ), + ), + ) + : SizedBox(width: defaultIconSize), + Expanded(child: widget.buildDisplay(_onPressed)), + ], ), ); } @@ -191,13 +158,8 @@ class _TreeViewItemState> extends State> return dataObject.level * defaultSpacing; } - void _onExpanded() { - widget?.onItemExpanded(widget.data); - setExpanded(widget.data.isExpanded); - } - - void _onSelected() { - widget?.onItemSelected(widget.data); + void _onPressed() { + widget.onItemPressed(widget.data); setExpanded(widget.data.isExpanded); } } diff --git a/packages/devtools_app/lib/src/trees.dart b/packages/devtools_app/lib/src/trees.dart index a4523067f83..95f6e105e63 100644 --- a/packages/devtools_app/lib/src/trees.dart +++ b/packages/devtools_app/lib/src/trees.dart @@ -43,9 +43,6 @@ abstract class TreeNode> { bool get isRoot => parent == null; - bool get isSelected => _selected; - bool _selected = false; - T get root { if (_root != null) { return _root; @@ -81,22 +78,15 @@ abstract class TreeNode> { _isExpanded = true; } - void select() { - _selected = true; - } - - void unselect() { - _selected = false; - } - // TODO(jacobr): cache the value of whether the node should be shown // so that lookups on this class are O(1) invalidating the cache when nodes // up the tree are expanded and collapsed. /// Whether this node should be shown in the tree. /// /// When using this, consider caching the value. It is O([level]) to compute. - bool shouldShow() => - parent == null || (parent.isExpanded && parent.shouldShow()); + bool shouldShow() { + return parent == null || (parent.isExpanded && parent.shouldShow()); + } void collapse() { _isExpanded = false; @@ -109,23 +99,10 @@ abstract class TreeNode> { /// Override to handle pressing on a Leaf node. void leaf() {} - void addChild(T child, {int index}) { - index ??= children.length; - assert(index <= children.length); - children.insert(index, child); + void addChild(T child) { + children.add(child); child.parent = this; - child.index = index; - for (int i = index + 1; i < children.length; ++i) { - children[i].index++; - } - } - - T removeChildAtIndex(int index) { - assert(index < children.length); - for (int i = index + 1; i < children.length; ++i) { - children[i].index--; - } - return children.removeAt(index); + child.index = children.length - 1; } void addAllChildren(List children) { @@ -139,12 +116,6 @@ abstract class TreeNode> { }); } - /// Expands this node and each parent node recursively. - void expandAscending() { - expand(); - parent?.expandAscending(); - } - /// Collapses this node and all of its children (cascading). void collapseCascading() { breadthFirstTraversal(this, action: (T node) { diff --git a/packages/devtools_app/lib/src/vm_developer/vm_service_private_extensions.dart b/packages/devtools_app/lib/src/vm_developer/vm_service_private_extensions.dart index 9dc09dbb8c5..44328c217f4 100644 --- a/packages/devtools_app/lib/src/vm_developer/vm_service_private_extensions.dart +++ b/packages/devtools_app/lib/src/vm_developer/vm_service_private_extensions.dart @@ -32,7 +32,3 @@ extension IsolatePrivateViewExtension on Isolate { int get newSpaceCapacity => json['_heaps']['new']['capacity']; int get oldSpaceCapacity => json['_heaps']['old']['capacity']; } - -extension ClassPrivateViewExtension on Class { - String get vmName => json['_vmName']; -} diff --git a/packages/devtools_app/lib/src/vm_service_wrapper.dart b/packages/devtools_app/lib/src/vm_service_wrapper.dart index 49a3d181eed..0b6d85c074a 100644 --- a/packages/devtools_app/lib/src/vm_service_wrapper.dart +++ b/packages/devtools_app/lib/src/vm_service_wrapper.dart @@ -1266,24 +1266,4 @@ extension VmServicePrivate on VmServiceWrapper { isolateId: isolateId, parser: PortList.parse, ); - - Future getReachableSize(String isolateId, String targetId) => - _privateRpcInvoke( - 'getReachableSize', - isolateId: isolateId, - args: { - 'targetId': targetId, - }, - parser: InstanceRef.parse, - ); - - Future getRetainedSize(String isolateId, String targetId) => - _privateRpcInvoke( - 'getRetainedSize', - isolateId: isolateId, - args: { - 'targetId': targetId, - }, - parser: InstanceRef.parse, - ); } diff --git a/packages/devtools_app/macos/Runner.xcodeproj/project.pbxproj b/packages/devtools_app/macos/Runner.xcodeproj/project.pbxproj index a4d00fa9693..bc03301f8ac 100644 --- a/packages/devtools_app/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/devtools_app/macos/Runner.xcodeproj/project.pbxproj @@ -308,9 +308,14 @@ files = ( ); inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/file_selector_macos/file_selector_macos.framework", + "${BUILT_PRODUCTS_DIR}/url_launcher_macos/url_launcher_macos.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_selector_macos.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_macos.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; diff --git a/packages/devtools_app/test/debugger_controller_test.dart b/packages/devtools_app/test/debugger_controller_test.dart index 466518f1f81..db5d19bc0b4 100644 --- a/packages/devtools_app/test/debugger_controller_test.dart +++ b/packages/devtools_app/test/debugger_controller_test.dart @@ -97,7 +97,7 @@ void main() { expect(history.current.value, ref1); }); - test('moveForward', () { + test('moveBack', () { history.pushEntry(ref1); history.pushEntry(ref2); @@ -257,9 +257,7 @@ void main() { DebuggerController debuggerController; setUp(() { - debuggerController = TestDebuggerController( - initialSwitchToIsolate: false, - ); + debuggerController = DebuggerController(initialSwitchToIsolate: false); debuggerController.parsedScript.value = ParsedScript( script: testScript, highlighter: null, diff --git a/packages/devtools_app/test/debugger_evalution_test.dart b/packages/devtools_app/test/debugger_evalution_test.dart index b1bb5c0d09d..7074b65ef19 100644 --- a/packages/devtools_app/test/debugger_evalution_test.dart +++ b/packages/devtools_app/test/debugger_evalution_test.dart @@ -10,7 +10,6 @@ import 'package:devtools_app/src/ui/search.dart'; import 'package:devtools_app/src/utils.dart'; import 'package:devtools_test/flutter_test_driver.dart'; import 'package:devtools_test/flutter_test_environment.dart'; -import 'package:devtools_test/mocks.dart'; import 'package:devtools_test/utils.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pedantic/pedantic.dart'; @@ -26,7 +25,7 @@ void main() { setUp(() async { isAlive = Disposable(); await env.setupEnvironment(); - debuggerController = TestDebuggerController(); + debuggerController = DebuggerController(); eval = EvalOnDartLibrary( 'package:flutter_app/src/autocomplete.dart', serviceManager.service, disableBreakpoints: false); diff --git a/packages/devtools_app/test/debugger_screen_test.dart b/packages/devtools_app/test/debugger_screen_test.dart index 7eb37488672..fd005c9aae7 100644 --- a/packages/devtools_app/test/debugger_screen_test.dart +++ b/packages/devtools_app/test/debugger_screen_test.dart @@ -10,7 +10,6 @@ import 'package:devtools_app/src/debugger/controls.dart'; import 'package:devtools_app/src/debugger/debugger_controller.dart'; import 'package:devtools_app/src/debugger/debugger_model.dart'; import 'package:devtools_app/src/debugger/debugger_screen.dart'; -import 'package:devtools_app/src/debugger/program_explorer_model.dart'; import 'package:devtools_app/src/globals.dart'; import 'package:devtools_app/src/service_manager.dart'; import 'package:devtools_test/mocks.dart'; @@ -209,7 +208,7 @@ void main() { // this so that forcing a scroll event is no longer necessary. Remove // once the change is in the stable release. debuggerController.showScriptLocation(ScriptLocation(mockScriptRef, - location: const SourcePosition(line: 50, column: 50))); + location: SourcePosition(line: 50, column: 50))); await tester.pumpAndSettle(); expect(find.byType(Scrollbar), findsNWidgets(2)); @@ -224,7 +223,7 @@ void main() { }, skip: !Platform.isMacOS); }); - testWidgetsWithWindowSize('File Explorer hidden', windowSize, + testWidgetsWithWindowSize('Libraries hidden', windowSize, (WidgetTester tester) async { final scripts = [ ScriptRef(uri: 'package:/test/script.dart', id: 'test-script') @@ -233,40 +232,27 @@ void main() { when(debuggerController.sortedScripts).thenReturn(ValueNotifier(scripts)); when(debuggerController.showFileOpener).thenReturn(ValueNotifier(false)); - // File Explorer view is hidden - when(debuggerController.fileExplorerVisible) + // Libraries view is hidden + when(debuggerController.librariesVisible) .thenReturn(ValueNotifier(false)); await pumpDebuggerScreen(tester, debuggerController); - expect(find.text('File Explorer'), findsOneWidget); + expect(find.text('Libraries'), findsOneWidget); }); - testWidgetsWithWindowSize('File Explorer visible', windowSize, + testWidgetsWithWindowSize('Libraries visible', windowSize, (WidgetTester tester) async { final scripts = [ ScriptRef(uri: 'package:test/script.dart', id: 'test-script') ]; when(debuggerController.sortedScripts).thenReturn(ValueNotifier(scripts)); - when(debuggerController.programExplorerController.rootObjectNodes) - .thenReturn( - ValueNotifier( - [ - VMServiceObjectNode( - debuggerController.programExplorerController, - 'package:test', - null, - ), - ], - ), - ); when(debuggerController.showFileOpener).thenReturn(ValueNotifier(false)); - // File Explorer view is shown - when(debuggerController.fileExplorerVisible) - .thenReturn(ValueNotifier(true)); + // Libraries view is shown + when(debuggerController.librariesVisible).thenReturn(ValueNotifier(true)); await pumpDebuggerScreen(tester, debuggerController); - // One for the button and one for the title of the File Explorer view. - expect(find.text('File Explorer'), findsNWidgets(2)); + // One for the button and one for the title of the Libraries view. + expect(find.text('Libraries'), findsNWidgets(2)); // test for items in the libraries tree expect(find.text(scripts.first.uri.split('/').first), findsOneWidget); @@ -290,7 +276,7 @@ void main() { final breakpointsWithLocation = [ BreakpointAndSourcePosition.create( breakpoints.first, - const SourcePosition(line: 10, column: 1), + SourcePosition(line: 10, column: 1), ) ]; @@ -536,7 +522,7 @@ void main() { ), kind: FrameKind.kRegular, ), - position: const SourcePosition( + position: SourcePosition( line: 1, column: 10, ), diff --git a/packages/devtools_app/test/inspector_tree_test.dart b/packages/devtools_app/test/inspector_tree_test.dart index 0fd72a96f67..0016831e35e 100644 --- a/packages/devtools_app/test/inspector_tree_test.dart +++ b/packages/devtools_app/test/inspector_tree_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:devtools_app/src/debugger/debugger_controller.dart'; import 'package:devtools_app/src/globals.dart'; import 'package:devtools_app/src/inspector/inspector_service.dart'; import 'package:devtools_app/src/inspector/inspector_tree.dart'; @@ -12,6 +13,7 @@ import 'package:devtools_test/mocks.dart'; import 'package:devtools_test/utils.dart'; import 'package:devtools_test/wrappers.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart' hide Fake; import 'package:mockito/mockito.dart'; @@ -37,7 +39,7 @@ void main() { onNodeAdded: (_, __) {}, onClientActiveChange: (_) {}, ); - final debuggerController = TestDebuggerController(); + final debuggerController = DebuggerController(); await tester.pumpWidget(wrap(InspectorTree( controller: controller, debuggerController: debuggerController, @@ -76,7 +78,7 @@ void main() { final treeController = inspectorTreeControllerFromNode(diagnosticNode); await tester.pumpWidget(wrap(InspectorTree( controller: treeController, - debuggerController: TestDebuggerController(), + debuggerController: DebuggerController(), ))); expect(find.richText('Text: "Content"'), findsOneWidget); @@ -98,7 +100,7 @@ void main() { final treeController = inspectorTreeControllerFromNode(diagnosticNode); await tester.pumpWidget(wrap(InspectorTree( controller: treeController, - debuggerController: TestDebuggerController(), + debuggerController: DebuggerController(), ))); expect(find.richText('Text: "Rich text"'), findsOneWidget); @@ -116,7 +118,7 @@ void main() { await tester.pumpWidget( wrap(InspectorTree( controller: treeController, - debuggerController: TestDebuggerController(), + debuggerController: DebuggerController(), )), ); diff --git a/packages/devtools_test/lib/mocks.dart b/packages/devtools_test/lib/mocks.dart index 853b7146d1f..7dcf3626297 100644 --- a/packages/devtools_test/lib/mocks.dart +++ b/packages/devtools_test/lib/mocks.dart @@ -660,16 +660,6 @@ class MockPerformanceController extends Mock implements PerformanceController {} class MockProfilerScreenController extends Mock implements ProfilerScreenController {} -class TestDebuggerController extends DebuggerController { - TestDebuggerController({bool initialSwitchToIsolate = true}) - : super(initialSwitchToIsolate: initialSwitchToIsolate); - - @override - ProgramExplorerController get programExplorerController => - _explorerController; - final _explorerController = MockProgramExplorerController.withDefaults(); -} - class MockDebuggerController extends Mock implements DebuggerController { MockDebuggerController(); @@ -681,8 +671,7 @@ class MockDebuggerController extends Mock implements DebuggerController { when(debuggerController.isSystemIsolate).thenReturn(false); when(debuggerController.breakpointsWithLocation) .thenReturn(ValueNotifier([])); - when(debuggerController.fileExplorerVisible) - .thenReturn(ValueNotifier(false)); + when(debuggerController.librariesVisible).thenReturn(ValueNotifier(false)); when(debuggerController.currentScriptRef).thenReturn(ValueNotifier(null)); when(debuggerController.sortedScripts).thenReturn(ValueNotifier([])); when(debuggerController.selectedBreakpoint).thenReturn(ValueNotifier(null)); @@ -699,25 +688,6 @@ class MockDebuggerController extends Mock implements DebuggerController { .thenReturn(ValueNotifier(null)); return debuggerController; } - - @override - final programExplorerController = - MockProgramExplorerController.withDefaults(); -} - -class MockProgramExplorerController extends Mock - implements ProgramExplorerController { - MockProgramExplorerController(); - - factory MockProgramExplorerController.withDefaults() { - final controller = MockProgramExplorerController(); - when(controller.initialized).thenReturn(ValueNotifier(true)); - when(controller.rootObjectNodes).thenReturn(ValueNotifier([])); - when(controller.outlineNodes).thenReturn(ValueNotifier([])); - when(controller.isLoadingOutline).thenReturn(ValueNotifier(false)); - - return controller; - } } class MockVM extends Mock implements VM {}