2022-10-08 20:52:10 +00:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:subwrite/sourcelink.dart';
|
|
|
|
import 'package:subwrite/theme.dart';
|
|
|
|
|
|
|
|
import 'annotation.dart';
|
|
|
|
|
|
|
|
double fontSize = 14;
|
|
|
|
double gutterFontSize = 12;
|
|
|
|
|
|
|
|
class LineDecoration {
|
|
|
|
int start = 0;
|
|
|
|
int end = 0;
|
|
|
|
Color color = Colors.white;
|
|
|
|
Color background = Colors.white;
|
|
|
|
bool underline = false;
|
|
|
|
bool italic = false;
|
|
|
|
bool bold = false;
|
|
|
|
|
|
|
|
SourceLink? message;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Highlighter {
|
|
|
|
Highlighter() {}
|
|
|
|
|
|
|
|
List<InlineSpan> run(BuildContext context, String text, int line, bool highlightRisk, TextStyle style, List<LineAnnotation>? insights, int? cursorStart, int? cursorEnd, bool lineSelected) {
|
|
|
|
TextStyle defaultStyle = style.copyWith(fontFamily: 'Iosevka', fontSize: fontSize, color: foreground, height: 1.2);
|
|
|
|
List<InlineSpan> res = <InlineSpan>[];
|
|
|
|
List<LineDecoration> decors = <LineDecoration>[];
|
|
|
|
|
|
|
|
insights
|
|
|
|
?.where(
|
|
|
|
(element) => element.annotationType == AnnotationType.highlight,
|
|
|
|
)
|
|
|
|
.forEach((element) {
|
|
|
|
LineDecoration d = LineDecoration();
|
|
|
|
//print("${element.sourceLink.colStart} ${element.sourceLink.colEnd}");
|
|
|
|
d.start = element.sourceLink.colStart! - 1;
|
|
|
|
d.end = element.sourceLink.colEnd! - 2;
|
|
|
|
d.background = Colors.transparent;
|
|
|
|
switch (element.type) {
|
|
|
|
case "header":
|
|
|
|
d.color = header;
|
|
|
|
break;
|
|
|
|
case "bold":
|
|
|
|
d.color = keyword;
|
|
|
|
d.bold = true;
|
|
|
|
break;
|
|
|
|
case "code":
|
|
|
|
d.color = function;
|
|
|
|
break;
|
2022-10-09 23:39:25 +00:00
|
|
|
case "spellcheck":
|
|
|
|
d.color = variable;
|
|
|
|
d.underline = true;
|
2022-10-08 20:52:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
decors.add(d);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Assume that decorators do not overlap...
|
|
|
|
decors.sort((a, b) {
|
|
|
|
return a.start.compareTo(b.start);
|
|
|
|
});
|
|
|
|
|
|
|
|
text += ' ';
|
|
|
|
|
|
|
|
for (int i = 0; i < text.length; i++) {
|
|
|
|
String current = text[i];
|
|
|
|
TextStyle style = defaultStyle.copyWith();
|
|
|
|
|
|
|
|
// decorate
|
|
|
|
for (var d in decors) {
|
|
|
|
// if we are in this decoration...
|
|
|
|
if (i >= d.start && i <= d.end) {
|
|
|
|
style = style.copyWith(color: d.color);
|
|
|
|
if (d.bold = true) {
|
|
|
|
style = style.copyWith(fontWeight: FontWeight.bold);
|
|
|
|
}
|
2022-10-09 23:39:25 +00:00
|
|
|
if (d.underline = true) {
|
|
|
|
style = style.copyWith(fontStyle: FontStyle.italic);
|
|
|
|
}
|
2022-10-08 20:52:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// is within selection
|
|
|
|
if (lineSelected) {
|
|
|
|
style = style.copyWith(backgroundColor: selection);
|
|
|
|
} else if (cursorStart != null && cursorEnd != null) {
|
|
|
|
if (i >= cursorStart && i < cursorEnd) {
|
|
|
|
style = style.copyWith(backgroundColor: selection);
|
|
|
|
}
|
|
|
|
} else if (cursorStart != null && cursorEnd == null) {
|
|
|
|
if (i >= cursorStart) {
|
|
|
|
style = style.copyWith(backgroundColor: selection);
|
|
|
|
}
|
|
|
|
} else if (cursorStart == null && cursorEnd != null) {
|
|
|
|
if (i < cursorEnd) {
|
|
|
|
style = style.copyWith(backgroundColor: selection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (highlightRisk) {
|
|
|
|
style = style.copyWith(
|
|
|
|
backgroundColor: risk.withOpacity(0.50),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (current == " ") {
|
|
|
|
res.add(TextSpan(text: "_", style: style.copyWith(color: style.backgroundColor), semanticsLabel: " ", mouseCursor: SystemMouseCursors.text));
|
|
|
|
} else {
|
|
|
|
// we need to add a '\ufeff' (Zero Width No-Break Space) here to avoid RichText doing ridiculous
|
|
|
|
// breaking logic with hyphens / slashes etc.
|
|
|
|
res.add(TextSpan(text: '$current\ufeff', style: style, mouseCursor: SystemMouseCursors.text));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|