Skip to content

Commit

Permalink
Show extension screens in the VS code sidebar DevTools dropdown (#6709)
Browse files Browse the repository at this point in the history
  • Loading branch information
kenzieschmoll committed Jan 19, 2024
1 parent 46f83fc commit 0fba400
Show file tree
Hide file tree
Showing 17 changed files with 670 additions and 199 deletions.
1 change: 1 addition & 0 deletions packages/devtools_app/lib/devtools_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,4 @@ export 'src/shared/ui/hover.dart';
export 'src/shared/ui/icons.dart';
export 'src/shared/ui/search.dart';
export 'src/shared/utils.dart';
export 'src/standalone_ui/api/vs_code_api.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ExtensionScreen extends Screen {
ExtensionScreen(this.extensionConfig)
: super.conditional(
// TODO(kenz): we may need to ensure this is a unique id.
id: '${extensionConfig.name}_ext',
id: extensionConfig.screenId,
title: extensionConfig.name,
icon: extensionConfig.icon,
// TODO(kenz): support static DevTools extensions.
Expand Down Expand Up @@ -134,4 +134,6 @@ extension ExtensionConfigExtension on DevToolsExtensionConfig {
materialIconCodePoint,
fontFamily: 'MaterialIcons',
);

String get screenId => '${name}_ext';
}
103 changes: 65 additions & 38 deletions packages/devtools_app/lib/src/extensions/extension_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ import '../shared/server/server.dart' as server;

class ExtensionService extends DisposableController
with AutoDisposeControllerMixin {
ExtensionService({this.fixedAppRootPath});

/// The fixed (unchanging) root path for the application this
/// [ExtensionService] will manage DevTools extensions for.
///
/// When null, the root path will instead be calculated from the
/// [serviceManager]'s currently connected app. See [_initRootPath].
final String? fixedAppRootPath;

/// The root path for the Dart / Flutter application this [ExtensionService]
/// will manage DevTools extensions for.
String? _rootPath;

/// All the DevTools extensions that are available for the connected
/// application, regardless of whether they have been enabled or disabled
/// by the user.
Expand Down Expand Up @@ -45,31 +58,43 @@ class ExtensionService extends DisposableController
<String, ValueNotifier<ExtensionEnabledState>>{};

Future<void> initialize() async {
await _initRootPath();
await _maybeRefreshExtensions();
addAutoDisposeListener(
serviceConnection.serviceManager.connectedState,
() async {
if (serviceConnection.serviceManager.connectedState.value.connected) {
await _maybeRefreshExtensions();
} else {
_reset();
}
},
);

// TODO(https://github.com/flutter/flutter/issues/134470): refresh on
// hot reload and hot restart events instead.
addAutoDisposeListener(
serviceConnection.serviceManager.isolateManager.mainIsolate,
() async {
if (serviceConnection.serviceManager.isolateManager.mainIsolate.value !=
null) {
await _maybeRefreshExtensions();
} else {
_reset();
}
},
);
cancelListeners();

// We only need to add VM service manager related listeners when we are
// interacting with the currently connected app (i.e. when
// [fixedAppRootPath] is null).
if (fixedAppRootPath == null) {
addAutoDisposeListener(
serviceConnection.serviceManager.connectedState,
() async {
if (serviceConnection.serviceManager.connectedState.value.connected) {
await _initRootPath();
await _maybeRefreshExtensions();
} else {
_reset();
}
},
);

// TODO(https://github.com/flutter/flutter/issues/134470): refresh on
// hot reload and hot restart events instead.
addAutoDisposeListener(
serviceConnection.serviceManager.isolateManager.mainIsolate,
() async {
if (serviceConnection
.serviceManager.isolateManager.mainIsolate.value !=
null) {
await _initRootPath();
await _maybeRefreshExtensions();
} else {
_reset();
}
},
);
}

addAutoDisposeListener(
preferences.devToolsExtensions.showOnlyEnabledExtensions,
Expand All @@ -83,29 +108,31 @@ class ExtensionService extends DisposableController
// .dart_tool/package_config.json file for changes.
}

Future<void> _initRootPath() async {
_rootPath = fixedAppRootPath ?? await _connectedAppRootPath();
}

Future<void> _maybeRefreshExtensions() async {
final appRootPath = await _connectedAppRootPath();
if (appRootPath == null) return;
if (_rootPath == null) return;

_refreshInProgress.value = true;
_availableExtensions.value =
await server.refreshAvailableExtensions(appRootPath)
await server.refreshAvailableExtensions(_rootPath!)
..sort();
await _refreshExtensionEnabledStates();
_refreshInProgress.value = false;
}

Future<void> _refreshExtensionEnabledStates() async {
final appRootPath = await _connectedAppRootPath();
if (appRootPath == null) return;
if (_rootPath == null) return;

final onlyIncludeEnabled =
preferences.devToolsExtensions.showOnlyEnabledExtensions.value;

final visible = <DevToolsExtensionConfig>[];
for (final extension in _availableExtensions.value) {
final stateFromOptionsFile = await server.extensionEnabledState(
rootPath: appRootPath,
rootPath: _rootPath!,
extensionName: extension.name,
);
final stateNotifier = _extensionEnabledStates.putIfAbsent(
Expand Down Expand Up @@ -133,18 +160,18 @@ class ExtensionService extends DisposableController
DevToolsExtensionConfig extension, {
required bool enable,
}) async {
final appRootPath = await _connectedAppRootPath();
if (appRootPath != null) {
await server.extensionEnabledState(
rootPath: appRootPath,
extensionName: extension.name,
enable: enable,
);
await _refreshExtensionEnabledStates();
}
if (_rootPath == null) return;

await server.extensionEnabledState(
rootPath: _rootPath!,
extensionName: extension.name,
enable: enable,
);
await _refreshExtensionEnabledStates();
}

void _reset() {
_rootPath = null;
_availableExtensions.value = [];
_visibleExtensions.value = [];
_extensionEnabledStates.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ bool debugAnalytics = false;
///
/// This flag should never be checked in with a value of true - this is covered
/// by a test.
final debugDevToolsExtensions = _debugDevToolsExtensions || integrationTestMode;
final debugDevToolsExtensions =
_debugDevToolsExtensions || integrationTestMode || testMode || stagerMode;
const _debugDevToolsExtensions = false;

List<DevToolsExtensionConfig> debugHandleRefreshAvailableExtensions(
Expand Down
17 changes: 17 additions & 0 deletions packages/devtools_app/lib/src/shared/globals.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

import 'package:devtools_app_shared/utils.dart';
import 'package:flutter/foundation.dart';

import '../extensions/extension_service.dart';
import '../screens/debugger/breakpoint_manager.dart';
Expand Down Expand Up @@ -70,3 +71,19 @@ bool _integrationTestMode = false;
void setIntegrationTestMode() {
_integrationTestMode = true;
}

/// Whether DevTools is being run in a test environment.
bool get testMode => _testMode;
bool _testMode = false;
void setTestMode() {
_testMode = true;
}

/// Whether DevTools is being run as a stager app.
bool get stagerMode => _stagerMode;
bool _stagerMode = false;
void setStagerMode() {
if (!kReleaseMode) {
_stagerMode = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import '../dart_tooling_api.dart';
import '../vs_code_api.dart';
import 'vs_code_api.dart';

// TODO(https://github.com/flutter/devtools/issues/7055): migrate away from
// postMessage and use the Dart Tooling Daemon to communicate between Dart
// tooling surfaces.

/// Whether to enable verbose logging for postMessage communication.
///
/// This is useful for debugging when running inside VS Code.
Expand Down
Loading

0 comments on commit 0fba400

Please sign in to comment.