buildTextSpan method
TextSpan
buildTextSpan({
- required BuildContext context,
- TextStyle? style,
- 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,
bool? withComposing,
}) {
TextStyle baseStyle = TextStyle(
color: editorTheme['root']?.color,
height: 1.5,
);
if (textStyle != null) {
baseStyle = baseStyle.merge(textStyle);
}
final int cursorPosition = selection.baseOffset;
int? bracket1, bracket2;
if (Shared().aiResponse != null &&
text.isNotEmpty &&
Shared().aiResponse!.isNotEmpty) {
final String textBeforeCursor = text.substring(0, cursorPosition);
final String textAfterCursor = text.substring(cursorPosition);
final bool atLineEnd =
textAfterCursor.isEmpty ||
textAfterCursor.startsWith('\n') ||
textAfterCursor.trim().isEmpty;
if (!atLineEnd) {
return TextSpan(text: text, style: baseStyle);
}
final String lastTypedChar = textBeforeCursor.isNotEmpty
? textBeforeCursor[textBeforeCursor.length - 1].replaceAll("\n", '')
: '';
final List<Node>? beforeCursorNodes = highlight
.parse(textBeforeCursor, language: _langId)
.nodes;
if (Shared().lastCursorPosition != null &&
cursorPosition != Shared().lastCursorPosition) {
final movedCursor =
(cursorPosition != Shared().lastCursorPosition! + 1);
final mismatch =
lastTypedChar.trim().isNotEmpty &&
(Shared().aiResponse == null ||
Shared().aiResponse!.isEmpty ||
Shared().aiResponse![0] != lastTypedChar);
if (movedCursor || mismatch) {
Shared().aiResponse = null;
}
}
if (Shared().aiResponse != null &&
Shared().aiResponse!.isNotEmpty &&
Shared().aiResponse![0] == lastTypedChar) {
Shared().aiResponse = Shared().aiResponse!.substring(1);
}
Shared().lastCursorPosition = cursorPosition;
TextSpan aiOverlay = TextSpan(
text: Shared().aiResponse,
style:
Shared().aiOverlayStyle ??
TextStyle(color: Colors.grey, fontStyle: FontStyle.italic),
);
final List<Node>? afterCursorNodes = highlight
.parse(textAfterCursor, language: _langId)
.nodes;
if (beforeCursorNodes != null) {
if (cursorPosition != selection.baseOffset) {
Shared().aiResponse = null;
}
return TextSpan(
style: baseStyle,
children: [
..._convert(beforeCursorNodes),
aiOverlay,
..._convert(afterCursorNodes ?? <Node>[]),
],
);
}
}
final List<String> lines = text.isNotEmpty ? text.split("\n") : [];
if (cursorPosition >= 0 && cursorPosition <= text.length) {
final String? before = cursorPosition > 0
? text[cursorPosition - 1]
: null;
final String? after = cursorPosition < text.length
? text[cursorPosition]
: null;
final int? pos = (before != null && '{}[]()'.contains(before))
? cursorPosition - 1
: (after != null && '{}[]()'.contains(after))
? cursorPosition
: null;
if (pos != null) {
final match = _findMatchingBracket(text, pos);
if (match != null) {
bracket1 = pos;
bracket2 = match;
}
}
}
final List<FoldRange> foldedRanges = Shared().lineStates.value
.where((line) => line.foldRange?.isFolded == true)
.map((line) => line.foldRange!)
.toList();
foldedRanges.sort((a, b) => a.startLine.compareTo(b.startLine));
final filteredFolds = <FoldRange>[];
for (final FoldRange fold in foldedRanges) {
bool isNested = filteredFolds.any(
(parent) =>
fold.startLine >= parent.startLine &&
fold.endLine <= parent.endLine,
);
if (!isNested) filteredFolds.add(fold);
}
filteredFolds.sort((a, b) => b.startLine.compareTo(a.startLine));
for (final fold in filteredFolds) {
int start = fold.startLine - 1;
int end = fold.endLine;
if (start >= 0 && end <= lines.length && start < end) {
lines[start] =
"${lines[start]}...${'\u200D' * ((lines.sublist(start + 1, end).join('\n').length) - 2)}";
lines.removeRange(start + 1, end);
}
}
final newText = lines.join('\n');
final List<Node>? nodes = highlight.parse(newText, language: _langId).nodes;
final Set<int> unmatchedBrackets = _findUnmatchedBrackets(text);
if (nodes != null && editorTheme.isNotEmpty) {
return TextSpan(
style: baseStyle,
children: _convert(nodes, 0, bracket1, bracket2, unmatchedBrackets),
);
} else {
return TextSpan(text: newText, style: textStyle);
}
}