chore: combine igcdatamodel and igcdatastate

This commit is contained in:
ggurdin 2025-11-06 10:49:01 -05:00
parent d35a978817
commit 14b89594c2
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
2 changed files with 200 additions and 256 deletions

View file

@ -1,66 +1,239 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/choreographer/enums/pangea_match_status.dart';
import 'package:fluffychat/pangea/choreographer/models/igc_text_state.dart';
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
import 'package:fluffychat/pangea/choreographer/models/pangea_match_state.dart';
import 'package:fluffychat/pangea/choreographer/models/span_data.dart';
import 'package:fluffychat/pangea/choreographer/repo/igc_repo.dart';
import 'package:fluffychat/widgets/matrix.dart';
class IGCTextData {
final String _originalInput;
final List<PangeaMatch> _matches;
final IGCTextState _state;
String _currentText;
final List<PangeaMatchState> _openMatches = [];
final List<PangeaMatchState> _closedMatches = [];
IGCTextData({
required String originalInput,
required List<PangeaMatch> matches,
}) : _state = IGCTextState(
currentText: originalInput,
matches: List<PangeaMatch>.from(matches),
),
}) : _currentText = originalInput,
_originalInput = originalInput,
_matches = matches;
_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();
}
Map<String, dynamic> toJson() => {
"original_input": _originalInput,
"matches": _matches.map((e) => e.toJson()).toList(),
};
bool get hasOpenMatches => _state.hasOpenMatches;
String get currentText => _currentText;
bool get hasOpenITMatches => _state.hasOpenITMatches;
List<PangeaMatchState> get openMatches => _openMatches;
bool get hasOpenIGCMatches => _state.hasOpenIGCMatches;
String get currentText => _state.currentText;
List<PangeaMatchState> get openMatches => _state.openMatches;
List<PangeaMatchState> get closedMatches => _closedMatches;
List<PangeaMatchState> get recentNormalizationMatches =>
_state.recentNormalizationMatches;
closedMatches.reversed
.takeWhile(
(m) => m.updatedMatch.status == PangeaMatchStatus.automatic,
)
.toList();
PangeaMatchState? get firstOpenMatch => _state.firstOpenMatch;
List<PangeaMatchState> get openNormalizationMatches => _openMatches
.where((match) => match.updatedMatch.match.isNormalizationError())
.toList();
PangeaMatchState? get openMatch => _state.openMatch;
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) =>
_state.getMatchByOffset(offset);
_openMatches.firstWhereOrNull(
(match) => match.updatedMatch.match.isOffsetInMatchSpan(offset),
);
List<PangeaMatchState> get openNormalizationMatches =>
_state.openNormalizationMatches;
PangeaMatchState? get openMatch {
final RegExp pattern = RegExp(r'span_card_overlay_.+');
final String? matchingKeys =
MatrixState.pAnyState.getMatchingOverlayKeys(pattern).firstOrNull;
if (matchingKeys == null) return null;
void clearMatches() => _state.clearMatches();
final parts = matchingKeys.split("_");
if (parts.length != 5) return null;
final offset = int.tryParse(parts[3]);
final length = int.tryParse(parts[4]);
if (offset == null || length == null) return null;
return _openMatches.firstWhereOrNull(
(match) =>
match.updatedMatch.match.offset == offset &&
match.updatedMatch.match.length == length,
);
}
void clearMatches() {
_openMatches.clear();
_closedMatches.clear();
}
void _filterIgnoredMatches() {
for (final match in _openMatches) {
if (IgcRepo.isIgnored(match.updatedMatch)) {
ignoreReplacement(match);
}
}
}
void setSpanData(PangeaMatchState match, SpanData spanData) {
_state.setSpanData(match, spanData);
final openMatch = _openMatches.firstWhereOrNull(
(m) => m.originalMatch == match.originalMatch,
);
match.setMatch(spanData);
_openMatches.remove(openMatch);
_openMatches.add(match);
}
PangeaMatch acceptReplacement(
PangeaMatchState match,
PangeaMatchStatus status,
) =>
_state.acceptReplacement(match, status);
) {
final openMatch = _openMatches.firstWhere(
(m) => m.originalMatch == match.originalMatch,
orElse: () => throw Exception(
'No open match found for acceptReplacement',
),
);
PangeaMatch ignoreReplacement(PangeaMatchState match) =>
_state.ignoreReplacement(match);
if (match.updatedMatch.match.selectedChoice == null) {
throw Exception(
'acceptReplacement called with null selectedChoice',
);
}
void undoReplacement(PangeaMatchState match) => _state.undoReplacement(match);
match.setStatus(status);
_openMatches.remove(openMatch);
_closedMatches.add(match);
_runReplacement(
match.updatedMatch.match.offset,
match.updatedMatch.match.length,
match.updatedMatch.match.selectedChoice!.value,
);
return match.updatedMatch;
}
PangeaMatch ignoreReplacement(PangeaMatchState match) {
final openMatch = _openMatches.firstWhere(
(m) => m.originalMatch == match.originalMatch,
orElse: () => throw Exception(
'No open match found for ignoreReplacement',
),
);
match.setStatus(PangeaMatchStatus.ignored);
_openMatches.remove(openMatch);
_closedMatches.add(match);
return match.updatedMatch;
}
void undoReplacement(PangeaMatchState match) {
final closedMatch = _closedMatches.firstWhere(
(m) => m.originalMatch == match.originalMatch,
orElse: () => throw Exception(
'No closed match found for undoReplacement',
),
);
_closedMatches.remove(closedMatch);
final choice = match.updatedMatch.match.selectedChoice?.value;
if (choice == null) {
throw Exception(
"match.match.selectedChoice is null in undoReplacement",
);
}
final String replacement = match.originalMatch.match.fullText.characters
.getRange(
match.originalMatch.match.offset,
match.originalMatch.match.offset + match.originalMatch.match.length,
)
.toString();
_runReplacement(
match.originalMatch.match.offset,
choice.characters.length,
replacement,
);
}
void _runReplacement(
int offset,
int length,
String replacement,
) {
final start = _currentText.characters.take(offset);
final end = _currentText.characters.skip(offset + length);
final fullText = start + replacement.characters + end;
_currentText = fullText.toString();
for (int i = 0; i < _openMatches.length; i++) {
final match = _openMatches[i].updatedMatch.match;
final updatedMatch = match.copyWith(
fullText: _currentText,
offset: match.offset > offset
? match.offset + replacement.characters.length - length
: match.offset,
);
_openMatches[i].setMatch(updatedMatch);
}
for (int i = 0; i < _closedMatches.length; i++) {
final match = _closedMatches[i].updatedMatch.match;
final updatedMatch = match.copyWith(
fullText: _currentText,
offset: match.offset > offset
? match.offset + replacement.characters.length - length
: match.offset,
);
_closedMatches[i].setMatch(updatedMatch);
}
}
}

View file

@ -1,229 +0,0 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:fluffychat/pangea/choreographer/enums/pangea_match_status.dart';
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
import 'package:fluffychat/pangea/choreographer/models/pangea_match_state.dart';
import 'package:fluffychat/pangea/choreographer/models/span_data.dart';
import 'package:fluffychat/pangea/choreographer/repo/igc_repo.dart';
import 'package:fluffychat/widgets/matrix.dart';
class IGCTextState {
String _currentText;
final List<PangeaMatchState> _openMatches = [];
final List<PangeaMatchState> _closedMatches = [];
IGCTextState({
required String currentText,
required List<PangeaMatch> matches,
}) : _currentText = currentText {
_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();
}
String get currentText => _currentText;
List<PangeaMatchState> get openMatches => _openMatches;
List<PangeaMatchState> get closedMatches => _closedMatches;
List<PangeaMatchState> get recentNormalizationMatches =>
closedMatches.reversed
.takeWhile(
(m) => m.updatedMatch.status == PangeaMatchStatus.automatic,
)
.toList();
List<PangeaMatchState> 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) =>
_openMatches.firstWhereOrNull(
(match) => match.updatedMatch.match.isOffsetInMatchSpan(offset),
);
PangeaMatchState? get openMatch {
final RegExp pattern = RegExp(r'span_card_overlay_.+');
final String? matchingKeys =
MatrixState.pAnyState.getMatchingOverlayKeys(pattern).firstOrNull;
if (matchingKeys == null) return null;
final parts = matchingKeys.split("_");
if (parts.length != 5) return null;
final offset = int.tryParse(parts[3]);
final length = int.tryParse(parts[4]);
if (offset == null || length == null) return null;
return _openMatches.firstWhereOrNull(
(match) =>
match.updatedMatch.match.offset == offset &&
match.updatedMatch.match.length == length,
);
}
void clearMatches() {
_openMatches.clear();
_closedMatches.clear();
}
void _filterIgnoredMatches() {
for (final match in _openMatches) {
if (IgcRepo.isIgnored(match.updatedMatch)) {
ignoreReplacement(match);
}
}
}
void setSpanData(PangeaMatchState match, SpanData spanData) {
final openMatch = _openMatches.firstWhereOrNull(
(m) => m.originalMatch == match.originalMatch,
);
match.setMatch(spanData);
_openMatches.remove(openMatch);
_openMatches.add(match);
}
PangeaMatch acceptReplacement(
PangeaMatchState match,
PangeaMatchStatus status,
) {
final openMatch = _openMatches.firstWhere(
(m) => m.originalMatch == match.originalMatch,
orElse: () => throw Exception(
'No open match found for acceptReplacement',
),
);
if (match.updatedMatch.match.selectedChoice == null) {
throw Exception(
'acceptReplacement called with null selectedChoice',
);
}
match.setStatus(status);
_openMatches.remove(openMatch);
_closedMatches.add(match);
_runReplacement(
match.updatedMatch.match.offset,
match.updatedMatch.match.length,
match.updatedMatch.match.selectedChoice!.value,
);
return match.updatedMatch;
}
PangeaMatch ignoreReplacement(PangeaMatchState match) {
final openMatch = _openMatches.firstWhere(
(m) => m.originalMatch == match.originalMatch,
orElse: () => throw Exception(
'No open match found for ignoreReplacement',
),
);
match.setStatus(PangeaMatchStatus.ignored);
_openMatches.remove(openMatch);
_closedMatches.add(match);
return match.updatedMatch;
}
void undoReplacement(PangeaMatchState match) {
final closedMatch = _closedMatches.firstWhere(
(m) => m.originalMatch == match.originalMatch,
orElse: () => throw Exception(
'No closed match found for undoReplacement',
),
);
_closedMatches.remove(closedMatch);
final choice = match.updatedMatch.match.selectedChoice?.value;
if (choice == null) {
throw Exception(
"match.match.selectedChoice is null in undoReplacement",
);
}
final String replacement = match.originalMatch.match.fullText.characters
.getRange(
match.originalMatch.match.offset,
match.originalMatch.match.offset + match.originalMatch.match.length,
)
.toString();
_runReplacement(
match.originalMatch.match.offset,
choice.characters.length,
replacement,
);
}
void _runReplacement(
int offset,
int length,
String replacement,
) {
final start = _currentText.characters.take(offset);
final end = _currentText.characters.skip(offset + length);
final fullText = start + replacement.characters + end;
_currentText = fullText.toString();
for (int i = 0; i < _openMatches.length; i++) {
final match = _openMatches[i].updatedMatch.match;
final updatedMatch = match.copyWith(
fullText: _currentText,
offset: match.offset > offset
? match.offset + replacement.characters.length - length
: match.offset,
);
_openMatches[i].setMatch(updatedMatch);
}
for (int i = 0; i < _closedMatches.length; i++) {
final match = _closedMatches[i].updatedMatch.match;
final updatedMatch = match.copyWith(
fullText: _currentText,
offset: match.offset > offset
? match.offset + replacement.characters.length - length
: match.offset,
);
_closedMatches[i].setMatch(updatedMatch);
}
}
}