Skip to content

Commit

Permalink
Add auto formats to history
Browse files Browse the repository at this point in the history
  • Loading branch information
amantoux committed Dec 4, 2023
1 parent 4a8a857 commit 69317ce
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 56 deletions.
31 changes: 20 additions & 11 deletions packages/fleather/lib/src/widgets/autoformats.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ abstract class AutoFormat {
/// Indicates whether character trigger auto format is kept in document
///
/// E.g: for link detections, '[space]' is kept whereas for Markdown block
/// shortcuts, the '[space]' is not added to document
/// shortcuts, the '[space]' is not added to document, it only serves to
/// trigger the block formatting
bool get keepTriggerCharacter;

/// Upon upon insertion of a space or new line run format detection and appy
/// Upon upon insertion of a space or new line run format detection and apply
/// formatting to document
/// Returns a [ActiveFormatResult].
AutoFormatResult? apply(
Expand All @@ -44,28 +45,36 @@ class AutoFormats {

AutoFormatResult? _activeSuggestion;

/// The selection override of the active formatting suggestion
TextSelection? get selection => _activeSuggestion!.selection;

/// The position at with the active suggestion can be deactivated
int get undoPosition => _activeSuggestion!.undoPositionCandidate;

/// `true` if the active suggestion auto format keeps trigger character in
/// document; `false` otherwise
bool get activeSuggestionKeepTriggerCharacter =>
_activeSuggestion!.keepTriggerCharacter;

/// `true` if there is an active auto format suggestion; `false` otherwise
bool get hasActiveSuggestion => _activeSuggestion != null;

/// Perform detection of auto formats and apply changes to [document]
///
/// Inserted data must be of type [String]
TextSelection? run(ParchmentDocument document, int position, Object data) {
/// Returns `true` if auto format was activated
bool run(ParchmentDocument document, int position, Object data) {
if (data is! String || data.isEmpty) {
return null;
return false;
}

for (final autoFormat in _autoFormats) {
_activeSuggestion = autoFormat.apply(document, position, data);
if (_activeSuggestion != null) {
return _activeSuggestion!.selection;
return true;
}
}
return null;
return false;
}

/// Remove auto format from [document] and de-activate current suggestion
Expand Down Expand Up @@ -168,7 +177,7 @@ class _AutoFormatLinks extends AutoFormat {
}
}

/// Replaces certain Markdown shortcuts with actual line or block styles.
// Replaces certain Markdown shortcuts with actual line or block styles.
class _MarkdownShortCuts extends AutoFormat {
static final rules = <String, ParchmentAttribute>{
'-': ParchmentAttribute.block.bulletList,
Expand Down Expand Up @@ -302,8 +311,8 @@ class _MarkdownShortCuts extends AutoFormat {
}
}

/// Skips to the beginning of line containing position at specified [length]
/// and returns contents of the line skipped so far.
// Skips to the beginning of line containing position at specified [length]
// and returns contents of the line skipped so far.
List<Operation> skipToLineAt(DeltaIterator iter, int length) {
if (length == 0) {
return List.empty(growable: false);

Check warning on line 318 in packages/fleather/lib/src/widgets/autoformats.dart

View check run for this annotation

Codecov / codecov/patch

packages/fleather/lib/src/widgets/autoformats.dart#L318

Added line #L318 was not covered by tests
Expand Down Expand Up @@ -333,8 +342,8 @@ List<Operation> skipToLineAt(DeltaIterator iter, int length) {
return prefix;
}

/// Infers text direction from the input when happens in the beginning of a line.
/// This rule also removes alignment and sets it based on inferred direction.
// Infers text direction from the input when happens in the beginning of a line.
// This rule also removes alignment and sets it based on inferred direction.
class _AutoTextDirection extends AutoFormat {
const _AutoTextDirection();

Expand Down
16 changes: 6 additions & 10 deletions packages/fleather/lib/src/widgets/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class FleatherController extends ChangeNotifier {
late final _Throttled<Delta> _throttledPush;
Timer? _throttleTimer;

// The autoformat handler
// The auto format handler
final AutoFormats _autoFormats;

/// Currently selected text within the [document].
Expand Down Expand Up @@ -120,6 +120,7 @@ class FleatherController extends ChangeNotifier {
final isDataNotEmpty = data is String ? data.isNotEmpty : true;

if (!_captureAutoFormatCancellationOrUndo(document, index, length, data)) {
_updateHistory();
notifyListeners();
return;
}
Expand Down Expand Up @@ -152,12 +153,12 @@ class FleatherController extends ChangeNotifier {
),
source: ChangeSource.local,
);
final autoFormatPerformed = _autoFormats.run(document, index, data);
// Only update history when text is being updated
// We do not want to update it when selection is changed
_updateHistory();
final autoFormatSelection = _autoFormats.run(document, index, data);
if (autoFormatSelection != null) {
_updateSelectionSilent(autoFormatSelection,
if (autoFormatPerformed && _autoFormats.selection != null) {
_updateSelectionSilent(_autoFormats.selection!,
source: ChangeSource.local);
}
}
Expand Down Expand Up @@ -329,12 +330,7 @@ extension HistoryHandler on FleatherController {
source: ChangeSource.history);
}

void _updateHistory({bool forceNewEntry = false}) {
if (forceNewEntry) {
_history.push(document.toDelta());
return;
}

void _updateHistory() {
if (plainTextEditingValue == TextEditingValue.empty) {
return;
}
Expand Down
60 changes: 25 additions & 35 deletions packages/fleather/test/widgets/autoformats_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ void main() {
final document = ParchmentDocument.fromJson([
{'insert': 'Some long text and a https://fleather-editor.github.io\n'}
]);
final selection = autoformats.run(document, 54, ' ');
expect(selection, isNull);
final performed = autoformats.run(document, 54, ' ');
expect(performed, true);
expect(autoformats.selection, isNull);
final attributes = document.toDelta().toList()[1].attributes;
expect(attributes, isNotNull);
expect(attributes!.containsKey(ParchmentAttribute.link.key), isTrue);
Expand All @@ -27,8 +28,9 @@ void main() {
final document = ParchmentDocument.fromJson([
{'insert': 'Some long text and a www.github.com\n'}
]);
final selection = autoformats.run(document, 35, ' ');
expect(selection, isNull);
final performed = autoformats.run(document, 35, ' ');
expect(performed, isTrue);
expect(autoformats.selection, isNull);
final attributes = document.toDelta().toList()[1].attributes;
expect(attributes, isNotNull);
expect(attributes!.containsKey(ParchmentAttribute.link.key), isTrue);
Expand All @@ -41,8 +43,8 @@ void main() {
final document = ParchmentDocument.fromJson([
{'insert': 'Some long text and a https://fleather-editor.github.io\n'}
]);
final selection = autoformats.run(document, 54, 'p');
expect(selection, null);
final performed = autoformats.run(document, 54, 'p');
expect(performed, false);
expect(autoformats.hasActiveSuggestion, isFalse);
});

Expand Down Expand Up @@ -74,8 +76,9 @@ void main() {
final document = ParchmentDocument.fromJson([
{'insert': 'Some long text\n* \nthat continues\n'}
]);
final selection = autoformats.run(document, 16, ' ');
expect(selection, const TextSelection.collapsed(offset: 15));
final performed = autoformats.run(document, 16, ' ');
expect(performed, true);
expect(autoformats.selection, const TextSelection.collapsed(offset: 15));
final attributes = document.toDelta().toList()[1].attributes;
expect(attributes, isNotNull);
expect(attributes!.containsKey(ParchmentAttribute.block.key), isTrue);
Expand All @@ -91,25 +94,9 @@ void main() {
},
{'insert': '\nthat continues\n'}
]);
final selection = autoformats.run(document, 16, ' ');
expect(selection, const TextSelection.collapsed(offset: 16));
final attributes = document.toDelta().toList()[2].attributes;
expect(attributes, isNotNull);
expect(attributes!.containsKey(ParchmentAttribute.block.key), isTrue);
expect(attributes[ParchmentAttribute.block.key],
ParchmentAttribute.block.bulletList.value);
});

test('Ignore if in ', () {
final document = ParchmentDocument.fromJson([
{'insert': 'Some long text\n* '},
{
'insert': SpanEmbed('some', data: {'ok': 'ok'}).toJson()
},
{'insert': '\nthat continues\n'}
]);
final selection = autoformats.run(document, 16, ' ');
expect(selection, const TextSelection.collapsed(offset: 16));
final performed = autoformats.run(document, 16, ' ');
expect(performed, true);
expect(autoformats.selection, const TextSelection.collapsed(offset: 16));
final attributes = document.toDelta().toList()[2].attributes;
expect(attributes, isNotNull);
expect(attributes!.containsKey(ParchmentAttribute.block.key), isTrue);
Expand All @@ -121,8 +108,9 @@ void main() {
final document = ParchmentDocument.fromJson([
{'insert': 'Some long text\n1. \nthat continues\n'}
]);
final selection = autoformats.run(document, 17, ' ');
expect(selection, const TextSelection.collapsed(offset: 15));
final performed = autoformats.run(document, 17, ' ');
expect(performed, true);
expect(autoformats.selection, const TextSelection.collapsed(offset: 15));
final attributes = document.toDelta().toList()[1].attributes;
expect(attributes, isNotNull);
expect(attributes!.containsKey(ParchmentAttribute.block.key), isTrue);
Expand All @@ -134,8 +122,9 @@ void main() {
final document = ParchmentDocument.fromJson([
{'insert': 'Some long text\n```\nthat continues\n'}
]);
final selection = autoformats.run(document, 17, '`');
expect(selection, const TextSelection.collapsed(offset: 15));
final performed = autoformats.run(document, 17, '`');
expect(performed, true);
expect(autoformats.selection, const TextSelection.collapsed(offset: 15));
final attributes = document.toDelta().toList()[1].attributes;
expect(attributes, isNotNull);
expect(attributes!.containsKey(ParchmentAttribute.block.key), isTrue);
Expand All @@ -147,8 +136,8 @@ void main() {
final document = ParchmentDocument.fromJson([
{'insert': 'Some long text\n* \nthat continues\n'}
]);
final selection = autoformats.run(document, 16, 'p');
expect(selection, null);
final performed = autoformats.run(document, 16, 'p');
expect(performed, false);
expect(autoformats.hasActiveSuggestion, isFalse);
});

Expand Down Expand Up @@ -180,8 +169,9 @@ void main() {
final document = ParchmentDocument.fromJson([
{'insert': 'some ltr text\nש\n'}
]);
final selection = autoformats.run(document, 14, 'ש');
expect(selection, isNull);
final performed = autoformats.run(document, 14, 'ש');
expect(performed, true);
expect(autoformats.selection, isNull);
final attributes = document.toDelta().toList()[1].attributes;
expect(attributes, isNotNull);
expect(attributes!.containsKey(ParchmentAttribute.direction.key), isTrue);
Expand Down
18 changes: 18 additions & 0 deletions packages/fleather/test/widgets/controller_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,24 @@ void main() {
expect(controller.selection, selection);
});

test('History undo of link detection', () {
fakeAsync((async) {
const text = 'Some link https://fleather-editor.github.io';
const selection = TextSelection.collapsed(offset: text.length);
controller.replaceText(0, 0, text,
selection:
const TextSelection.collapsed(offset: text.length - 1));
async.flushTimers();
controller.replaceText(text.length, 0, ' ', selection: selection);
async.flushTimers();
controller.undo();
expect(controller.document.toDelta().length, 1);
expect(controller.document.toDelta()[0].data,
'Some link https://fleather-editor.github.io\n');
expect(controller.document.toDelta()[0].attributes, isNull);
});
});

test('Undo link detection', () {
const text = 'Some link https://fleather-editor.github.io';
const selection = TextSelection.collapsed(offset: text.length);
Expand Down

0 comments on commit 69317ce

Please sign in to comment.