buildTextSpan method
TextSpan
buildTextSpan({
- required BuildContext context,
- TextStyle? style,
- required bool withComposing,
override
Builds TextSpan from current editing value.
By default makes text in composing range appear as underlined. Descendants can override this method to customize appearance of text.
Implementation
@override
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
required bool withComposing,
}) {
final theme = Theme.of(context);
final base = (style ?? const TextStyle()).copyWith(
fontFamily: 'monospace',
fontSize: style?.fontSize ?? 12,
height: style?.height ?? 1.35,
color: style?.color ?? theme.colorScheme.onSurface,
);
final keyStyle = base.copyWith(
color: theme.colorScheme.primary,
fontWeight: FontWeight.w600,
);
final stringStyle = base.copyWith(
color: theme.brightness == Brightness.dark
? const Color(0xFFCE9178)
: const Color(0xFFA31515),
);
final numberStyle = base.copyWith(
color: theme.brightness == Brightness.dark
? const Color(0xFFB5CEA8)
: const Color(0xFF098658),
);
final keywordStyle = base.copyWith(
color: theme.brightness == Brightness.dark
? const Color(0xFF569CD6)
: const Color(0xFF0000FF),
fontWeight: FontWeight.w600,
);
final punctStyle = base.copyWith(
color: theme.colorScheme.onSurface.withValues(alpha: 0.55),
);
final text = value.text;
if (text.isEmpty) {
return TextSpan(style: base, text: '');
}
final issue = findJsonParseIssue(text);
final match = findMatchingBracket(text, value.selection.baseOffset);
final errorColor = theme.colorScheme.error;
final matchColor = theme.colorScheme.primary.withValues(alpha: 0.22);
final spans = <InlineSpan>[];
var start = 0;
void emit(int from, int to, TextStyle tokenStyle) {
if (from >= to) return;
var cursor = from;
while (cursor < to) {
var next = to;
TextStyle styleForRun = tokenStyle;
if (issue != null) {
final errStart = issue.offset.clamp(0, text.length);
final errEnd = issue.end.clamp(0, text.length);
if (cursor < errEnd && next > errStart) {
if (cursor < errStart) {
next = errStart;
} else {
next = math.min(next, errEnd);
styleForRun = tokenStyle.copyWith(
decoration: TextDecoration.underline,
decorationColor: errorColor,
decorationStyle: TextDecorationStyle.wavy,
decorationThickness: 1.6,
color: errorColor,
);
}
}
}
if (match != null) {
for (final index in [match.open, match.close]) {
if (cursor <= index && index < next) {
if (cursor < index) {
next = index;
} else {
next = index + 1;
styleForRun = styleForRun.copyWith(
backgroundColor: matchColor,
fontWeight: FontWeight.w800,
color: theme.colorScheme.primary,
);
}
break;
}
}
}
spans.add(
TextSpan(
text: text.substring(cursor, next),
style: styleForRun,
),
);
cursor = next;
}
}
for (final tokenMatch in _tokenPattern.allMatches(text)) {
if (tokenMatch.start > start) {
emit(start, tokenMatch.start, base);
}
final token = tokenMatch.group(0)!;
final TextStyle tokenStyle;
if (token.endsWith(':')) {
tokenStyle = keyStyle;
} else if (token.startsWith('"')) {
tokenStyle = stringStyle;
} else if (token == 'true' || token == 'false' || token == 'null') {
tokenStyle = keywordStyle;
} else if (RegExp(r'^-?\d').hasMatch(token)) {
tokenStyle = numberStyle;
} else {
tokenStyle = punctStyle;
}
emit(tokenMatch.start, tokenMatch.end, tokenStyle);
start = tokenMatch.end;
}
if (start < text.length) {
emit(start, text.length, base);
}
return TextSpan(style: base, children: spans);
}