Skip to content

Commit 6aae52d

Browse files
committed
more robust syntax highlighting relocation on line changes
1 parent b693a5f commit 6aae52d

File tree

2 files changed

+59
-25
lines changed

2 files changed

+59
-25
lines changed

src/SharpIDE.Godot/Features/CodeEditor/CustomSyntaxHighlighter.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,15 @@ static System.Collections.Generic.Dictionary<int, T> Rearrange<T>(System.Collect
7676
var newDict = new System.Collections.Generic.Dictionary<int, T>();
7777
foreach (var kvp in existingDictionary)
7878
{
79+
// StartOfLine: the new blank line was inserted *before* the existing content on fromLine,
80+
// so fromLine's old content has shifted down — shift it.
81+
// EndOfLine: the caret was at the end; the new line is blank and below; fromLine keeps its content.
82+
// MidLine: the content before the caret stays on fromLine; only lines after shift.
83+
// Treat the same as EndOfLine — fromLine's highlighting data stays put.
84+
// Unknown: we don't know; conservatively keep fromLine's data in place (same as EndOfLine).
7985
bool shouldShift =
80-
kvp.Key > fromLine || // always shift lines after the insertion point
81-
(origin == SharpIdeCodeEdit.LineEditOrigin.StartOfLine && kvp.Key == fromLine); // shift current line if origin is Start
86+
kvp.Key > fromLine ||
87+
(origin == SharpIdeCodeEdit.LineEditOrigin.StartOfLine && kvp.Key == fromLine);
8288

8389
int newKey = shouldShift ? kvp.Key + difference : kvp.Key;
8490
newDict[newKey] = kvp.Value;

src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public partial class SharpIdeCodeEdit : CodeEdit
4949
private bool _fileChangingSuppressBreakpointToggleEvent;
5050
private bool _settingWholeDocumentTextSuppressLineEditsEvent; // A dodgy workaround - setting the whole document doesn't guarantee that the line count stayed the same etc. We are still going to have broken highlighting. TODO: Investigate getting minimal text change ranges, and change those ranges only
5151
private bool _fileDeleted;
52+
// Captured in _GuiInput *before* a line-modifying keystroke is processed, so that OnLinesEditedFrom
53+
// can determine the correct LineEditOrigin from pre-edit state rather than post-edit state.
54+
private (int line, int col, string lineText)? _pendingLineEditOrigin;
5255
private IDisposable? _projectDiagnosticsObserveDisposable;
5356

5457
[Inject] private readonly IdeOpenTabsFileManager _openTabsFileManager = null!;
@@ -140,43 +143,42 @@ private async Task OnSolutionAltered()
140143
public enum LineEditOrigin
141144
{
142145
StartOfLine,
146+
MidLine,
143147
EndOfLine,
144148
Unknown
145149
}
146150
// Line removed - fromLine 55, toLine 54
147151
// Line added - fromLine 54, toLine 55
148152
// Multi cursor gets a single line event for each
149-
// problem is 10 to 11 gets returned for 'Enter' at the start of line 10, as well as 'Enter' at the end of line 10
150-
// This means that the line that moves down needs to be based on whether the new line was from the start or end of the line
151153
private void OnLinesEditedFrom(long fromLine, long toLine)
152154
{
153155
if (fromLine == toLine) return;
154156
if (_settingWholeDocumentTextSuppressLineEditsEvent) return;
155-
var fromLineText = GetLine((int)fromLine);
156-
var caretPosition = this.GetCaretPosition();
157-
var caretPositionEnum = LineEditOrigin.Unknown;
158157

159-
if (caretPosition.col > fromLineText.Length)
160-
{
161-
_syntaxHighlighter.LinesChanged(fromLine, toLine, caretPositionEnum);
162-
return;
163-
}
158+
// Consume the pre-edit snapshot captured in _GuiInput (if any).
159+
// Because the snapshot was taken *before* the edit, the caret position and line text
160+
// are exactly what they were at the moment the key was pressed — no post-edit guesswork.
161+
var snapshot = _pendingLineEditOrigin;
162+
_pendingLineEditOrigin = null;
164163

165-
var textFrom0ToCaret = fromLineText.AsSpan()[..caretPosition.col];
166-
if (textFrom0ToCaret.IsEmpty || textFrom0ToCaret.IsWhiteSpace())
167-
{
168-
caretPositionEnum = LineEditOrigin.StartOfLine;
169-
}
170-
else
164+
var origin = LineEditOrigin.Unknown;
165+
if (snapshot is not null)
171166
{
172-
var textfromCaretToEnd = fromLineText.AsSpan()[caretPosition.col..];
173-
if (textfromCaretToEnd.IsEmpty || textfromCaretToEnd.IsWhiteSpace())
174-
{
175-
caretPositionEnum = LineEditOrigin.EndOfLine;
176-
}
167+
var (_, snapCol, snapText) = snapshot.Value;
168+
var clampedCol = Math.Min(snapCol, snapText.Length);
169+
var textBeforeCaret = snapText.AsSpan()[..clampedCol];
170+
var textAfterCaret = snapText.AsSpan()[clampedCol..];
171+
172+
if (textBeforeCaret.IsEmpty || textBeforeCaret.IsWhiteSpace())
173+
origin = LineEditOrigin.StartOfLine;
174+
else if (textAfterCaret.IsEmpty || textAfterCaret.IsWhiteSpace())
175+
origin = LineEditOrigin.EndOfLine;
176+
else
177+
origin = LineEditOrigin.MidLine;
177178
}
178-
//GD.Print($"Lines edited from {fromLine} to {toLine}, origin: {caretPositionEnum}, current caret position: {caretPosition}");
179-
_syntaxHighlighter.LinesChanged(fromLine, toLine, caretPositionEnum);
179+
180+
//GD.Print($"Lines edited from {fromLine} to {toLine}, origin: {origin}");
181+
_syntaxHighlighter.LinesChanged(fromLine, toLine, origin);
180182
}
181183

182184
public override void _ExitTree()
@@ -428,6 +430,30 @@ public override void _Draw()
428430
public override void _GuiInput(InputEvent @event)
429431
{
430432
if (@event is InputEventMouseMotion) return;
433+
434+
// Capture pre-edit caret state for line-modifying keystrokes, so that OnLinesEditedFrom
435+
// can determine LineEditOrigin from the state *before* the edit happened.
436+
// We only do this for single-caret edits; multi-caret falls back to Unknown.
437+
if (@event is InputEventKey { Pressed: true } keyEvent && GetCaretCount() == 1)
438+
{
439+
var (caretLine, caretCol) = GetCaretPosition();
440+
switch (keyEvent.Keycode)
441+
{
442+
// Enter / numpad Enter — line(s) added
443+
case Key.Enter or Key.KpEnter:
444+
_pendingLineEditOrigin = (caretLine, caretCol, GetLine(caretLine));
445+
break;
446+
// Forward-delete at end of line merges the next line up — line removed
447+
case Key.Delete when !HasSelection():
448+
{
449+
var lineText = GetLine(caretLine);
450+
if (caretCol == lineText.Length && caretLine < GetLineCount() - 1)
451+
_pendingLineEditOrigin = (caretLine, caretCol, lineText);
452+
break;
453+
}
454+
}
455+
}
456+
431457
if (@event.IsActionPressed(InputStringNames.Backspace, true) && HasSelection() is false)
432458
{
433459
var (caretLine, caretCol) = GetCaretPosition();
@@ -437,6 +463,8 @@ public override void _GuiInput(InputEvent @event)
437463
var textBeforeCaret = lineText.AsSpan()[..caretCol];
438464
if (textBeforeCaret.IsEmpty || textBeforeCaret.IsWhiteSpace())
439465
{
466+
// Capture pre-edit state before RemoveText triggers LinesEditedFrom
467+
if (GetCaretCount() == 1) _pendingLineEditOrigin = (caretLine, caretCol, lineText);
440468
BeginComplexOperation();
441469
var prevLine = caretLine - 1;
442470
var prevLineLength = GetLine(prevLine).Length;

0 commit comments

Comments
 (0)