-
Notifications
You must be signed in to change notification settings - Fork 344
add a debugger UI #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
/* found in the LICENSE file. */ | ||
|
||
.debugger-menu { | ||
width: 280px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: does this need to be hard coded to 280px?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, currently. It's the width of the left sections in the debugger UI. Our options are hard-coding a width, letting it float based on a percentage of the screen width, or building a user-positionable splitter control.
Of the 3 I prefer a hard-coded width. It ensures that the contents are still readable in more narrow windows, and doesn't get too wide for wider windows.
When designing this ui, I think we can assume a browser width of ~1300px (https://www.websitedimensions.com/).
Long term we'll likely want some user positionable controls, but likely not an immediate concern.
lib/debugger/debugger.dart
Outdated
final PButton resumeButton = new PButton(null) | ||
..primary() | ||
..small() | ||
..element.style.minWidth = '90px' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason this style shouldn't be in the css?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I'll move this css.
stepIn.click(() => debuggerState.stepIn()); | ||
stepOut.click(() => debuggerState.stepOut()); | ||
|
||
final Map<String, dynamic> options = <String, dynamic>{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: consider omitting they types on the RHS unless they are adding value. we aren't forcing the flutter types everywhere style for this code anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 will do; I started this PR before the style change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I think it's nicer to omit types on the left. If the generic arguments are needed (they could be skipped altogether here) put them on the literal only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if we had a dart quick fix that stripped out all types that are inferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll want to hew closely to the flutter/flutter style in this repo.
lib/debugger/debugger.dart
Outdated
final CodeMirror codeMirror = | ||
new CodeMirror.fromElement(sourceArea.element, options: options); | ||
codeMirror.setReadOnly(true); | ||
// ignore: always_specify_types |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this ignore:
is not needed anymore as we switched the lint.
lib/debugger/debugger.dart
Outdated
final Frame frame = frames.first; | ||
|
||
final Frame newFrame = new Frame(); | ||
newFrame.type = frame.type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: cascades are nice for this case. also consider adding clone constructor to Frame.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 re: cascades
The ctor is defined in a package: we're importing, but I'll open an issue to improve the options for constructors for these classes.
lib/debugger/debugger.dart
Outdated
deviceStatus.element.text = ''; | ||
|
||
scriptsView.clearScripts(); | ||
_scriptCountDiv.text = '0'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see setting the state of _scriptCountDiv a lot of places. Consider adding a helper that syncs its value to the current script count.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to listening to script changes, and having the listener update the count
lib/debugger/debugger.dart
Outdated
scriptPrefix = | ||
scriptPrefix.substring(0, scriptPrefix.lastIndexOf('/') + 1); | ||
} | ||
} else if (scriptPrefix.contains('/example/')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this hard coding of /example/ needed? Seems a bit surprising. Generally it would be helpful to move this logic for determining the root directory to a shared util as I also need it for the inspector to clarify what directories are considered the local project for highlighting only user created widgets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is tough as we don't have a fool-proof way to determine what the root of a project is w/ just the info from the service protocol.
For flutter apps, they generally (always?) run as package:my_app urls (the first case above), so I think we'll be able to determine app code vs other code fairly reliably.
For cli apps, they generally run as file: urls, and have a bit more ambiguity about what is app code. In the logic above, I'm trying to walkup the url a bit. and use package layout conventions to identify the app directory.
I'll trim the logic for example/
above as its less common than lib/ or bin/.
lib/debugger/debugger.dart
Outdated
} | ||
} | ||
|
||
class Pos { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Pos --> Position?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That conflicts with an import, but I can find a better name than Pos
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SourcePosition
lib/debugger/debugger.dart
Outdated
_items = new SelectableList<Breakpoint>() | ||
..flex() | ||
..clazz('menu-item-bottom-border') | ||
..element.style.overflowY = 'scroll'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: specify overflowY with CSS file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/debugger/debugger.dart
Outdated
_items.setRenderer((Breakpoint breakpoint) { | ||
final dynamic location = breakpoint.location; | ||
|
||
final CoreElement element = li(text: '', c: 'list-item'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can text parameter be omitted as it is empty?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup, done
|
||
Stream<Breakpoint> get onDoubleClick => _items.onDoubleClick; | ||
|
||
final CoreElement _breakpointsCountDiv; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider moving all fields up closer to the top of the class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/debugger/debugger.dart
Outdated
String uri1 = ref1.uri; | ||
String uri2 = ref2.uri; | ||
|
||
if (uri1.startsWith('dart:_')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a helper method to cleanup dart:_ uris
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/debugger/debugger.dart
Outdated
_items = new SelectableList<Frame>() | ||
..flex() | ||
..clazz('menu-item-bottom-border') | ||
..element.style.overflowY = 'scroll'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like all the menus allow scroll. maybe add that to the css file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/debugger/debugger.dart
Outdated
} | ||
|
||
class ScriptAndPos { | ||
ScriptAndPos(this.uri, {this.position}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add @required to position argument assuming it is required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
ScriptRef getRef(dynamic location) { | ||
if (location is SourceLocation) { | ||
return location.script; | ||
} else if (location is UnresolvedSourceLocation) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit :
no need for either else
as each if clause ends in a return.
|
||
int getPos(dynamic location) { | ||
if (location is SourceLocation) { | ||
return location.tokenPos ?? 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: make location.tokenPos be made non-nullable instead of having a check here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We get this info directly from the service protocol - in some cases tokenPos can be null.
lib/debugger/debugger.dart
Outdated
ConsoleArea() { | ||
final Map<String, dynamic> options = <String, dynamic>{ | ||
'mode': 'text/plain', | ||
//'lineNumbers': true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove commented line or add it as an option to ConsoleArea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
lib/debugger/debugger.dart
Outdated
_editor.scrollIntoView(lastLineIndex, lastLine.length); | ||
} | ||
|
||
// for testing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! Lgtm with comments.
example/test.dart
Outdated
description = 'items: $count'; | ||
|
||
final List<int> numbers = new List.generate(10, (index) => index * index); | ||
// ignore: unused_local_variable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: unnecessary ignore
lib/debugger/debugger.dart
Outdated
|
||
if (value is Sentinel) { | ||
return value.valueAsString; | ||
} else if (value is TypeArgumentsRef) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: else's not needed since every if statement returns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
if (result is Instance) { | ||
final Instance obj = result; | ||
return obj.valueAsString; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: else not needed since the if statement returns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the parallelism here since there are just two branches.
lib/debugger/debugger.dart
Outdated
Future<Success> stepOut() => | ||
service.resume(isolateRef.id, step: StepOption.kOut); | ||
|
||
// Used for testing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use visibleForTesting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
lib/debugger/debugger.dart
Outdated
return service.addBreakpoint(isolateRef.id, scriptId, line); | ||
} | ||
|
||
// Used for testing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: visibleForTesting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
Thanks for the comments! I'm going to get the tests working, work through the comments, and then ping this PR. |
The tests look like they have race conditions when run on travis unfortunately.
Perhaps just adding a wait or a condition at the right place? |
stepIn.click(() => debuggerState.stepIn()); | ||
stepOut.click(() => debuggerState.stepOut()); | ||
|
||
final Map<String, dynamic> options = <String, dynamic>{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I think it's nicer to omit types on the left. If the generic arguments are needed (they could be skipped altogether here) put them on the literal only.
lib/debugger/debugger.dart
Outdated
} | ||
|
||
class DebuggerState { | ||
DebuggerState(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] omit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this would likely make a good lint...
|
||
List<Breakpoint> get breakpoints => _breakpoints.value; | ||
|
||
void setVmService(VmService service) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how would the user of this API know to use this instead of the setter directly?
How bout making the field private with a public set and get to attach the extra behavior here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an internal class, so not intended to be driven from outside the library.
I like the explicit setFoo
here, as this does do a bit of work. +1 to making the field private; this method is the only way that people outside of the class should manipulate the value.
commonScriptPrefix = scriptPrefix; | ||
} | ||
|
||
String getShortScriptName(String uri) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nit] How about shortScriptName
https://www.dartlang.org/guides/language/effective-dart/design#avoid-starting-a-method-name-with-get
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure that applies, as this method takes a param?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the guide: "Even if the member does need to be a method because it takes arguments or otherwise isn’t a good fit for a getter, you should still avoid get
."
test/example/test.dart
Outdated
|
||
void main() { | ||
const Duration delay = Duration(seconds: 4); | ||
Function work; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is only assigned once, should it be written as a function instead of assigning a lambda?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use this script for manual testing of the debugger, so it's got some oddities. I can look at using a class instead of top-level functions in order to get the same behavior I need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function does not need to be top level, it can be defined within main
. From what I can see you can use exactly what you have if you:
- Remove variable declaration
- Remove the
=
at the definition point - Remove the semicolon following the last
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, when you were testing did you specifically need a variable that held a closure? If that's the case maybe add a comment?
test/example/test.dart
Outdated
@@ -0,0 +1,83 @@ | |||
// Copyright 2018 The Chromium Authors. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: for all new files update the year to 2019.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made a pass through the comments, and w/ the updated build from a separate PR, believe I have the tests running on the bots.
/* found in the LICENSE file. */ | ||
|
||
.debugger-menu { | ||
width: 280px; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, currently. It's the width of the left sections in the debugger UI. Our options are hard-coding a width, letting it float based on a percentage of the screen width, or building a user-positionable splitter control.
Of the 3 I prefer a hard-coded width. It ensures that the contents are still readable in more narrow windows, and doesn't get too wide for wider windows.
When designing this ui, I think we can assume a browser width of ~1300px (https://www.websitedimensions.com/).
Long term we'll likely want some user positionable controls, but likely not an immediate concern.
lib/debugger/debugger.dart
Outdated
final PButton resumeButton = new PButton(null) | ||
..primary() | ||
..small() | ||
..element.style.minWidth = '90px' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I'll move this css.
stepIn.click(() => debuggerState.stepIn()); | ||
stepOut.click(() => debuggerState.stepOut()); | ||
|
||
final Map<String, dynamic> options = <String, dynamic>{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll want to hew closely to the flutter/flutter style in this repo.
lib/debugger/debugger.dart
Outdated
final Frame frame = frames.first; | ||
|
||
final Frame newFrame = new Frame(); | ||
newFrame.type = frame.type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 re: cascades
The ctor is defined in a package: we're importing, but I'll open an issue to improve the options for constructors for these classes.
lib/debugger/debugger.dart
Outdated
|
||
if (value is Sentinel) { | ||
return value.valueAsString; | ||
} else if (value is TypeArgumentsRef) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
|
||
int getPos(dynamic location) { | ||
if (location is SourceLocation) { | ||
return location.tokenPos ?? 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We get this info directly from the service protocol - in some cases tokenPos can be null.
lib/debugger/debugger.dart
Outdated
ConsoleArea() { | ||
final Map<String, dynamic> options = <String, dynamic>{ | ||
'mode': 'text/plain', | ||
//'lineNumbers': true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed
lib/debugger/debugger.dart
Outdated
_editor.scrollIntoView(lastLineIndex, lastLine.length); | ||
} | ||
|
||
// for testing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
test/example/test.dart
Outdated
@@ -0,0 +1,83 @@ | |||
// Copyright 2018 The Chromium Authors. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do
test/example/test.dart
Outdated
|
||
void main() { | ||
const Duration delay = Duration(seconds: 4); | ||
Function work; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use this script for manual testing of the debugger, so it's got some oddities. I can look at using a class instead of top-level functions in order to get the same behavior I need.
@@ -4,13 +4,15 @@ version: 0.0.1 | |||
homepage: https://github.com/flutter/devtools | |||
|
|||
environment: | |||
sdk: '>=2.0.0-dev <3.0.0' | |||
sdk: '>=2.1.0 <3.0.0' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW - this constraint prevents the SDK included in Flutter v1.0 from being able to activate this package (it shipped with 2.1.0-dev.9.4.flutter-f9ebf21297
and a -dev
suffix makes that "less than" 2.1.0).
I suspect we're not concerned but since I noticed it when adding DC integration tests and it's a little counter-intuitive (we consider Flutter v1.0 shipped with Dart 2.1, but semver doesn't see it that way!) I thought it worth noting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're effectively just using this constraint in the pub global activated server app, I think we can change the lower bound to Flutter's.
This adds a debugging UI to devtools. It:
This PR is ready for review. We will want to (perhaps out of band of the PR), decide whether we do want a debugger UI in devtools, whether we should have it enabled now or only accessible through a compile time flag or similar. Happy to take feedback about how this fits into the product here or offline.
@jacob314 @kenzieschmoll