diff --git a/lib/code_forge/code_area.dart b/lib/code_forge/code_area.dart index 83834a6..6c45b1c 100644 --- a/lib/code_forge/code_area.dart +++ b/lib/code_forge/code_area.dart @@ -1036,19 +1036,9 @@ class _CodeForgeState extends State with TickerProviderStateMixin { void didUpdateWidget(covariant CodeForge oldWidget) { super.didUpdateWidget(oldWidget); - final oldTabSize = oldWidget.tabSize ?? (oldWidget.useSpaceAsTab ? 2 : 1); final newTabSize = widget.tabSize ?? (widget.useSpaceAsTab ? 2 : 1); - final newUseSpaceAsTab = widget.useSpaceAsTab; - - if (oldWidget._tabSize != widget._tabSize || - oldWidget.useSpaceAsTab != widget.useSpaceAsTab) { - _controller.tabSize = newTabSize; - _controller.useSpaceAsTab = newUseSpaceAsTab; - _controller.reindentDocument( - oldTabSize: oldTabSize, - newTabSize: newTabSize, - ); - } + _controller.useSpaceAsTab = widget.useSpaceAsTab; + _controller.tabSize = newTabSize; } @override diff --git a/lib/code_forge/controller.dart b/lib/code_forge/controller.dart index 529d4a9..433e03f 100644 --- a/lib/code_forge/controller.dart +++ b/lib/code_forge/controller.dart @@ -4076,8 +4076,12 @@ class CodeForgeController implements DeltaTextInputClient { notifyListeners(); } - void reindentDocument({required int oldTabSize, required int newTabSize}) { + // Handel Tab Size + void reindentDocument({required int newTabSize}) { + final oldTabSize = detectIndentation(); + if (oldTabSize <= 0 || newTabSize <= 0 || oldTabSize == newTabSize) { + tabSize = newTabSize; return; } @@ -4092,91 +4096,141 @@ class CodeForgeController implements DeltaTextInputClient { final newLines = []; for (final line in originalLines) { - final leadingWhitespace = - RegExp(r"^[ \t]+", multiLine: true).stringMatch(line) ?? ""; + final match = RegExp(r"^[ \t]+").firstMatch(line); + if (match == null) { + newLines.add(line); + continue; + } + + final leadingWhitespace = match.group(0)!; final content = line.substring(leadingWhitespace.length); - final indentLength = leadingWhitespace - .replaceAll("\t", " " * oldTabSize) - .length; - final indentLevels = indentLength ~/ oldTabSize; - final newIndentLength = indentLevels * newTabSize; - newLines.add("${" " * newIndentLength}$content"); + + int visualSpaces = 0; + for (int i = 0; i < leadingWhitespace.length; i++) { + if (leadingWhitespace[i] == '\t') { + visualSpaces += oldTabSize - (visualSpaces % oldTabSize); + } else { + visualSpaces += 1; + } + } + + final indentLevels = visualSpaces ~/ oldTabSize; + final remainder = visualSpaces % oldTabSize; + final newIndentLength = (indentLevels * newTabSize) + remainder; + + newLines.add("${' ' * newIndentLength}$content"); } final newText = newLines.join("\n"); - if (newText == originalText) { - tabSize = newTabSize; - return; - } + tabSize = newTabSize; + + if (newText == originalText) return; final newLineStarts = _lineStarts(newText); final originalLineStarts = _lineStarts(originalText); - final newSelection = TextSelection( + final mappedSelection = TextSelection( baseOffset: _mapOffsetForReindent( - originalOffset: originalSelection.start, + originalOffset: originalSelection.baseOffset, originalText: originalText, newText: newText, - oldTabSize: oldTabSize, - newTabSize: newTabSize, originalLineStarts: originalLineStarts, newLineStarts: newLineStarts, ), extentOffset: _mapOffsetForReindent( - originalOffset: originalSelection.end, + originalOffset: originalSelection.extentOffset, originalText: originalText, newText: newText, - oldTabSize: oldTabSize, - newTabSize: newTabSize, originalLineStarts: originalLineStarts, newLineStarts: newLineStarts, ), ); text = newText; - selection = newSelection; - tabSize = newTabSize; + selection = mappedSelection; + } + + // Detect File Tab Size + int detectIndentation() { + final lines = text.split("\n"); + final diffCount = {}; + int previousIndent = 0; + + for (final line in lines) { + if (line.trim().isEmpty) continue; + + final match = RegExp(r"^( +)\S").firstMatch(line); + final currentIndent = match?.group(1)?.length ?? 0; + + final diff = (currentIndent - previousIndent).abs(); + if (diff >= 1 && diff <= 32) { + diffCount[diff] = (diffCount[diff] ?? 0) + 1; + } + + previousIndent = currentIndent; + } + + if (diffCount.isEmpty) return tabSize > 0 ? tabSize : 2; + + int bestIndent = tabSize; + int maxOccurrences = 0; + + diffCount.forEach((indent, count) { + if (count > maxOccurrences) { + maxOccurrences = count; + bestIndent = indent; + } + }); + + return bestIndent; } int _mapOffsetForReindent({ required int originalOffset, required String originalText, required String newText, - required int oldTabSize, - required int newTabSize, required List originalLineStarts, required List newLineStarts, }) { if (originalOffset <= 0) return 0; + if (originalOffset >= originalText.length) return newText.length; final lineIndex = _findLineIndex(originalLineStarts, originalOffset); - final lineStart = originalLineStarts[lineIndex]; - final lineText = originalText.substring( - lineStart, - lineIndex + 1 < originalLineStarts.length - ? originalLineStarts[lineIndex + 1] - 1 - : originalText.length, + final originalLineStart = originalLineStarts[lineIndex]; + final originalNextLineStart = lineIndex + 1 < originalLineStarts.length + ? originalLineStarts[lineIndex + 1] + : originalText.length; + + final originalLineText = originalText.substring( + originalLineStart, + originalNextLineStart, ); - final leadingWhitespace = - RegExp(r"^[ \t]+", multiLine: true).stringMatch(lineText) ?? ""; - final oldIndentLength = leadingWhitespace - .replaceAll("\t", " " * oldTabSize) - .length; - final newIndentLength = (oldIndentLength ~/ oldTabSize) * newTabSize; - final column = originalOffset - lineStart; - - if (newLineStarts.length <= lineIndex) { - return newText.length; - } - - final newLineStart = newLineStarts[lineIndex]; - final newColumn = column <= oldIndentLength - ? (oldIndentLength == 0 - ? 0 - : ((column * newIndentLength) / oldIndentLength).round()) - : newIndentLength + (column - oldIndentLength); - - return newLineStart + newColumn.clamp(0, newText.length - newLineStart); + final originalWhitespaceLen = + RegExp(r"^[ \t]+").firstMatch(originalLineText)?.group(0)?.length ?? 0; + + final newLineStart = lineIndex < newLineStarts.length + ? newLineStarts[lineIndex] + : newText.length; + final newNextLineStart = lineIndex + 1 < newLineStarts.length + ? newLineStarts[lineIndex + 1] + : newText.length; + + final newLineText = newText.substring(newLineStart, newNextLineStart); + final newWhitespaceLen = + RegExp(r"^[ \t]+").firstMatch(newLineText)?.group(0)?.length ?? 0; + + final column = originalOffset - originalLineStart; + + int newColumn; + if (column <= originalWhitespaceLen) { + newColumn = originalWhitespaceLen == 0 + ? 0 + : ((column / originalWhitespaceLen) * newWhitespaceLen).round(); + } else { + newColumn = newWhitespaceLen + (column - originalWhitespaceLen); + } + + return (newLineStart + newColumn).clamp(0, newText.length); } List _lineStarts(String value) {