Skip to content

Commit fc96652

Browse files
authored
Debugger: soft wrap console input lines (#8855)
1 parent eb03e90 commit fc96652

File tree

4 files changed

+128
-92
lines changed

4 files changed

+128
-92
lines changed

packages/devtools_app/lib/src/shared/console/console.dart

Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -162,46 +162,55 @@ class _ConsoleOutputState extends State<_ConsoleOutput>
162162
child: Padding(
163163
padding: const EdgeInsets.symmetric(horizontal: denseSpacing),
164164
child: SelectionArea(
165-
child: ListView.separated(
166-
padding: const EdgeInsets.all(denseSpacing),
167-
itemCount: _currentLines.length + (widget.footer != null ? 1 : 0),
168-
controller: _scroll,
169-
// Scroll physics to try to keep content within view and avoid bouncing.
170-
physics: const ClampingScrollPhysics(
171-
parent: RangeMaintainingScrollPhysics(),
172-
),
173-
separatorBuilder: (_, _) {
174-
return const PaddedDivider.noPadding();
175-
},
176-
itemBuilder: (context, index) {
177-
if (index == _currentLines.length && widget.footer != null) {
178-
return widget.footer!;
179-
}
180-
final line = _currentLines[index];
181-
if (line is TextConsoleLine) {
182-
return Text.rich(
183-
TextSpan(
184-
// TODO(jacobr): consider caching the processed ansi terminal
185-
// codes.
186-
children: textSpansFromAnsi(
187-
line.text,
188-
theme.regularTextStyle,
189-
),
165+
child: Column(
166+
mainAxisSize: MainAxisSize.min,
167+
children: [
168+
Expanded(
169+
child: ListView.separated(
170+
padding: const EdgeInsets.all(denseSpacing),
171+
itemCount: _currentLines.length,
172+
controller: _scroll,
173+
// Scroll physics to try to keep content within view and avoid bouncing.
174+
physics: const ClampingScrollPhysics(
175+
parent: RangeMaintainingScrollPhysics(),
190176
),
191-
);
192-
} else if (line is VariableConsoleLine) {
193-
return ExpandableVariable(
194-
variable: line.variable,
195-
isSelectable: false,
196-
);
197-
} else {
198-
assert(
199-
false,
200-
'ConsoleLine of unsupported type ${line.runtimeType} encountered',
201-
);
202-
return const SizedBox();
203-
}
204-
},
177+
separatorBuilder: (_, _) {
178+
return const PaddedDivider.noPadding();
179+
},
180+
itemBuilder: (context, index) {
181+
final line = _currentLines[index];
182+
if (line is TextConsoleLine) {
183+
return Text.rich(
184+
TextSpan(
185+
// TODO(jacobr): consider caching the processed ansi terminal
186+
// codes.
187+
children: textSpansFromAnsi(
188+
line.text,
189+
theme.regularTextStyle,
190+
),
191+
),
192+
);
193+
} else if (line is VariableConsoleLine) {
194+
return ExpandableVariable(
195+
variable: line.variable,
196+
isSelectable: false,
197+
);
198+
} else {
199+
assert(
200+
false,
201+
'ConsoleLine of unsupported type ${line.runtimeType} encountered',
202+
);
203+
return const SizedBox();
204+
}
205+
},
206+
),
207+
),
208+
// consider constraining a max height.
209+
Padding(
210+
padding: const EdgeInsets.only(top: denseSpacing),
211+
child: widget.footer!,
212+
),
213+
],
205214
),
206215
),
207216
),

packages/devtools_app/lib/src/shared/console/widgets/evaluate.dart

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ class ExpressionEvalField extends StatefulWidget {
3535

3636
final AutoCompleteResultsFunction getAutoCompleteResults;
3737

38-
static const _evalFieldHeight = 32.0;
39-
4038
@override
4139
ExpressionEvalFieldState createState() => ExpressionEvalFieldState();
4240
}
@@ -218,46 +216,44 @@ class ExpressionEvalFieldState extends State<ExpressionEvalField>
218216

219217
return KeyEventResult.ignored;
220218
},
221-
child: SizedBox(
222-
height: ExpressionEvalField._evalFieldHeight,
223-
child: AutoCompleteSearchField(
224-
controller: _autoCompleteController,
225-
searchFieldEnabled: true,
226-
shouldRequestFocus: false,
227-
clearFieldOnEscapeWhenOverlayHidden: true,
228-
onSelection: _onSelection,
229-
decoration: InputDecoration(
230-
contentPadding: const EdgeInsets.all(denseSpacing),
231-
border: const OutlineInputBorder(),
232-
focusedBorder: const OutlineInputBorder(
233-
borderSide: BorderSide.none,
234-
),
235-
enabledBorder: const OutlineInputBorder(
236-
borderSide: BorderSide.none,
237-
),
238-
labelText: 'Eval. Enter "?" for help.',
239-
labelStyle: Theme.of(context).subtleTextStyle,
219+
child: AutoCompleteSearchField(
220+
controller: _autoCompleteController,
221+
searchFieldEnabled: true,
222+
shouldRequestFocus: false,
223+
clearFieldOnEscapeWhenOverlayHidden: true,
224+
onSelection: _onSelection,
225+
decoration: InputDecoration(
226+
contentPadding: const EdgeInsets.all(denseSpacing),
227+
border: const OutlineInputBorder(),
228+
focusedBorder: const OutlineInputBorder(
229+
borderSide: BorderSide.none,
240230
),
241-
overlayXPositionBuilder: (
242-
String inputValue,
243-
TextStyle? inputStyle,
244-
) {
245-
// X-coordinate is equivalent to the width of the input text
246-
// up to the last "." or the insertion point (cursor):
247-
final indexOfDot = inputValue.lastIndexOf('.');
248-
final textSegment =
249-
indexOfDot != -1
250-
? inputValue.substring(0, indexOfDot + 1)
251-
: inputValue;
252-
return calculateTextSpanWidth(
253-
TextSpan(text: textSegment, style: inputStyle),
254-
);
255-
},
256-
// Disable ligatures, so the suggestions of the auto complete work correcly.
257-
style: Theme.of(context).fixedFontStyle.copyWith(
258-
fontFeatures: [const FontFeature.disable('liga')],
231+
enabledBorder: const OutlineInputBorder(
232+
borderSide: BorderSide.none,
259233
),
234+
labelText: 'Eval. Enter "?" for help.',
235+
labelStyle: Theme.of(context).subtleTextStyle,
236+
),
237+
overlayXPositionBuilder: (
238+
String inputValue,
239+
TextStyle? inputStyle,
240+
) {
241+
// X-coordinate is equivalent to the width of the input text
242+
// up to the last "." or the insertion point (cursor):
243+
final indexOfDot = inputValue.lastIndexOf('.');
244+
final textSegment =
245+
indexOfDot != -1
246+
? inputValue.substring(0, indexOfDot + 1)
247+
: inputValue;
248+
return calculateTextSpanWidth(
249+
TextSpan(text: textSegment, style: inputStyle),
250+
);
251+
},
252+
// Disable ligatures, so the suggestions of the auto complete work correcly.
253+
style: Theme.of(context).fixedFontStyle.copyWith(
254+
fontFeatures: [const FontFeature.disable('liga')],
260255
),
256+
maxLines: null,
261257
),
262258
),
263259
),

packages/devtools_app/lib/src/shared/ui/search.dart

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -893,8 +893,11 @@ class SearchField<T extends SearchControllerMixin> extends StatefulWidget {
893893
this.onClose,
894894
this.searchFieldWidth = defaultSearchFieldWidth,
895895
double? searchFieldHeight,
896+
int? maxLines = 1,
896897
super.key,
897-
}) : searchFieldHeight = searchFieldHeight ?? defaultTextFieldHeight;
898+
}) : assert(maxLines != 0, "'maxLines' must not be 0"),
899+
searchFieldHeight = searchFieldHeight ?? defaultTextFieldHeight,
900+
_maxLines = maxLines;
898901

899902
final T searchController;
900903

@@ -917,6 +920,11 @@ class SearchField<T extends SearchControllerMixin> extends StatefulWidget {
917920
/// triggered.
918921
final VoidCallback? onClose;
919922

923+
/// The maximum number of lines, by default one.
924+
///
925+
/// Can be set to null to remove the restriction; must not be zero.
926+
final int? _maxLines;
927+
920928
@override
921929
State<SearchField> createState() => _SearchFieldState();
922930
}
@@ -928,18 +936,23 @@ class _SearchFieldState extends State<SearchField>
928936

929937
@override
930938
Widget build(BuildContext context) {
931-
return SizedBox(
932-
width: widget.searchFieldWidth,
933-
height: widget.searchFieldHeight,
934-
child: StatelessSearchField(
935-
controller: searchController,
936-
searchFieldEnabled: widget.searchFieldEnabled,
937-
shouldRequestFocus: widget.shouldRequestFocus,
938-
supportsNavigation: widget.supportsNavigation,
939-
onClose: widget.onClose,
940-
searchFieldHeight: widget.searchFieldHeight,
941-
),
939+
final searchField = StatelessSearchField(
940+
controller: searchController,
941+
searchFieldEnabled: widget.searchFieldEnabled,
942+
shouldRequestFocus: widget.shouldRequestFocus,
943+
supportsNavigation: widget.supportsNavigation,
944+
onClose: widget.onClose,
945+
searchFieldHeight: widget.searchFieldHeight,
946+
maxLines: widget._maxLines,
942947
);
948+
949+
return widget._maxLines != 1
950+
? searchField
951+
: SizedBox(
952+
width: widget.searchFieldWidth,
953+
height: widget.searchFieldHeight,
954+
child: searchField,
955+
);
943956
}
944957
}
945958

@@ -970,7 +983,9 @@ class StatelessSearchField<T extends SearchableDataMixin>
970983
this.suffix,
971984
this.style,
972985
this.searchFieldHeight,
973-
});
986+
int? maxLines = 1,
987+
}) : assert(maxLines != 0, "'maxLines' must not be 0"),
988+
_maxLines = maxLines;
974989

975990
final SearchControllerMixin<T> controller;
976991

@@ -1014,6 +1029,11 @@ class StatelessSearchField<T extends SearchableDataMixin>
10141029

10151030
final double? searchFieldHeight;
10161031

1032+
/// The maximum number of lines, by default one.
1033+
///
1034+
/// Can be set to null to remove the restriction; must not be zero.
1035+
final int? _maxLines;
1036+
10171037
@override
10181038
Widget build(BuildContext context) {
10191039
final theme = Theme.of(context);
@@ -1032,6 +1052,7 @@ class StatelessSearchField<T extends SearchableDataMixin>
10321052
focusNode: controller.searchFieldFocusNode,
10331053
controller: controller.searchTextFieldController,
10341054
style: textStyle,
1055+
maxLines: _maxLines,
10351056
onChanged: onChanged,
10361057
onEditingComplete: () {
10371058
controller.searchFieldFocusNode?.requestFocus();
@@ -1116,7 +1137,9 @@ class AutoCompleteSearchField extends StatefulWidget {
11161137
this.onFocusLost,
11171138
this.style,
11181139
this.keyEventsToIgnore = const {},
1119-
});
1140+
int? maxLines = 1,
1141+
}) : assert(maxLines != 0, "'maxLines' must not be 0"),
1142+
_maxLines = maxLines;
11201143

11211144
final AutoCompleteSearchControllerMixin controller;
11221145

@@ -1164,6 +1187,11 @@ class AutoCompleteSearchField extends StatefulWidget {
11641187
/// [controller.autocompleteFocusNode] has lost focus.
11651188
final VoidCallback? onFocusLost;
11661189

1190+
/// The maximum number of lines, by default one.
1191+
///
1192+
/// Can be set to null to remove the restriction; must not be zero.
1193+
final int? _maxLines;
1194+
11671195
@override
11681196
State<AutoCompleteSearchField> createState() =>
11691197
_AutoCompleteSearchFieldState();
@@ -1214,6 +1242,7 @@ class _AutoCompleteSearchFieldState extends State<AutoCompleteSearchField>
12141242
},
12151243
onClose: widget.onClose,
12161244
style: widget.style,
1245+
maxLines: widget._maxLines,
12171246
),
12181247
),
12191248
);

packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ TODO: Remove this section if there are not any general updates.
4040
* Updated syntax highlighting with support for digit separators,
4141
and improved comment and string interpolation handling. -
4242
[#8861](https://github.com/flutter/devtools/pull/8861)
43+
* Added soft line wrapping in the debugger console.
44+
[#8855](https://github.com/flutter/devtools/pull/8855).
4345

4446
## Network profiler updates
4547

0 commit comments

Comments
 (0)