126 lines
3.6 KiB
Dart
126 lines
3.6 KiB
Dart
import 'package:flutter/services.dart';
|
|
|
|
const unorderedListTypes = ['*', '+', '-'];
|
|
const orderedListTypes = [')', '.'];
|
|
|
|
extension Utilities on String {
|
|
int getBeginningOfTheLine(int from) {
|
|
if (from <= 0) return 0;
|
|
for (var i = from; i >= 0; i--) {
|
|
if (this[i] == '\n') return i + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int getEndOfTheLine(int from) {
|
|
for (var i = from; i < length; i++) {
|
|
if (this[i] == '\n') return i + 1;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
/// returns the line that ends at endingIndex
|
|
String lineUpTo(int characterIndex) {
|
|
return substring(getBeginningOfTheLine(characterIndex), characterIndex + 1);
|
|
}
|
|
}
|
|
|
|
extension on TextEditingValue {
|
|
/// Append a string after the cursor
|
|
TextEditingValue append(String s) {
|
|
final beg = text.substring(0, selection.baseOffset);
|
|
final end = text.substring(selection.baseOffset);
|
|
|
|
return copyWith(
|
|
text: '$beg$s$end',
|
|
selection: selection.copyWith(
|
|
baseOffset: selection.baseOffset + s.length,
|
|
extentOffset: selection.extentOffset + s.length,
|
|
),
|
|
);
|
|
}
|
|
|
|
/// cuts [characterCount] number of chars from before the cursor
|
|
TextEditingValue trimBeforeCursor(int characterCount) {
|
|
final beg = text.substring(0, selection.baseOffset);
|
|
final end = text.substring(selection.baseOffset);
|
|
|
|
return copyWith(
|
|
text: beg.substring(0, beg.length - characterCount - 1) + end,
|
|
selection: selection.copyWith(
|
|
baseOffset: selection.baseOffset - characterCount,
|
|
extentOffset: selection.extentOffset - characterCount,
|
|
));
|
|
}
|
|
}
|
|
|
|
/// Provides convenience formatting in markdown text fields
|
|
class MarkdownFormatter extends TextInputFormatter {
|
|
@override
|
|
TextEditingValue formatEditUpdate(
|
|
TextEditingValue oldValue, TextEditingValue newValue) {
|
|
if (oldValue.text.length > newValue.text.length) return newValue;
|
|
|
|
var newVal = newValue;
|
|
|
|
final char = newValue.text[newValue.selection.baseOffset - 1];
|
|
|
|
if (char == '\n') {
|
|
final lineBefore =
|
|
newValue.text.lineUpTo(newValue.selection.baseOffset - 2);
|
|
|
|
TextEditingValue unorderedListContinuation(
|
|
String listChar, TextEditingValue tev) {
|
|
final regex = RegExp(r'(\s*)' '${RegExp.escape(listChar)} (.*)');
|
|
final match = regex.matchAsPrefix(lineBefore);
|
|
|
|
if (match == null) {
|
|
return tev;
|
|
}
|
|
|
|
final listItemBody = match.group(2);
|
|
final indent = match.group(1);
|
|
|
|
if (listItemBody == null || listItemBody.isEmpty) {
|
|
return tev.trimBeforeCursor(listChar.length + (indent?.length ?? 1));
|
|
}
|
|
|
|
return tev.append('$indent$listChar ');
|
|
}
|
|
|
|
TextEditingValue orderedListContinuation(
|
|
String afterNumberChar, TextEditingValue tev) {
|
|
final regex =
|
|
RegExp(r'(\s*)(\d+)' '${RegExp.escape(afterNumberChar)} (.*)');
|
|
final match = regex.matchAsPrefix(lineBefore);
|
|
|
|
if (match == null) {
|
|
return tev;
|
|
}
|
|
|
|
final listItemBody = match.group(3)!;
|
|
final indent = match.group(1)!;
|
|
final numberStr = match.group(2)!;
|
|
|
|
if (listItemBody.isEmpty) {
|
|
return tev.trimBeforeCursor(
|
|
indent.length + numberStr.length + afterNumberChar.length + 1);
|
|
}
|
|
|
|
final number = (int.tryParse(match.group(2)!) ?? 0) + 1;
|
|
return tev.append('$indent$number$afterNumberChar ');
|
|
}
|
|
|
|
for (final c in unorderedListTypes) {
|
|
newVal = unorderedListContinuation(c, newVal);
|
|
}
|
|
for (final c in orderedListTypes) {
|
|
newVal = orderedListContinuation(c, newVal);
|
|
}
|
|
}
|
|
|
|
return newVal;
|
|
}
|
|
}
|