Skip to content

Commit 698ac2c

Browse files
committed
Bump version to 9.9.0 and fix caret layout sync
1 parent 7385222 commit 698ac2c

6 files changed

Lines changed: 91 additions & 98 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,4 +381,8 @@ This release establishes **CodeForge** as a powerful, production-ready code edit
381381
- Enhanced large text handling.
382382

383383
## 9.8.0
384-
- - FIX: LSP initialization bug.
384+
- FIX: LSP initialization bug.
385+
386+
## 9.9.0
387+
- FIX: Cursor jump on typing.
388+
- FIX: Frozen horizontal scroll on dynamic font size.

README.md

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@
4545
This will make the code_forge lightning fast like the [zed editor](https://zed.dev/).**
4646

4747

48-
### What's new in 9.8.0
49-
- FIX: LSP initialization bug.
48+
### What's new in 9.9.0
49+
- FIX: Cursor jump on typing.
50+
- FIX: Frozen horizontal scroll on dynamic font size.
5051

5152

5253

@@ -122,7 +123,7 @@ Add CodeForge to your `pubspec.yaml`:
122123

123124
```yaml
124125
dependencies:
125-
code_forge: ^9.8.0
126+
code_forge: ^9.9.0
126127
```
127128
128129
Then run:
@@ -733,15 +734,9 @@ CodeForge supports a variety of keyboard shortcuts for efficient editing:
733734

734735
---
735736

736-
## Contributing
737+
## Used by
737738

738-
Contributions are welcome! Whether it's bug fixes, new features, or documentation improvements.
739-
740-
1. Fork the repository
741-
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
742-
3. Commit your changes (`git commit -m 'Add amazing feature'`)
743-
4. Push to the branch (`git push origin feature/amazing-feature`)
744-
5. Open a Pull Request
739+
- [ROXUM IDE](https://github.com/heckmon/roxum-ide) - A minimal and powerful IDE/Code editor for Android.
745740

746741
---
747742

example/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ packages:
4747
path: ".."
4848
relative: true
4949
source: path
50-
version: "9.5.0"
50+
version: "9.8.0"
5151
collection:
5252
dependency: transitive
5353
description:

lib/LSP/lsp.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,6 @@ sealed class LspConfig {
239239
///
240240
/// This method is used internally by the [CodeForge] widget and calling it directly is not recommended.
241241
///
242-
/// If [initialContent] is provided, it will be used as the document content.
243242
/// Otherwise, the content will be read from [filePath].
244243
Future<void> openDocument(String filePath) async {
245244
final version = (_openDocuments[filePath] ?? 0) + 1;

lib/code_forge/code_area.dart

Lines changed: 78 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -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]!;

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: code_forge
22
description: "A sophisticated code editor package with AI completion, LSP support, syntax highlighting, and advanced editing capabilities."
3-
version: 9.8.0
3+
version: 9.9.0
44
homepage: https://github.com/heckmon/code_forge
55

66
environment:

0 commit comments

Comments
 (0)