fix(editor): drop misaligned gutter error pin, add bottom diagnostic strip
Two glitches visible in 0.15.1:
1. The flutter_code_editor gutter renders an Icons.cancel pin
for every analyzer issue, hard-positioned inside a 16-px
slot. With a single-digit indent the icon centres look off;
with double-digit line numbers it shifts further.
2. The hover popup for that pin paints at `offset.dx + width`
of the gutter cell — which lands INSIDE the code area, so
'Unknown capability ...' overlaps the YAML text the
operator is trying to read.
The package doesn't expose a fix for either — the icon is a
const in error.dart, the popup position is hard-coded in the
StatefulWidget. Cleanest path: hide the gutter error column
entirely (`GutterStyle.showErrors: false`) and surface the
analyzer issues through a dedicated bottom strip we own.
The new _DiagnosticStrip renders as a single 22-px line under
the editor: a coloured dot + 'N errors · M warnings · L7:
${first.message}'. Tap expands into a per-issue list capped at
140-px scroll, dot-coloured by IssueType. Empty state shows a
muted 'No issues' so the strip never disappears and never
shifts the layout.
Wavy underlines in the text body keep working untouched — the
two surfaces complement each other: the underline points at
the broken token, the strip names what's wrong.
Bumped to 0.16.0.
Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
ae443c6f81
commit
1b50924e16
2 changed files with 233 additions and 13 deletions
|
|
@ -592,6 +592,9 @@ outputs:
|
||||||
);
|
);
|
||||||
return CodeTheme(
|
return CodeTheme(
|
||||||
data: CodeThemeData(styles: _yamlStyle(theme)),
|
data: CodeThemeData(styles: _yamlStyle(theme)),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
child: CodeField(
|
child: CodeField(
|
||||||
controller: _controller.codeController,
|
controller: _controller.codeController,
|
||||||
textStyle: mono,
|
textStyle: mono,
|
||||||
|
|
@ -599,12 +602,27 @@ outputs:
|
||||||
minLines: null,
|
minLines: null,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
gutterStyle: GutterStyle(
|
gutterStyle: GutterStyle(
|
||||||
textStyle: mono.copyWith(color: theme.colorScheme.onSurfaceVariant),
|
textStyle: mono.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
background: theme.colorScheme.surfaceContainer,
|
background: theme.colorScheme.surfaceContainer,
|
||||||
showLineNumbers: true,
|
showLineNumbers: true,
|
||||||
|
// Disable the built-in error column entirely.
|
||||||
|
// The Icons.cancel pin is hard-positioned inside
|
||||||
|
// a 16-px slot, and its mouse-hover popup paints
|
||||||
|
// OVER the code area instead of beside it — the
|
||||||
|
// overlap was the operator-visible glitch in
|
||||||
|
// editor 0.15.1. Issues remain surfaced via the
|
||||||
|
// wavy underline (FlowYamlCodeController) plus
|
||||||
|
// the bottom diagnostic strip below.
|
||||||
|
showErrors: false,
|
||||||
),
|
),
|
||||||
background: theme.colorScheme.surface,
|
background: theme.colorScheme.surface,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
_DiagnosticStrip(controller: _controller.codeController),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1105,3 +1123,205 @@ class _NewFlowDialogState extends State<_NewFlowDialog> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bottom diagnostic strip — single-line "X issues · {first}"
|
||||||
|
/// summary that opens into a per-line list on tap. Replaces the
|
||||||
|
/// gutter's hard-positioned error pin so the popup never paints
|
||||||
|
/// over the code area, and gives the operator a stable surface
|
||||||
|
/// to scan even when the wavy underline sits off-screen.
|
||||||
|
///
|
||||||
|
/// The strip listens to the CodeController; it rebuilds on every
|
||||||
|
/// analyzer pass without polling.
|
||||||
|
class _DiagnosticStrip extends StatefulWidget {
|
||||||
|
final CodeController controller;
|
||||||
|
const _DiagnosticStrip({required this.controller});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_DiagnosticStrip> createState() => _DiagnosticStripState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DiagnosticStripState extends State<_DiagnosticStrip> {
|
||||||
|
bool _expanded = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
widget.controller.addListener(_onChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.controller.removeListener(_onChange);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onChange() {
|
||||||
|
if (mounted) setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final issues = widget.controller.analysisResult.issues;
|
||||||
|
if (issues.isEmpty) {
|
||||||
|
return Container(
|
||||||
|
height: 22,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.surfaceContainer,
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: theme.colorScheme.outlineVariant),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'No issues',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'JetBrains Mono',
|
||||||
|
fontSize: 10,
|
||||||
|
letterSpacing: 0.4,
|
||||||
|
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final errorCount = issues.where((i) => i.type == IssueType.error).length;
|
||||||
|
final warnCount = issues.where((i) => i.type == IssueType.warning).length;
|
||||||
|
final tone = errorCount > 0
|
||||||
|
? theme.colorScheme.error
|
||||||
|
: const Color(0xFFEF6C00); // amber/orange for warnings
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: theme.colorScheme.surfaceContainer,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => setState(() => _expanded = !_expanded),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: theme.colorScheme.outlineVariant),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: tone,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
_summary(errorCount, warnCount),
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'JetBrains Mono',
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: tone,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'L${issues.first.line + 1}: ${issues.first.message}',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'JetBrains Mono',
|
||||||
|
fontSize: 11,
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
_expanded ? Icons.expand_more : Icons.expand_less,
|
||||||
|
size: 16,
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_expanded) ...[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Divider(height: 1, color: theme.colorScheme.outlineVariant),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 140),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
for (final issue in issues)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 6,
|
||||||
|
height: 6,
|
||||||
|
margin: const EdgeInsets.only(top: 5, right: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: switch (issue.type) {
|
||||||
|
IssueType.error =>
|
||||||
|
theme.colorScheme.error,
|
||||||
|
IssueType.warning =>
|
||||||
|
const Color(0xFFEF6C00),
|
||||||
|
IssueType.info =>
|
||||||
|
theme.colorScheme.primary,
|
||||||
|
},
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 42,
|
||||||
|
child: Text(
|
||||||
|
'L${issue.line + 1}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'JetBrains Mono',
|
||||||
|
fontSize: 11,
|
||||||
|
color:
|
||||||
|
theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
issue.message,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'JetBrains Mono',
|
||||||
|
fontSize: 11,
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _summary(int errors, int warnings) {
|
||||||
|
final parts = <String>[];
|
||||||
|
if (errors > 0) parts.add('$errors error${errors == 1 ? '' : 's'}');
|
||||||
|
if (warnings > 0) {
|
||||||
|
parts.add('$warnings warning${warnings == 1 ? '' : 's'}');
|
||||||
|
}
|
||||||
|
return parts.join(' · ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
name: fai_studio_flow_editor
|
name: fai_studio_flow_editor
|
||||||
description: Swappable inline YAML editor for F∆I Studio flows.
|
description: Swappable inline YAML editor for F∆I Studio flows.
|
||||||
version: 0.15.1
|
version: 0.16.0
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
repository: https://git.flemming.ai/fai/studio-flow-editor
|
repository: https://git.flemming.ai/fai/studio-flow-editor
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue