Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import '../../shared/console/primitives/simple_items.dart';
import '../../shared/diagnostics/diagnostics_node.dart';
import '../../shared/diagnostics/inspector_service.dart';
import '../../shared/diagnostics/primitives/instance_ref.dart';
import '../../shared/diagnostics/primitives/source_location.dart';
import '../../shared/framework/screen_controllers.dart';
import '../../shared/globals.dart';
import '../../shared/managers/notifications.dart';
Expand All @@ -55,6 +56,9 @@ typedef WidgetTreeNodeProperties = ({

/// Layout properties for the widget.
LayoutProperties? layoutProperties,

/// Source location where the selected widget was created.
InspectorSourceLocation? creationLocation,
});

/// This class is based on the InspectorPanel class from the Flutter IntelliJ
Expand Down Expand Up @@ -255,6 +259,7 @@ class InspectorController extends DisposableController
widgetProperties: [],
renderProperties: [],
layoutProperties: null,
creationLocation: null,
));

/// Whether the implementation widgets are hidden in the widget tree.
Expand Down Expand Up @@ -817,14 +822,15 @@ class InspectorController extends DisposableController
final widgetProperties = <RemoteDiagnosticsNode>[];
final renderProperties = <RemoteDiagnosticsNode>[];
LayoutProperties? layoutProperties;
InspectorSourceLocation? creationLocation;
final diagnostic = node?.diagnostic;
final objectGroupApi = diagnostic?.objectGroupApi;
if (diagnostic != null && objectGroupApi != null) {
try {
// Fetch widget properties:
final wProperties = await diagnostic.getProperties(objectGroupApi);
// Check if the selected node has changed, and if so return early:
if (_selectedNode.value != node) {
if (disposed || _selectedNode.value != node) {
return;
}
widgetProperties.addAll(
Expand All @@ -838,11 +844,22 @@ class InspectorController extends DisposableController
diagnostic,
forFlexLayout: false,
);
// Fetch creation location from the details subtree. Summary tree nodes
// omit creationLocation when loaded with fullDetails: false.
final detailsNode = await objectGroupApi.getDetailsSubtree(
diagnostic,
subtreeDepth: 0,
);
// Check if the selected node has changed, and if so return early:
if (disposed || _selectedNode.value != node) {
return;
}
creationLocation = detailsNode?.creationLocation;
// Fetch RenderObject properties:
for (final renderObject in renderProperties) {
final rProperties = await renderObject.getProperties(objectGroupApi);
// Check if the selected node has changed, and if so return early:
if (_selectedNode.value != node) {
if (disposed || _selectedNode.value != node) {
return;
}
renderProperties.addAll(rProperties);
Expand All @@ -851,10 +868,12 @@ class InspectorController extends DisposableController
_log.warning(e, st);
}
}
if (disposed) return;
_selectedNodeProperties.value = (
widgetProperties: widgetProperties,
renderProperties: renderProperties,
layoutProperties: layoutProperties,
creationLocation: creationLocation,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.

import 'dart:async';
import 'dart:math';

import 'package:devtools_app_shared/service.dart';
import 'package:devtools_app_shared/ui.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

import '../../../shared/analytics/constants.dart' as gac;
import '../../../shared/console/widgets/description.dart';
import '../../../shared/globals.dart';
import '../../../shared/diagnostics/diagnostics_node.dart';
import '../../../shared/primitives/utils.dart';
import '../../../shared/ui/tab.dart';
Expand Down Expand Up @@ -37,10 +41,7 @@ class _DetailsTableState extends State<DetailsTable> {
RemoteDiagnosticsNode? get selectedNode =>
widget.controller.selectedDiagnostic;

final _widgetPropertiesTab = DevToolsTab.create(
tabName: 'Widget properties',
gaPrefix: DetailsTable.gaPrefix,
);
late final DevToolsTab _widgetPropertiesTab;

final _renderObjectTab = DevToolsTab.create(
tabName: 'Render object',
Expand All @@ -57,6 +58,11 @@ class _DetailsTableState extends State<DetailsTable> {
@override
void initState() {
super.initState();
_widgetPropertiesTab = DevToolsTab.create(
tabName: 'Widget properties',
gaPrefix: DetailsTable.gaPrefix,
trailing: WidgetCreationLocationTrailing(controller: widget.controller),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This trailing widget should probably be on all tabs, not just the properties tab.

If you update the AnalyticsTabbedView constructor to this, then you can set the trailingWidgets property directly in the AnalyticsTabbedView constructor, and revert the changes to _widgetPropertiesTab.

  AnalyticsTabbedView({
    super.key,
    required this.tabs,
    required this.gaScreen,
    this.sendAnalytics = true,
    this.onTabChanged,
    this.initialSelectedIndex,
    this.analyticsSessionIdentifier,
    this.staticSingleTab = false,
    List<Widget> trailingWidgets = const [],
  }) : trailingWidgets = [
         ...List.generate(
           tabs.length,
           (index) => tabs[index].tab.trailing ?? const SizedBox(),
         ),
         ...trailingWidgets,
       ];

);
_widgetPropertiesScrollController = ScrollController();
_renderPropertiesScrollController = ScrollController();
}
Expand All @@ -76,7 +82,6 @@ class _DetailsTableState extends State<DetailsTable> {
final widgetProperties = properties.widgetProperties;
final renderProperties = properties.renderProperties;
final layoutProperties = properties.layoutProperties;

final renderTabExists = renderProperties.isNotEmpty;
final flexExplorerTabExists = selectedNode?.isFlexLayout ?? false;

Expand Down Expand Up @@ -160,6 +165,90 @@ class _DetailsTableState extends State<DetailsTable> {
];
}

/// Displays the source file path for the selected widget in the tab bar.
///
/// Matches the legacy inspector format: `filename.dart:line:column`.
/// Tapping the link navigates the connected IDE to the source location.
class WidgetCreationLocationTrailing extends StatefulWidget {
const WidgetCreationLocationTrailing({super.key, required this.controller});

final InspectorController controller;

@override
State<WidgetCreationLocationTrailing> createState() =>
_WidgetCreationLocationTrailingState();
}

class _WidgetCreationLocationTrailingState
extends State<WidgetCreationLocationTrailing> {
late final TapGestureRecognizer _tapRecognizer;

@override
void initState() {
super.initState();
_tapRecognizer = TapGestureRecognizer()
..onTap = () {
unawaited(_navigateToLocation());
};
}

@override
void dispose() {
_tapRecognizer.dispose();
super.dispose();
}

Future<void> _navigateToLocation() async {
final location =
widget.controller.selectedNodeProperties.value.creationLocation;
final file = location?.getFile();
if (file == null) {
return;
}

await serviceConnection.serviceManager.service?.navigateToCode(
fileUriString: file,
line: location!.getLine(),
column: location.getColumn(),
source: 'devtools.inspector',
);
}

@override
Widget build(BuildContext context) {
return ValueListenableBuilder<WidgetTreeNodeProperties>(
valueListenable: widget.controller.selectedNodeProperties,
builder: (context, properties, _) {
final location = properties.creationLocation;
final file = location?.getFile();
if (file == null) {
return const SizedBox.shrink();
}

final line = location!.getLine();
final column = location.getColumn();
final shortLocation = '${fileNameFromUri(file)}:$line:$column';
final fullLocation = '$file:$line:$column';

return Padding(
padding: const EdgeInsets.symmetric(horizontal: denseSpacing),
child: DevToolsTooltip(
message: fullLocation,
child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan(
text: shortLocation,
style: Theme.of(context).linkTextStyle,
recognizer: _tapRecognizer,
),
),
),
);
},
);
}
}

/// Displays a widget's properties, including the layout properties and a
/// layout visualizer.
class PropertiesView extends StatefulWidget {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,24 @@ abstract class InspectorObjectGroupBase
);
}

@override
Future<RemoteDiagnosticsNode?> getDetailsSubtree(
RemoteDiagnosticsNode? node, {
int subtreeDepth = 2,
}) async {
if (node == null || node.valueRef.id == null) return null;
return parseDiagnosticsNodeDaemon(
invokeServiceMethodDaemonParams(
WidgetInspectorServiceExtensions.getDetailsSubtree.name,
{
'objectGroup': groupName,
'arg': node.valueRef.id,
'subtreeDepth': subtreeDepth.toString(),
},
),
);
}

@override
bool isLocalClass(RemoteDiagnosticsNode node) =>
inspectorService.isLocalClass(node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,10 @@ abstract class InspectorObjectGroupApi<T extends DiagnosticableTree>
);

Future<List<T>> getProperties(InspectorInstanceRef instanceRef);

/// Returns a details subtree for [node], including creation location data.
///
/// Pass a small [subtreeDepth] (for example `0`) when only node-level details
/// such as creation location are needed.
Future<T?> getDetailsSubtree(T? node, {int subtreeDepth = 2});
}
5 changes: 4 additions & 1 deletion packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ To learn more about DevTools, check out the

## Inspector updates

TODO: Remove this section if there are not any updates.
* Added the widget source file path to the Inspector details pane
(`filename.dart:line:column`), matching legacy Inspector behavior. -
[#9972](https://github.com/flutter/devtools/pull/9972),
[#9922](https://github.com/flutter/devtools/issues/9922)

## Performance updates

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -708,22 +708,3 @@ Future<void> _resetPubRootDirectories(InspectorService inspectorService) async {
await inspectorService.addPubRootDirectories([rootLibrary]);
}
}

extension _ObjectGroupTestExtension on ObjectGroup {
Future<RemoteDiagnosticsNode?> getDetailsSubtree(
RemoteDiagnosticsNode? node, {
int subtreeDepth = 2,
}) async {
if (node == null) return null;
final args = {
'objectGroup': groupName,
'arg': node.valueRef.id,
'subtreeDepth': subtreeDepth.toString(),
};
final json = await invokeServiceMethodDaemonParams(
WidgetInspectorServiceExtensions.getDetailsSubtree.name,
args,
);
return parseDiagnosticsNodeHelper(json as Map<String, Object?>?);
}
}
Loading