Skip to content

Commit a215380

Browse files
committed
Polish tooltip
1 parent a149e65 commit a215380

4 files changed

Lines changed: 141 additions & 26 deletions

File tree

packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ class PropertyEditorController extends DisposableController
4646
_editableWidgetData;
4747
final _editableWidgetData = ValueNotifier<EditableWidgetData?>(null);
4848

49+
List<EditableProperty> get allProperties =>
50+
_editableWidgetData.value?.properties ?? [];
51+
String? get widgetName => _editableWidgetData.value?.name;
52+
String? get widgetDocumentation => _editableWidgetData.value?.documentation;
53+
String? get fileUri => _editableWidgetData.value?.fileUri;
54+
4955
ValueListenable<bool> get shouldReconnect => _shouldReconnect;
5056
final _shouldReconnect = ValueNotifier<bool>(false);
5157

packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_view.dart

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,25 +50,25 @@ class PropertyEditorView extends StatelessWidget {
5050
);
5151
}
5252

53-
final (:properties, :name, :documentation, :fileUri) =
54-
editableWidgetData;
53+
final fileUri = controller.fileUri;
5554
if (fileUri != null && !fileUri.endsWith('.dart')) {
5655
return const CenteredMessage(
5756
message: 'No Dart code found at the current cursor location.',
5857
);
5958
}
6059

6160
final filteredProperties = values.fourth as List<EditableProperty>;
61+
final widgetName = controller.widgetName;
6262
return Column(
6363
crossAxisAlignment: CrossAxisAlignment.start,
6464
children: [
65-
if (name != null)
65+
if (widgetName != null)
6666
_WidgetNameAndDocumentation(
67-
name: name,
68-
documentation: documentation,
67+
name: widgetName,
68+
documentation: controller.widgetDocumentation,
6969
),
70-
properties.isEmpty
71-
? _NoEditablePropertiesMessage(name: name)
70+
controller.allProperties.isEmpty
71+
? _NoEditablePropertiesMessage(name: controller.widgetName)
7272
: _PropertiesList(
7373
controller: controller,
7474
editableProperties: filteredProperties,
@@ -122,6 +122,7 @@ class _PropertiesListState extends State<_PropertiesList> {
122122
_EditablePropertyItem(
123123
property: property,
124124
editProperty: widget.controller.editArgument,
125+
widgetDocumentation: widget.controller.widgetDocumentation,
125126
),
126127
].joinWith(const PaddedDivider.noPadding()),
127128
);
@@ -132,10 +133,12 @@ class _EditablePropertyItem extends StatelessWidget {
132133
const _EditablePropertyItem({
133134
required this.property,
134135
required this.editProperty,
136+
required this.widgetDocumentation,
135137
});
136138

137139
final EditableProperty property;
138140
final EditArgumentFunction editProperty;
141+
final String? widgetDocumentation;
139142

140143
@override
141144
Widget build(BuildContext context) {
@@ -153,7 +156,10 @@ class _EditablePropertyItem extends StatelessWidget {
153156
bottom: largeSpacing,
154157
right: densePadding,
155158
),
156-
child: _InfoTooltip(property: property),
159+
child: _InfoTooltip(
160+
property: property,
161+
widgetDocumentation: widgetDocumentation,
162+
),
157163
),
158164
Expanded(
159165
child: _PropertyInput(
@@ -258,17 +264,57 @@ class _PropertyLabels extends StatelessWidget {
258264
}
259265

260266
class _InfoTooltip extends StatelessWidget {
261-
const _InfoTooltip({required this.property});
267+
const _InfoTooltip({
268+
required this.property,
269+
required this.widgetDocumentation,
270+
});
262271

263272
final EditableProperty property;
273+
final String? widgetDocumentation;
264274

265275
@override
266276
Widget build(BuildContext context) {
267277
return DevToolsTooltip(
268-
message: property.documentation,
278+
richMessage: _infoMessage(context),
269279
child: Icon(size: defaultIconSize, Icons.info_outline),
270280
);
271281
}
282+
283+
TextSpan _infoMessage(BuildContext context) {
284+
final theme = Theme.of(context);
285+
final textColor = theme.colorScheme.tooltipTextColor;
286+
final regularFontStyle = theme.regularTextStyle.copyWith(color: textColor);
287+
final boldFontStyle = theme.boldTextStyle.copyWith(color: textColor);
288+
final fixedFontStyle = theme.fixedFontStyle.copyWith(color: textColor);
289+
290+
final spans =
291+
property.hasDefault
292+
? [
293+
TextSpan(text: 'Default value: ', style: boldFontStyle),
294+
TextSpan(
295+
text: property.defaultValue.toString(),
296+
style: fixedFontStyle,
297+
),
298+
]
299+
: [
300+
TextSpan(text: 'Default value:\n', style: boldFontStyle),
301+
TextSpan(text: property.name, style: fixedFontStyle),
302+
TextSpan(text: ' has no default value.', style: regularFontStyle),
303+
];
304+
305+
final documentation = property.documentation;
306+
if (documentation != null && documentation != widgetDocumentation) {
307+
spans.addAll([
308+
TextSpan(text: '\n\nDocumentation:\n', style: boldFontStyle),
309+
...DartDocConverter(documentation).toTextSpans(
310+
regularFontStyle: regularFontStyle,
311+
fixedFontStyle: fixedFontStyle,
312+
),
313+
]);
314+
}
315+
316+
return TextSpan(children: spans);
317+
}
272318
}
273319

274320
class _PropertyInput extends StatelessWidget {

packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/utils/utils.dart

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,18 @@ class DartDocConverter {
2626
return Text.rich(TextSpan(children: children));
2727
}
2828

29-
@visibleForTesting
3029
List<TextSpan> toTextSpans({
3130
required TextStyle regularFontStyle,
3231
required TextStyle fixedFontStyle,
3332
}) {
33+
final text = _removeTemplateIndicators(dartDocText);
34+
3435
final children = <TextSpan>[];
3536
int currentIndex = 0;
3637

37-
while (currentIndex < dartDocText.length) {
38-
final openBracketIndex = dartDocText.indexOf('[', currentIndex);
39-
final openBacktickIndex = dartDocText.indexOf('`', currentIndex);
38+
while (currentIndex < text.length) {
39+
final openBracketIndex = text.indexOf('[', currentIndex);
40+
final openBacktickIndex = text.indexOf('`', currentIndex);
4041

4142
int nextSpecialCharIndex = -1;
4243
bool isLink = false;
@@ -53,46 +54,85 @@ class DartDocConverter {
5354
if (nextSpecialCharIndex == -1) {
5455
// No more special characters, add the remaining text.
5556
children.add(
56-
TextSpan(
57-
text: dartDocText.substring(currentIndex),
58-
style: regularFontStyle,
59-
),
57+
TextSpan(text: text.substring(currentIndex), style: regularFontStyle),
6058
);
6159
break;
6260
}
6361

6462
// Add text before the special character.
6563
children.add(
6664
TextSpan(
67-
text: dartDocText.substring(currentIndex, nextSpecialCharIndex),
65+
text: text.substring(currentIndex, nextSpecialCharIndex),
6866
style: regularFontStyle,
6967
),
7068
);
7169

72-
final closeIndex = dartDocText.indexOf(
70+
final closeIndex = text.indexOf(
7371
isLink ? ']' : '`',
7472
isLink ? nextSpecialCharIndex : nextSpecialCharIndex + 1,
7573
);
7674
if (closeIndex == -1) {
7775
// Treat unmatched brackets/backticks as regular text.
7876
children.add(
7977
TextSpan(
80-
text: dartDocText.substring(nextSpecialCharIndex),
78+
text: text.substring(nextSpecialCharIndex),
8179
style: regularFontStyle,
8280
),
8381
);
84-
currentIndex = dartDocText.length; // Effectively break the loop.
82+
currentIndex = text.length; // Effectively break the loop.
8583
} else {
86-
final content = dartDocText.substring(
87-
nextSpecialCharIndex + 1,
88-
closeIndex,
89-
);
84+
final content = text.substring(nextSpecialCharIndex + 1, closeIndex);
9085
children.add(TextSpan(text: content, style: fixedFontStyle));
9186
currentIndex = closeIndex + 1;
9287
}
9388
}
9489
return children;
9590
}
91+
92+
/// Removes @template and @endtemplate indicators from the [input].
93+
String _removeTemplateIndicators(String input) {
94+
const templateStart = '{@template';
95+
const templateEnd = '{@endtemplate';
96+
const closingCurlyBrace = '}';
97+
const newLine = '\n';
98+
String result = '';
99+
int currentIndex = 0;
100+
101+
while (currentIndex < input.length) {
102+
final startTemplateIndex = input.indexOf(templateStart, currentIndex);
103+
final endTemplateIndex = input.indexOf(templateEnd, currentIndex);
104+
105+
int templateIndex;
106+
if (startTemplateIndex != -1 && endTemplateIndex != -1) {
107+
templateIndex =
108+
(startTemplateIndex < endTemplateIndex)
109+
? startTemplateIndex
110+
: endTemplateIndex;
111+
} else if (startTemplateIndex != -1) {
112+
templateIndex = startTemplateIndex;
113+
} else if (endTemplateIndex != -1) {
114+
templateIndex = endTemplateIndex;
115+
} else {
116+
result += input.substring(currentIndex);
117+
break;
118+
}
119+
120+
result += input.substring(currentIndex, templateIndex);
121+
122+
final closingIndex = input.indexOf(closingCurlyBrace, templateIndex);
123+
if (closingIndex == -1) {
124+
result += input.substring(templateIndex);
125+
break;
126+
}
127+
final closingChars =
128+
input.substring(closingIndex).startsWith('$closingCurlyBrace$newLine')
129+
? '$closingCurlyBrace$newLine'
130+
: closingCurlyBrace;
131+
currentIndex = closingIndex + closingChars.length;
132+
}
133+
134+
return result;
135+
}
96136
}
97137

98138
/// Workaround to force reload the Property Editor when it disconnects.

packages/devtools_app/test/standalone_ui/ide_shared/property_editor/utils_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,17 @@ void main() {
154154
expect(_hasStyle(secondChild, style: regularFontStyle), isTrue);
155155
});
156156

157+
testWidgets('DartDocConverter handles @template indicators', (
158+
WidgetTester tester,
159+
) async {
160+
final converter = DartDocConverter(_stringWithTemplates);
161+
final text = converter.toText(
162+
regularFontStyle: regularFontStyle,
163+
fixedFontStyle: fixedFontStyle,
164+
);
165+
expect(text.textSpan?.toPlainText(), equals(_stringWithoutTemplates));
166+
});
167+
157168
testWidgets('DartDocConverter handles empty strings', (
158169
WidgetTester tester,
159170
) async {
@@ -169,3 +180,15 @@ void main() {
169180

170181
bool _hasStyle(TextSpan span, {required TextStyle style}) =>
171182
span.style!.color == style.color;
183+
184+
const _stringWithTemplates = '''
185+
This is a Dart doc.
186+
{@template flutter.widgets.someWidget}
187+
Inside of the template.
188+
{@endtemplate}
189+
''';
190+
191+
const _stringWithoutTemplates = '''
192+
This is a Dart doc.
193+
Inside of the template.
194+
''';

0 commit comments

Comments
 (0)