@@ -3883,11 +3883,11 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
38833883 late double _lineHeight;
38843884 final _dtap = DoubleTapGestureRecognizer ();
38853885 final _onetap = TapGestureRecognizer ();
3886- late final double _gutterPadding;
3886+ late double _gutterPadding;
38873887 late final Paint _caretPainter;
38883888 late final Paint _bracketHighlightPainter;
38893889 late ui.ParagraphStyle _paragraphStyle;
3890- late final ui.TextStyle _uiTextStyle;
3890+ late ui.TextStyle _uiTextStyle;
38913891 late SyntaxHighlighter _syntaxHighlighter;
38923892 late double _gutterWidth;
38933893 TextStyle ? _ghostTextStyle;
@@ -3919,6 +3919,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
39193919 bool _showBubble = false , _draggingCHandle = false , _readOnly;
39203920 bool _isDeferringLayout = false , _hasCachedHeight = false ;
39213921 bool _isCachedHeightExact = false ;
3922+ bool _caretSyncAfterLayoutScheduled = false ;
39223923 TextDirection _textDirection;
39233924 Map <int , FoldRange >? _lastLspFoldRanges;
39243925 Rect ? _startHandleRect, _endHandleRect, _normalHandle;
@@ -4516,9 +4517,42 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
45164517 _textStyle = style;
45174518
45184519 final fontSize = style? .fontSize ?? 14.0 ;
4520+ final fontFamily = style? .fontFamily;
4521+ final color = style? .color ?? _editorTheme['root' ]? .color ?? Colors .black;
45194522 final lineHeightMultiplier = style? .height ?? 1.2 ;
45204523
45214524 _lineHeight = fontSize * lineHeightMultiplier;
4525+ _paragraphStyle = ui.ParagraphStyle (
4526+ fontFamily: fontFamily,
4527+ fontSize: fontSize,
4528+ height: lineHeightMultiplier,
4529+ textDirection: _textDirection,
4530+ textAlign: ui.TextAlign .start,
4531+ );
4532+ _uiTextStyle = ui.TextStyle (
4533+ color: color,
4534+ fontSize: fontSize,
4535+ fontFamily: fontFamily,
4536+ );
4537+
4538+ _gutterPadding = fontSize;
4539+ if (_enableGutter) {
4540+ if (_gutterStyle.gutterWidth != null ) {
4541+ _gutterWidth = _gutterStyle.gutterWidth! ;
4542+ } else {
4543+ final digits = controller.lineCount.toString ().length;
4544+ final digitWidth = digits * _gutterPadding * 0.6 ;
4545+ final foldIconSpace = _enableFolding ? fontSize + 4 : 0 ;
4546+ _gutterWidth = digitWidth + foldIconSpace + _gutterPadding;
4547+ }
4548+ } else {
4549+ _gutterWidth = 0 ;
4550+ }
4551+
4552+ if (_selectionStyle.cursorColor == null ) {
4553+ _caretPainter.color = color;
4554+ }
4555+ _bracketHighlightPainter.color = color;
45224556
45234557 try {
45244558 _syntaxHighlighter.dispose ();
@@ -4543,6 +4577,10 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
45434577 _lineOffsetCache.clear ();
45444578 _caretInfoCache.clear ();
45454579 _lineIndentCache.clear ();
4580+ _longLineWidth = 0.0 ;
4581+ _cachedRtlContentWidth = 0.0 ;
4582+ _hasCachedHeight = false ;
4583+ _isCachedHeightExact = false ;
45464584
45474585 markNeedsLayout ();
45484586 markNeedsPaint ();
@@ -4703,8 +4741,19 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
47034741 }
47044742 }
47054743
4706- void _deferLayout () {
4744+ void _deferLayout ({ required int lineDelta} ) {
47074745 _layoutDebounceTimer? .cancel ();
4746+
4747+ // Apply layout immediately for small incremental edits (like pressing
4748+ // Enter) so caret position and gutter stay visually in sync.
4749+ if (lineDelta.abs () <= 4 ) {
4750+ _isDeferringLayout = false ;
4751+ markNeedsLayout ();
4752+ markNeedsPaint ();
4753+ _scheduleCaretSyncAfterLayout ();
4754+ return ;
4755+ }
4756+
47084757 _isDeferringLayout = true ;
47094758
47104759 if (_hasCachedHeight) {
@@ -4719,9 +4768,24 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
47194768 }
47204769 markNeedsPaint ();
47214770
4722- _layoutDebounceTimer = Timer (const Duration (milliseconds: 100 ), () {
4771+ _layoutDebounceTimer = Timer (const Duration (milliseconds: 24 ), () {
47234772 _isDeferringLayout = false ;
47244773 markNeedsLayout ();
4774+ _scheduleCaretSyncAfterLayout ();
4775+ });
4776+ }
4777+
4778+ void _scheduleCaretSyncAfterLayout () {
4779+ if (_caretSyncAfterLayoutScheduled || _isFoldToggleInProgress) return ;
4780+ _caretSyncAfterLayoutScheduled = true ;
4781+
4782+ WidgetsBinding .instance.addPostFrameCallback ((_) {
4783+ _caretSyncAfterLayoutScheduled = false ;
4784+ if (! attached) return ;
4785+ if (! vscrollController.hasClients || ! hscrollController.hasClients) {
4786+ return ;
4787+ }
4788+ _ensureCaretVisible ();
47254789 });
47264790 }
47274791
@@ -4901,6 +4965,8 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
49014965 _indentGuideCache.clear ();
49024966 _indentEndLineCache.clear ();
49034967 _lineIndentCache.clear ();
4968+ _caretInfoCache.clear ();
4969+ _cachedCaretOffset = - 1 ;
49044970 }
49054971
49064972 final dirtyRange = controller.dirtyRegion;
@@ -4952,6 +5018,9 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
49525018 );
49535019
49545020 _cachedLineCount = newLineCount;
5021+ _lineOffsetCache.clear ();
5022+ _caretInfoCache.clear ();
5023+ _cachedCaretOffset = - 1 ;
49555024
49565025 final startInvalidation = insertionLine > 0 ? insertionLine - 1 : 0 ;
49575026 for (int i = startInvalidation; i <= newLineCount; i++ ) {
@@ -5023,7 +5092,7 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
50235092 _invalidateFoldRanges (startInvalidation);
50245093 }
50255094
5026- _deferLayout ();
5095+ _deferLayout (lineDelta : lineDelta );
50275096 } else if (affectedLine != null ) {
50285097 _foldRanges.remove (affectedLine);
50295098 if (affectedLine > 0 ) {
@@ -5712,84 +5781,10 @@ class _CodeFieldRenderer extends RenderBox implements MouseTrackerAnnotation {
57125781
57135782 final hasActiveFolds = _hasActiveFolds;
57145783
5715- if (controller.isBufferActive) {
5716- final lineIndex = controller.bufferLineIndex! ;
5717- final columnIndex = controller.bufferCursorColumn;
5718- final lineY = _getLineYOffset (lineIndex, hasActiveFolds);
5719- final lineText = controller.bufferLineText ?? '' ;
5720-
5721- final contentWidth =
5722- size.width - _gutterWidth - (innerPadding? .horizontal ?? 0 );
5723- final paragraphWidth = lineWrap
5724- ? _wrapWidth
5725- : (isRTL ? max (contentWidth * 3 , 10000.0 ) : null );
5726-
5727- final para = _buildHighlightedParagraph (
5728- lineIndex,
5729- lineText,
5730- width: paragraphWidth,
5731- );
5732- final clampedCol = columnIndex.clamp (0 , lineText.length);
5733-
5734- double caretX = 0.0 ;
5735- double caretYInLine = 0.0 ;
5736-
5737- if (isRTL) {
5738- final paragraphOffset = lineWrap
5739- ? 0.0
5740- : (contentWidth - (paragraphWidth ?? 0 ));
5741-
5742- if (lineText.isEmpty) {
5743- caretX = contentWidth;
5744- } else if (clampedCol == 0 ) {
5745- final boxes = para.getBoxesForRange (0 , 1 );
5746- if (boxes.isNotEmpty) {
5747- caretX = boxes.first.right + paragraphOffset;
5748- caretYInLine = boxes.first.top;
5749- } else {
5750- caretX = contentWidth;
5751- }
5752- } else if (clampedCol >= lineText.length) {
5753- final boxes = para.getBoxesForRange (
5754- lineText.length - 1 ,
5755- lineText.length,
5756- );
5757- if (boxes.isNotEmpty) {
5758- caretX = boxes.first.left + paragraphOffset;
5759- caretYInLine = boxes.first.top;
5760- } else {
5761- caretX = paragraphOffset;
5762- }
5763- } else {
5764- final boxes = para.getBoxesForRange (clampedCol - 1 , clampedCol);
5765- if (boxes.isNotEmpty) {
5766- caretX = boxes.first.left + paragraphOffset;
5767- caretYInLine = boxes.first.top;
5768- } else {
5769- caretX = contentWidth;
5770- }
5771- }
5772- } else {
5773- if (lineText.isEmpty) {
5774- caretX = 0 ;
5775- } else if (clampedCol > 0 ) {
5776- final boxes = para.getBoxesForRange (clampedCol - 1 , clampedCol);
5777- if (boxes.isNotEmpty) {
5778- caretX = boxes.first.right;
5779- caretYInLine = boxes.first.top;
5780- }
5781- }
5782- }
5783-
5784- final ghostOffset = _getTotalVirtualOffset (lineIndex);
5785-
5786- return (
5787- lineIndex: lineIndex,
5788- columnIndex: columnIndex,
5789- offset: Offset (caretX, lineY + caretYInLine + ghostOffset),
5790- height: _lineHeight,
5791- );
5792- }
5784+ // For buffered edits that include newlines, using a single multi-line
5785+ // buffer paragraph can place the caret one visual line behind. Keep caret
5786+ // mapping aligned with gutter by always resolving logical line/column
5787+ // through offset-based controller helpers.
57935788
57945789 if (! isRTL && _caretInfoCache.containsKey (cursorOffset)) {
57955790 return _caretInfoCache[cursorOffset]! ;
0 commit comments