From ab8387c522d3669c1d5a601a49f0cc898780f843 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Thu, 6 Nov 2025 11:28:37 -0500 Subject: [PATCH] better documentation --- .../controllers/choreographer.dart | 2 +- .../choreographer_state_extension.dart | 35 ------ .../controllers/igc_controller.dart | 14 +-- .../controllers/pangea_text_controller.dart | 2 +- .../models/igc_text_data_model.dart | 109 ++++++++++-------- 5 files changed, 67 insertions(+), 95 deletions(-) diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 8a61be964..9441b55a1 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -407,7 +407,7 @@ class Choreographer extends ChangeNotifier { void clearMatches(Object error) { MatrixState.pAnyState.closeAllOverlays(); - igcController.clearMatches(); + igcController.clearIGCMatches(); errorService.setError(ChoreoError(raw: error)); } diff --git a/lib/pangea/choreographer/controllers/extensions/choreographer_state_extension.dart b/lib/pangea/choreographer/controllers/extensions/choreographer_state_extension.dart index fa8258ab6..1e27c486a 100644 --- a/lib/pangea/choreographer/controllers/extensions/choreographer_state_extension.dart +++ b/lib/pangea/choreographer/controllers/extensions/choreographer_state_extension.dart @@ -1,5 +1,4 @@ import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; -import 'package:fluffychat/pangea/choreographer/controllers/extensions/choregrapher_user_settings_extension.dart'; import 'package:fluffychat/pangea/choreographer/enums/assistance_state_enum.dart'; import 'package:fluffychat/pangea/choreographer/enums/choreo_mode.dart'; @@ -28,38 +27,4 @@ extension ChoregrapherUserSettingsExtension on Choreographer { if (!igcController.hasIGCTextData) return AssistanceState.notFetched; return AssistanceState.complete; } - - bool get canSendMessage { - // if there's an error, let them send. we don't want to block them from sending in this case - if (errorService.isError || - l2Lang == null || - l1Lang == null || - timesClicked > 1) { - return true; - } - - // if they're in IT mode, don't let them send - if (itEnabled && isRunningIT) return false; - - // if they've turned off IGC then let them send the message when they want - if (!isAutoIGCEnabled) return true; - - // if we're in the middle of fetching results, don't let them send - if (isFetching.value) return false; - - // they're supposed to run IGC but haven't yet, don't let them send - if (!igcController.hasIGCTextData) { - return itController.dismissed; - } - - // if they have relevant matches, don't let them send - final hasITMatches = igcController.hasOpenITMatches; - final hasIGCMatches = igcController.hasOpenIGCMatches; - if ((itEnabled && hasITMatches) || (igcEnabled && hasIGCMatches)) { - return false; - } - - // otherwise, let them send - return true; - } } diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index eb9b4af38..6be8103ae 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -23,10 +23,8 @@ class IgcController { String? get currentText => _igcTextData?.currentText; bool get hasOpenMatches => _igcTextData?.hasOpenMatches == true; - bool get hasOpenITMatches => _igcTextData?.hasOpenITMatches == true; - bool get hasOpenIGCMatches => _igcTextData?.hasOpenIGCMatches == true; - PangeaMatchState? get openMatch => _igcTextData?.openMatch; + PangeaMatchState? get currentlyOpenMatch => _igcTextData?.currentlyOpenMatch; PangeaMatchState? get firstOpenMatch => _igcTextData?.firstOpenMatch; List? get openMatches => _igcTextData?.openMatches; List? get recentNormalizationMatches => @@ -43,10 +41,10 @@ class IgcController { MatrixState.pAnyState.closeAllOverlays(); } - void clearMatches() => _igcTextData?.clearMatches(); + void clearIGCMatches() => _igcTextData?.clearIGCMatches(); PangeaMatchState? getMatchByOffset(int offset) => - _igcTextData?.getMatchByOffset(offset); + _igcTextData?.getOpenMatchByOffset(offset); PangeaMatch acceptReplacement( PangeaMatchState match, @@ -55,7 +53,7 @@ class IgcController { if (_igcTextData == null) { throw "acceptReplacement called with null igcTextData"; } - final updateMatch = _igcTextData!.acceptReplacement(match, status); + final updateMatch = _igcTextData!.makeAcceptedMatchUpdates(match, status); return updateMatch; } @@ -64,14 +62,14 @@ class IgcController { if (_igcTextData == null) { throw "should not be in onIgnoreMatch with null igcTextData"; } - return _igcTextData!.ignoreReplacement(match); + return _igcTextData!.makeIgnoredMatchUpdates(match); } void undoReplacement(PangeaMatchState match) { if (_igcTextData == null) { throw "undoReplacement called with null igcTextData"; } - _igcTextData!.undoReplacement(match); + _igcTextData!.removeMatchUpdates(match); } Future getIGCTextData( diff --git a/lib/pangea/choreographer/controllers/pangea_text_controller.dart b/lib/pangea/choreographer/controllers/pangea_text_controller.dart index 7f3fb5eb0..1e2991bd3 100644 --- a/lib/pangea/choreographer/controllers/pangea_text_controller.dart +++ b/lib/pangea/choreographer/controllers/pangea_text_controller.dart @@ -201,7 +201,7 @@ class PangeaTextController extends TextEditingController { } final openMatch = - choreographer.igcController.openMatch?.updatedMatch.match; + choreographer.igcController.currentlyOpenMatch?.updatedMatch.match; final style = _textStyle( match.updatedMatch, defaultStyle, diff --git a/lib/pangea/choreographer/models/igc_text_data_model.dart b/lib/pangea/choreographer/models/igc_text_data_model.dart index 52dfdbaed..03933c9f6 100644 --- a/lib/pangea/choreographer/models/igc_text_data_model.dart +++ b/lib/pangea/choreographer/models/igc_text_data_model.dart @@ -9,45 +9,47 @@ import 'package:fluffychat/pangea/choreographer/models/span_data.dart'; import 'package:fluffychat/pangea/choreographer/repo/igc_repo.dart'; import 'package:fluffychat/widgets/matrix.dart'; +/// A model representing the mutable text and match state used by +/// Interactive Grammar Correction (IGC). +/// +/// This class tracks the original input text, the current working text, +/// and the states of open and closed grammar matches as the user accepts, +/// ignores, or reverses suggested corrections. class IGCTextData { + /// The user's original text before any corrections or replacements. final String _originalInput; + + /// The full list of detected matches from the initial grammar analysis. final List _matches; - String _currentText; + /// Matches currently pending user action (neither accepted nor ignored). final List _openMatches = []; + + /// Matches that have been resolved by either accepting or ignoring them. final List _closedMatches = []; + /// The current text content after applying all accepted corrections. + String _currentText; + IGCTextData({ required String originalInput, required List matches, }) : _currentText = originalInput, _originalInput = originalInput, _matches = matches { - _openMatches.addAll( - matches - .where((match) => match.status == PangeaMatchStatus.open) - .map((match) { - return PangeaMatchState( - match: match.match, - status: match.status, - original: match, - ); - }), - ); - - _closedMatches.addAll( - matches - .where((match) => match.status != PangeaMatchStatus.open) - .map((match) { - return PangeaMatchState( - match: match.match, - status: match.status, - original: match, - ); - }), - ); - - _filterIgnoredMatches(); + for (final match in matches) { + final matchState = PangeaMatchState( + match: match.match, + status: PangeaMatchStatus.open, + original: match, + ); + if (match.status == PangeaMatchStatus.open) { + _openMatches.add(matchState); + } else { + _closedMatches.add(matchState); + } + } + _filterPreviouslyIgnoredMatches(); } Map toJson() => { @@ -59,35 +61,33 @@ class IGCTextData { List get openMatches => _openMatches; - List get closedMatches => _closedMatches; + bool get hasOpenMatches => _openMatches.isNotEmpty; + PangeaMatchState? get firstOpenMatch => _openMatches.firstOrNull; + + /// Normalization matches that have been closed in the last choreo step(s). + /// Used to display automatic corrections made by the IGC system. List get recentNormalizationMatches => - closedMatches.reversed + _closedMatches.reversed .takeWhile( (m) => m.updatedMatch.status == PangeaMatchStatus.automatic, ) .toList(); + /// Convenience getter for open normalization error matches. + /// Used for auto-correction of normalization errors. List get openNormalizationMatches => _openMatches .where((match) => match.updatedMatch.match.isNormalizationError()) .toList(); - bool get hasOpenMatches => _openMatches.isNotEmpty; - - bool get hasOpenITMatches => - _openMatches.any((match) => match.updatedMatch.isITStart); - - bool get hasOpenIGCMatches => - _openMatches.any((match) => !match.updatedMatch.isITStart); - - PangeaMatchState? get firstOpenMatch => _openMatches.firstOrNull; - - PangeaMatchState? getMatchByOffset(int offset) => + /// Returns the open match that contains the given text offset, if any. + PangeaMatchState? getOpenMatchByOffset(int offset) => _openMatches.firstWhereOrNull( (match) => match.updatedMatch.match.isOffsetInMatchSpan(offset), ); - PangeaMatchState? get openMatch { + /// Returns the match whose span card overlay is currently open, if any. + PangeaMatchState? get currentlyOpenMatch { final RegExp pattern = RegExp(r'span_card_overlay_.+'); final String? matchingKeys = MatrixState.pAnyState.getMatchingOverlayKeys(pattern).firstOrNull; @@ -105,19 +105,23 @@ class IGCTextData { ); } - void clearMatches() { + /// Clears all matches from the IGC text data. + /// Call on error that make continuing IGC processing invalid. + void clearIGCMatches() { _openMatches.clear(); _closedMatches.clear(); } - void _filterIgnoredMatches() { + /// Filters out previously ignored matches from the open matches list. + void _filterPreviouslyIgnoredMatches() { for (final match in _openMatches) { if (IgcRepo.isIgnored(match.updatedMatch)) { - ignoreReplacement(match); + makeIgnoredMatchUpdates(match); } } } + /// Replaces the span data for a given match. void setSpanData(PangeaMatchState match, SpanData spanData) { final openMatch = _openMatches.firstWhereOrNull( (m) => m.originalMatch == match.originalMatch, @@ -128,7 +132,9 @@ class IGCTextData { _openMatches.add(match); } - PangeaMatch acceptReplacement( + /// Accepts the specified [match] and updates both the open/closed match lists + /// and the [_currentText] to include the chosen replacement text. + PangeaMatch makeAcceptedMatchUpdates( PangeaMatchState match, PangeaMatchStatus status, ) { @@ -158,11 +164,12 @@ class IGCTextData { return match.updatedMatch; } - PangeaMatch ignoreReplacement(PangeaMatchState match) { + /// Ignores a given match and updates the IGC text data state accordingly. + PangeaMatch makeIgnoredMatchUpdates(PangeaMatchState match) { final openMatch = _openMatches.firstWhere( (m) => m.originalMatch == match.originalMatch, orElse: () => throw Exception( - 'No open match found for ignoreReplacement', + 'No open match found for makeIgnoredMatchUpdates', ), ); @@ -172,21 +179,22 @@ class IGCTextData { return match.updatedMatch; } - void undoReplacement(PangeaMatchState match) { + /// Removes a given match from the closed match history and undoes the + /// changes to igc text data state caused by accepting the match. + void removeMatchUpdates(PangeaMatchState match) { final closedMatch = _closedMatches.firstWhere( (m) => m.originalMatch == match.originalMatch, orElse: () => throw Exception( - 'No closed match found for undoReplacement', + 'No closed match found for removeMatchUpdates', ), ); _closedMatches.remove(closedMatch); - final choice = match.updatedMatch.match.selectedChoice?.value; if (choice == null) { throw Exception( - "match.match.selectedChoice is null in undoReplacement", + "match.match.selectedChoice is null in removeMatchUpdates", ); } @@ -204,6 +212,7 @@ class IGCTextData { ); } + /// Runs a text replacement and updates match offsets / current text accordingly. void _runReplacement( int offset, int length,