Skip to content

Commit fd9297d

Browse files
authored
Reland ProgramExplorer (#3448)
This reverts commit fcaf954.
1 parent 1ece492 commit fd9297d

30 files changed

+1503
-322
lines changed

packages/devtools_app/lib/devtools_app.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export 'src/common_widgets.dart';
1212
export 'src/connected_app.dart';
1313
export 'src/console_service.dart';
1414
export 'src/debugger/debugger_controller.dart';
15+
export 'src/debugger/program_explorer_controller.dart';
1516
export 'src/debugger/span_parser.dart';
1617
export 'src/debugger/syntax_highlighter.dart';
1718
export 'src/error_badge_manager.dart';

packages/devtools_app/lib/src/common_widgets.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,7 @@ class AreaPaneHeader extends StatelessWidget implements PreferredSizeWidget {
675675
this.needsLeftBorder = false,
676676
this.leftActions = const [],
677677
this.rightActions = const [],
678+
this.leftPadding = defaultSpacing,
678679
this.rightPadding = densePadding,
679680
this.tall = false,
680681
}) : super(key: key);
@@ -686,6 +687,7 @@ class AreaPaneHeader extends StatelessWidget implements PreferredSizeWidget {
686687
final bool needsLeftBorder;
687688
final List<Widget> leftActions;
688689
final List<Widget> rightActions;
690+
final double leftPadding;
689691
final double rightPadding;
690692
final bool tall;
691693

@@ -704,7 +706,7 @@ class AreaPaneHeader extends StatelessWidget implements PreferredSizeWidget {
704706
),
705707
color: theme.titleSolidBackgroundColor,
706708
),
707-
padding: EdgeInsets.only(left: defaultSpacing, right: rightPadding),
709+
padding: EdgeInsets.only(left: leftPadding, right: rightPadding),
708710
alignment: Alignment.centerLeft,
709711
child: Row(
710712
children: [

packages/devtools_app/lib/src/debugger/codeview.dart

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class CodeView extends StatefulWidget {
4242
const CodeView({
4343
Key key,
4444
this.controller,
45+
this.initialPosition,
4546
this.scriptRef,
4647
this.parsedScript,
4748
this.onSelected,
@@ -57,6 +58,7 @@ class CodeView extends StatefulWidget {
5758
static double get assumedCharacterWidth => scaleByFontFactor(16.0);
5859

5960
final DebuggerController controller;
61+
final ScriptLocation initialPosition;
6062
final ScriptRef scriptRef;
6163
final ParsedScript parsedScript;
6264

@@ -80,6 +82,10 @@ class _CodeViewState extends State<CodeView>
8082

8183
ParsedScript get parsedScript => widget.parsedScript;
8284

85+
// Used to ensure we don't update the scroll position when expanding or
86+
// collapsing the file explorer.
87+
ScriptRef _lastScriptRef;
88+
8389
@override
8490
void initState() {
8591
super.initState();
@@ -88,6 +94,16 @@ class _CodeViewState extends State<CodeView>
8894
gutterController = verticalController.addAndGet();
8995
textController = verticalController.addAndGet();
9096
horizontalController = ScrollController();
97+
_lastScriptRef = widget.scriptRef;
98+
99+
if (widget.initialPosition != null) {
100+
final location = widget.initialPosition.location;
101+
// Lines are 1-indexed. Scrolling to line 1 required a scroll position of
102+
// 0.
103+
final lineIndex = location.line - 1;
104+
final scrollPosition = lineIndex * CodeView.rowHeight;
105+
verticalController.jumpTo(scrollPosition);
106+
}
91107

92108
addAutoDisposeListener(
93109
widget.controller.scriptLocation,
@@ -124,12 +140,8 @@ class _CodeViewState extends State<CodeView>
124140
}
125141

126142
void _updateScrollPosition({bool animate = true}) {
127-
if (widget.controller.scriptLocation.value?.scriptRef != scriptRef) {
128-
return;
129-
}
130-
131-
final location = widget.controller.scriptLocation.value?.location;
132-
if (location?.line == null) {
143+
if (widget.controller.scriptLocation.value?.scriptRef?.uri !=
144+
scriptRef?.uri) {
133145
return;
134146
}
135147

@@ -138,17 +150,37 @@ class _CodeViewState extends State<CodeView>
138150
log('LinkedScrollControllerGroup has no attached controllers');
139151
return;
140152
}
153+
final location = widget.controller.scriptLocation.value?.location;
154+
if (location?.line == null) {
155+
// Don't scroll to top if we're just rebuilding the code view for the
156+
// same script.
157+
if (_lastScriptRef?.uri != scriptRef?.uri) {
158+
// Default to scrolling to the top of the script.
159+
if (animate) {
160+
verticalController.animateTo(
161+
0,
162+
duration: longDuration,
163+
curve: defaultCurve,
164+
);
165+
} else {
166+
verticalController.jumpTo(0);
167+
}
168+
_lastScriptRef = scriptRef;
169+
}
170+
return;
171+
}
141172

142173
final position = verticalController.position;
143174
final extent = position.extentInside;
144175

145176
// TODO(devoncarew): Adjust this so we don't scroll if we're already in the
146177
// middle third of the screen.
147178
if (parsedScript.lineCount * CodeView.rowHeight > extent) {
148-
// Scroll to the middle of the screen.
149179
final lineIndex = location.line - 1;
150-
final scrollPosition =
151-
lineIndex * CodeView.rowHeight - (extent - CodeView.rowHeight) / 2;
180+
final scrollPosition = lineIndex * CodeView.rowHeight -
181+
(widget.controller.shouldCenterScrollLocation
182+
? ((extent - CodeView.rowHeight) / 2)
183+
: 0);
152184
if (animate) {
153185
verticalController.animateTo(
154186
scrollPosition,
@@ -159,6 +191,7 @@ class _CodeViewState extends State<CodeView>
159191
verticalController.jumpTo(scrollPosition);
160192
}
161193
}
194+
_lastScriptRef = scriptRef;
162195
}
163196

164197
void _onPressed(int line) {
@@ -644,7 +677,7 @@ class _LinesState extends State<Lines> with AutoDisposeMixin {
644677

645678
if (isOutOfViewTop || isOutOfViewBottom) {
646679
// Scroll this search token to the middle of the view.
647-
final targetOffset = math.max(
680+
final targetOffset = math.max<double>(
648681
activeSearch.position.line * CodeView.rowHeight - widget.height / 2,
649682
0.0,
650683
);

packages/devtools_app/lib/src/debugger/controls.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import '../common_widgets.dart';
1212
import '../theme.dart';
1313
import '../ui/label.dart';
1414
import 'debugger_controller.dart';
15-
import 'scripts.dart';
1615

1716
class DebuggingControls extends StatefulWidget {
1817
const DebuggingControls({Key key}) : super(key: key);
@@ -118,13 +117,14 @@ class _DebuggingControlsState extends State<DebuggingControls>
118117

119118
Widget _librariesButton() {
120119
return ValueListenableBuilder(
121-
valueListenable: controller.librariesVisible,
120+
valueListenable: controller.fileExplorerVisible,
122121
builder: (context, visible, _) {
122+
const libraryIcon = Icons.insert_chart;
123123
return RoundedOutlinedBorder(
124124
child: Container(
125125
color: visible ? Theme.of(context).highlightColor : null,
126126
child: DebuggerButton(
127-
title: 'Libraries',
127+
title: 'File Explorer',
128128
icon: libraryIcon,
129129
onPressed: controller.toggleLibrariesVisible,
130130
),

packages/devtools_app/lib/src/debugger/debugger_controller.dart

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import '../ui/search.dart';
1919
import '../utils.dart';
2020
import '../vm_service_wrapper.dart';
2121
import 'debugger_model.dart';
22+
import 'program_explorer_controller.dart';
2223
import 'syntax_highlighter.dart';
2324

2425
// TODO(devoncarew): Add some delayed resume value notifiers (to be used to
@@ -33,12 +34,23 @@ class DebuggerController extends DisposableController
3334
// `initialSwitchToIsolate` can be set to false for tests to skip the logic
3435
// in `switchToIsolate`.
3536
DebuggerController({this.initialSwitchToIsolate = true}) {
37+
_programExplorerController = ProgramExplorerController(
38+
debuggerController: this,
39+
);
3640
autoDispose(serviceManager.onConnectionAvailable
3741
.listen(_handleConnectionAvailable));
3842
_scriptHistoryListener = () {
3943
_showScriptLocation(ScriptLocation(scriptsHistory.current.value));
4044
};
4145
scriptsHistory.current.addListener(_scriptHistoryListener);
46+
addAutoDisposeListener(currentScriptRef, () {
47+
if (!programExplorerController.initialized.value) {
48+
programExplorerController.initialize();
49+
}
50+
if (currentScriptRef.value != null) {
51+
programExplorerController.selectScriptNode(currentScriptRef.value);
52+
}
53+
});
4254

4355
if (_service != null) {
4456
initialize();
@@ -119,6 +131,10 @@ class DebuggerController extends DisposableController
119131
Map<LibraryRef, Future<Set<String>>>
120132
libraryMemberAndImportsAutocompleteCache = {};
121133

134+
ProgramExplorerController get programExplorerController =>
135+
_programExplorerController;
136+
ProgramExplorerController _programExplorerController;
137+
122138
final ScriptCache _scriptCache = ScriptCache();
123139

124140
final ScriptsHistory scriptsHistory = ScriptsHistory();
@@ -160,8 +176,11 @@ class DebuggerController extends DisposableController
160176
final _showFileOpener = ValueNotifier<bool>(false);
161177

162178
/// Jump to the given ScriptRef and optional SourcePosition.
163-
void showScriptLocation(ScriptLocation scriptLocation) {
164-
_showScriptLocation(scriptLocation);
179+
void showScriptLocation(
180+
ScriptLocation scriptLocation, {
181+
bool centerLocation = true,
182+
}) {
183+
_showScriptLocation(scriptLocation, centerLocation: centerLocation);
165184

166185
// Update the scripts history (and make sure we don't react to the
167186
// subsequent event).
@@ -172,7 +191,11 @@ class DebuggerController extends DisposableController
172191

173192
/// Show the given script location (without updating the script navigation
174193
/// history).
175-
void _showScriptLocation(ScriptLocation scriptLocation) {
194+
void _showScriptLocation(
195+
ScriptLocation scriptLocation, {
196+
bool centerLocation = true,
197+
}) {
198+
_shouldCenterScrollLocation = centerLocation;
176199
_currentScriptRef.value = scriptLocation?.scriptRef;
177200

178201
_parseCurrentScript();
@@ -281,6 +304,9 @@ class DebuggerController extends DisposableController
281304
/// Return the sorted list of ScriptRefs active in the current isolate.
282305
ValueListenable<List<ScriptRef>> get sortedScripts => _sortedScripts;
283306

307+
bool get shouldCenterScrollLocation => _shouldCenterScrollLocation;
308+
bool _shouldCenterScrollLocation = true;
309+
284310
final _breakpoints = ValueNotifier<List<Breakpoint>>([]);
285311

286312
ValueListenable<List<Breakpoint>> get breakpoints => _breakpoints;
@@ -303,7 +329,7 @@ class DebuggerController extends DisposableController
303329

304330
final _librariesVisible = ValueNotifier(false);
305331

306-
ValueListenable<bool> get librariesVisible => _librariesVisible;
332+
ValueListenable<bool> get fileExplorerVisible => _librariesVisible;
307333

308334
/// Make the 'Libraries' view on the right-hand side of the screen visible or
309335
/// hidden.
@@ -607,7 +633,6 @@ class DebuggerController extends DisposableController
607633

608634
void _handleIsolateEvent(Event event) {
609635
if (event.isolate.id != isolateRef?.id) return;
610-
611636
switch (event.kind) {
612637
case EventKind.kIsolateReload:
613638
_updateAfterIsolateReload(event);
@@ -862,25 +887,12 @@ class DebuggerController extends DisposableController
862887
_populateScriptAndShowLocation(mainScriptRef);
863888
}
864889

865-
SourcePosition calculatePosition(Script script, int tokenPos) {
866-
final List<List<int>> table = script.tokenPosTable;
867-
if (table == null) {
868-
return null;
869-
}
870-
871-
return SourcePosition(
872-
line: script.getLineNumberFromTokenPos(tokenPos),
873-
column: script.getColumnNumberFromTokenPos(tokenPos),
874-
tokenPos: tokenPos,
875-
);
876-
}
877-
878890
Future<BreakpointAndSourcePosition> _createBreakpointWithLocation(
879891
Breakpoint breakpoint) async {
880892
if (breakpoint.resolved) {
881893
final bp = BreakpointAndSourcePosition.create(breakpoint);
882894
return getScript(bp.scriptRef).then((Script script) {
883-
final pos = calculatePosition(script, bp.tokenPos);
895+
final pos = SourcePosition.calculatePosition(script, bp.tokenPos);
884896
return BreakpointAndSourcePosition.create(breakpoint, pos);
885897
});
886898
} else {
@@ -897,7 +909,8 @@ class DebuggerController extends DisposableController
897909
}
898910

899911
final script = await getScript(location.script);
900-
final position = calculatePosition(script, location.tokenPos);
912+
final position =
913+
SourcePosition.calculatePosition(script, location.tokenPos);
901914
return StackFrameAndSourcePosition(frame, position: position);
902915
}
903916

@@ -1002,7 +1015,7 @@ class DebuggerController extends DisposableController
10021015
for (SourceReportRange range in report.ranges) {
10031016
if (range.possibleBreakpoints != null) {
10041017
for (int tokenPos in range.possibleBreakpoints) {
1005-
positions.add(calculatePosition(script, tokenPos));
1018+
positions.add(SourcePosition.calculatePosition(script, tokenPos));
10061019
}
10071020
}
10081021
}

packages/devtools_app/lib/src/debugger/debugger_model.dart

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,27 @@ class ScriptLocation {
9393
String toString() => '${scriptRef.uri} $location';
9494
}
9595

96-
/// A line, column, and an optional tokenPos.
9796
class SourcePosition {
98-
SourcePosition({@required this.line, @required this.column, this.tokenPos});
97+
const SourcePosition({
98+
@required this.line,
99+
@required this.column,
100+
this.file,
101+
this.tokenPos,
102+
});
103+
104+
factory SourcePosition.calculatePosition(Script script, int tokenPos) {
105+
if (script.tokenPosTable == null) {
106+
return null;
107+
}
108+
109+
return SourcePosition(
110+
line: script.getLineNumberFromTokenPos(tokenPos),
111+
column: script.getColumnNumberFromTokenPos(tokenPos),
112+
tokenPos: tokenPos,
113+
);
114+
}
99115

116+
final String file;
100117
final int line;
101118
final int column;
102119
final int tokenPos;

0 commit comments

Comments
 (0)