From 3c295552df52cc6a1d939563e0894a779ac29df7 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Wed, 6 Jul 2022 11:28:06 +0200 Subject: [PATCH] list button implementation --- lib/formatter.dart | 15 ++++-- lib/util/text_lines_iterator.dart | 89 +++++++++++++++++++++++++++++++ lib/widgets/editor/toolbar.dart | 66 +++++++++++++++++++++-- 3 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 lib/util/text_lines_iterator.dart diff --git a/lib/formatter.dart b/lib/formatter.dart index 9f7c85c..5638d86 100644 --- a/lib/formatter.dart +++ b/lib/formatter.dart @@ -1,16 +1,25 @@ import 'package:flutter/services.dart'; -extension on String { - int getBeginningOfPreviousLine(int startingIndex) { +extension Utilities on String { + int getBeginningOfTheLine(int startingIndex) { + if (startingIndex <= 0) return 0; for (var i = startingIndex; 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; + } + + return length - 1; + } + // returns the line that ends at endingIndex String lineBefore(int endingIndex) { - return substring(getBeginningOfPreviousLine(endingIndex), endingIndex + 1); + return substring(getBeginningOfTheLine(endingIndex), endingIndex + 1); } } diff --git a/lib/util/text_lines_iterator.dart b/lib/util/text_lines_iterator.dart new file mode 100644 index 0000000..ae930a4 --- /dev/null +++ b/lib/util/text_lines_iterator.dart @@ -0,0 +1,89 @@ +import 'package:flutter/widgets.dart'; + +/// utililty class for traversing through multiline text +class TextLinesIterator extends Iterator { + String text; + int beg; + int end; + TextSelection? selection; + + TextLinesIterator(this.text, {this.selection}) + : end = -1, + beg = -1; + + factory TextLinesIterator.fromController(TextEditingController controller) => + TextLinesIterator(controller.text, selection: controller.selection); + + bool get isWithinSelection { + final selection = this.selection; + if (selection == null || beg == -1) { + return false; + } else { + return (selection.end >= beg && beg >= selection.start) || + (selection.end >= end && end >= selection.start) || + (end >= selection.start && selection.start >= beg) || + (end >= selection.end && selection.end >= beg) || + (beg <= selection.start && + selection.start <= end && + beg <= selection.end && + selection.end <= end); + } + } + + @override + String get current { + return text.substring(beg, end); + } + + set current(String newVal) { + final selected = isWithinSelection; + text = text.replaceRange(beg, end, newVal); + final wordLen = end - beg; + final dif = newVal.length - wordLen; + end += dif; + + final selection = this.selection; + if (selection == null) return; + + if (selected || selection.baseOffset > end) { + this.selection = + selection.copyWith(extentOffset: selection.extentOffset + dif); + } + } + + void reset() { + end = -1; + beg = -1; + } + + @override + bool moveNext() { + if (end == text.length) { + return false; + } + if (beg == -1) { + end = 0; + beg = 0; + } else { + end += 1; + beg = end; + } + for (; end < text.length; end++) { + if (text[end] == '\n') { + return true; + } + } + end = text.length; + return true; + } + + /// returns the lines as a list but also moves the pointer to the back + List get asList { + reset(); + final list = []; + while (moveNext()) { + list.add(current); + } + return list; + } +} diff --git a/lib/widgets/editor/toolbar.dart b/lib/widgets/editor/toolbar.dart index d4bafee..bf4939d 100644 --- a/lib/widgets/editor/toolbar.dart +++ b/lib/widgets/editor/toolbar.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import '../../formatter.dart'; import '../../util/extensions/spaced.dart'; +import '../../util/text_lines_iterator.dart'; class Reformat { final String text; @@ -43,6 +45,54 @@ extension on TextEditingController { )); } + String get firstSelectedLine { + if (text.isEmpty) return ''; + return text.substring(text.getBeginningOfTheLine(selection.start - 1), + text.getEndOfTheLine(selection.end)); + } + + void removeAtBeginningOfEverySelectedLine(String s) { + final lines = TextLinesIterator.fromController(this); + var linesCount = 0; + while (lines.moveNext()) { + if (lines.isWithinSelection) { + if (lines.current.startsWith(RegExp.escape(s))) { + lines.current = lines.current.substring(s.length); + linesCount++; + } + } + } + + value = value.copyWith( + text: lines.text, + selection: selection.copyWith( + baseOffset: selection.baseOffset - s.length, + extentOffset: selection.extentOffset - s.length * linesCount, + ), + ); + } + + void insertAtBeginningOfEverySelectedLine(String s) { + final lines = TextLinesIterator.fromController(this); + var linesCount = 0; + while (lines.moveNext()) { + if (lines.isWithinSelection) { + if (!lines.current.startsWith(RegExp.escape(s))) { + lines.current = '$s${lines.current}'; + linesCount++; + } + } + } + + value = value.copyWith( + text: lines.text, + selection: selection.copyWith( + baseOffset: selection.baseOffset + s.length, + extentOffset: selection.extentOffset + s.length * linesCount, + ), + ); + } + void reformat(Reformat Function(String selection) reformatter) { final beg = beforeSelectionText; final mid = selectionText; @@ -101,9 +151,7 @@ class Toolbar extends HookWidget { icon: const Icon(Icons.person), ), IconButton( - onPressed: () { - // - }, + onPressed: () {}, icon: const Icon(Icons.home), ), IconButton( @@ -119,7 +167,17 @@ class Toolbar extends HookWidget { icon: const Icon(Icons.format_quote), ), IconButton( - onPressed: () {}, + onPressed: () { + final line = controller.firstSelectedLine; + + if (line.startsWith(RegExp.escape('* '))) { + controller.removeAtBeginningOfEverySelectedLine('* '); + } else if (line.startsWith('- ')) { + controller.removeAtBeginningOfEverySelectedLine('- '); + } else { + controller.insertAtBeginningOfEverySelectedLine('- '); + } + }, icon: const Icon(Icons.format_list_bulleted)), IconButton( onPressed: () => controller.surround('`'),