Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multiline editing #3

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions lib/src/code_controller.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:math';

import 'package:code_text_field/src/autocomplete/popup_controller.dart';
import 'package:code_text_field/src/multiline_controller.dart';
import 'package:code_text_field/src/autocomplete/suggestion.dart';
import 'package:code_text_field/src/autocomplete/suggestion_generator.dart';
import 'package:code_text_field/src/code_modifier.dart';
Expand Down Expand Up @@ -54,6 +55,7 @@ class CodeController extends TextEditingController {
bool isPopupShown = false;
RegExp? styleRegExp;
late PopupController popupController;
late MultilineController? multilineController;
SuggestionGenerator? suggestionGenerator;

CodeController({
Expand Down Expand Up @@ -82,6 +84,7 @@ class CodeController extends TextEditingController {
modifiers.forEach((el) {
modifierMap[el.char] = el;
});
this.multilineController = MultilineController();
suggestionGenerator = SuggestionGenerator(
'language_id'); // TODO: replace string with some generated value for current language id
this.popupController = PopupController(onCompletionSelected: this.insertSelectedWord);
Expand Down Expand Up @@ -127,7 +130,26 @@ class CodeController extends TextEditingController {
removeChar();
}

void handleTap(bool isMulti) {
if (isMulti) {
this.multilineController!.isMutli = true;
this.multilineController!.isCaret = true;
value = this.multilineController!.insertCaret(value);
} else {
multilineController!.updateCurrentSelection(
multilineController!.currentSelection, value.selection.start);
value = multilineController!.clearCarets(value);
}
}

KeyEventResult onKey(RawKeyEvent event) {
if (event.isKeyPressed(LogicalKeyboardKey.arrowDown) ||
event.isKeyPressed(LogicalKeyboardKey.arrowUp) ||
event.isKeyPressed(LogicalKeyboardKey.arrowRight) ||
event.isKeyPressed(LogicalKeyboardKey.arrowLeft)) {
value = this.multilineController!.clearCarets(value);
}

if (event.isKeyPressed(LogicalKeyboardKey.tab)) {
text = text.replaceRange(selection.start, selection.end, "\t");
return KeyEventResult.handled;
Expand Down Expand Up @@ -155,11 +177,13 @@ class CodeController extends TextEditingController {
String selectedWord = popupController.getSelectedWord();
int startPosition = selection.baseOffset -
suggestionGenerator!.getCurrentWordPrefix().length;
text = text.replaceRange(startPosition, selection.baseOffset, selectedWord);
selection = previousSelection.copyWith(
baseOffset: startPosition + selectedWord.length,
extentOffset: startPosition + selectedWord.length,
);
value = value.copyWith(
text: text.replaceRange(
startPosition, selection.baseOffset, selectedWord),
selection: previousSelection.copyWith(
baseOffset: startPosition + selectedWord.length,
extentOffset: startPosition + selectedWord.length,
));
popupController.hide();
}

Expand Down Expand Up @@ -201,7 +225,7 @@ class CodeController extends TextEditingController {
@override
set value(TextEditingValue newValue) {
final loc = _insertedLoc(text, newValue.text);
if (loc != null) {
if (loc != null && !multilineController!.isMutli) {
final char = newValue.text[loc];
final modifier = modifierMap[char];
final val = modifier?.updateString(rawText, selection, params);
Expand All @@ -215,9 +239,14 @@ class CodeController extends TextEditingController {
}
}

multilineController!.updateCurrentSelection(
newValue.selection.start, value.selection.start);

bool hasTextChanged = newValue.text != super.value.text;
bool hasSelectionChanged = (newValue.selection != super.value.selection);

newValue = multilineController!.updateMultiline(value, newValue);

//Because of this part of code ctrl + z dont't work. But maybe it's important, so please don't delete.
// Now fix the textfield for web
// if (_webSpaceFix)
Expand Down
7 changes: 6 additions & 1 deletion lib/src/code_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ class CodeFieldState extends State<CodeField> {
//
StreamSubscription<bool>? _keyboardVisibilitySubscription;
FocusNode? _focusNode;
bool isMultiline = false;
String? lines;
String longestLine = "";
late Size windowSize;
Expand Down Expand Up @@ -235,6 +236,8 @@ class CodeFieldState extends State<CodeField> {
}

KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
this.isMultiline =
event.isAltPressed && event.isControlPressed ? true : false;
return widget.controller.onKey(event);
}

Expand Down Expand Up @@ -396,7 +399,9 @@ class CodeFieldState extends State<CodeField> {
enabled: widget.enabled,
onChanged: widget.onChanged,
readOnly: widget.readOnly,
);
onTap: () {
widget.controller.handleTap(isMultiline);
});

final editingField = Theme(
data: Theme.of(context).copyWith(
Expand Down
135 changes: 135 additions & 0 deletions lib/src/multiline_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import 'package:flutter/material.dart';

class MultilineController {
List<int> carets = [];
late int currentSelection = 0;
bool isCaret = false;
bool isMutli = false;

MultilineController() : super();

TextEditingValue updateMultiline(
TextEditingValue value, TextEditingValue newValue) {
bool isDeleting = newValue.text.length < value.text.length;
bool isInserting = newValue.text.length > value.text.length;
if (this.isMutli) {
if (isDeleting || (isInserting && !isCaret)) {
int diff = newValue.text.length - value.text.length;
String newText = newValue.text;
int start = newValue.selection.start - diff;

String insertedValue =
diff > 0 ? newText.substring(start, start + diff) : "";

for (int i = 0; i < carets.length; i++) {
if (carets[i] >= currentSelection) {
carets[i] += diff;
}
}

carets.sort();

for (int i = 0; i < carets.length; i++) {
newText = newText.replaceRange(
carets[i] - (diff > 0 ? 0 : 1), carets[i], insertedValue);

carets[i] += diff;

if (isDeleting) {
bool isThisCaretCollide = carets.any((caret) =>
(carets[i] == caret && i != carets.indexOf(caret))) ||
carets[i] == newValue.selection.start ||
carets[i] == newValue.selection.start - 1;

if (isThisCaretCollide) {
newText = newText.replaceRange(carets[i] - 2, carets[i] - 1, "");
carets[i] = -1;
}
}

if (i + 1 != carets.length) {
carets[i + 1] += diff * (i + 1);
}
}

carets = carets.where((i) => i != -1).toList();

int newOffSet =
carets.where((pos) => pos <= currentSelection).length * diff;

newValue = newValue.copyWith(
selection: newValue.selection.copyWith(
baseOffset: newValue.selection.start + newOffSet,
extentOffset: newValue.selection.start + newOffSet),
text: newText.toString());
}
this.isCaret = false;
}
return newValue;
}

TextEditingValue clearCarets(TextEditingValue value) {
if (carets.isEmpty) {
return value;
}

this.isMutli = false;
String clearedText = value.text;
carets.sort();

for (int i = 0; i < carets.length; i++) {
clearedText = clearedText.replaceRange(carets[i], carets[i] + 1, "");
if (i + 1 != carets.length) {
carets[i + 1] -= i + 1;
}
}

value = value.copyWith(
text: clearedText,
selection: value.selection.copyWith(
baseOffset: value.selection.start - carets.length,
extentOffset: value.selection.start - carets.length));
carets.clear();

return value;
}

TextEditingValue insertCaret(TextEditingValue value) {
final sel = value.selection;
if (this.carets.contains(sel.start) ||
this.carets.contains(sel.start - 1) ||
value.selection.start == currentSelection) {
return value;
}

if (value.text.length == 0) {
currentSelection = 0;
}

int isAbove = currentSelection < value.selection.start ? 1 : 0;

for (int i = 0; i < carets.length; i++) {
if (carets[i] >= currentSelection) {
carets[i] += 1;
}
}

carets.add(currentSelection);

value = value.copyWith(
text: value.text.replaceRange(currentSelection, currentSelection, "|"),
selection: sel.copyWith(
baseOffset: sel.start + isAbove,
extentOffset: sel.start + isAbove,
));
currentSelection = value.selection.start;

return value;
}

void updateCurrentSelection(int newSelection, int selection) {
if (newSelection != selection || currentSelection == -1) {
currentSelection = selection;
}
}
}