Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions src/OneWare.Essentials/EditorExtensions/TextModificationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,19 @@ public void SetModification(string key, params TextModificationSegment[] segment
if (s.StartOffset >= f.StartOffset)
{
if (s.StartOffset <= f.EndOffset) continue; //Completely overlapped
if (s.EndOffset > f.EndOffset + 1) s.StartOffset = f.EndOffset + 1;
if (s.EndOffset > f.EndOffset + 1)
{
var endOffset = s.EndOffset;
s.StartOffset = f.EndOffset + 1;
s.EndOffset = endOffset;
}
}

if (s.StartOffset < f.StartOffset)
if (s.EndOffset > f.EndOffset - 1)
s.EndOffset = f.EndOffset - 1;

if (s.Length <= 0) continue;
}

m.Add(s);
Expand All @@ -67,13 +74,21 @@ protected override void ColorizeLine(DocumentLine line)

foreach (var overlap in overlaps)
{
if (overlap.EndOffset > line.EndOffset) overlap.EndOffset = line.EndOffset;
if (overlap.StartOffset < line.Offset) overlap.StartOffset = line.Offset;
ChangeLinePart(overlap.StartOffset, overlap.EndOffset, x => ApplyChanges(x, overlap));
if (!TryClampToLine(overlap, line, out var startOffset, out var endOffset)) continue;
ChangeLinePart(startOffset, endOffset, x => ApplyChanges(x, overlap));
}
}
}

// Must not mutate the segment: it is shared by all lines it spans, and setting
// TextSegment.StartOffset moves the segment (keeps its length) instead of trimming it.
internal static bool TryClampToLine(ISegment segment, DocumentLine line, out int startOffset, out int endOffset)
{
startOffset = Math.Max(segment.Offset, line.Offset);
endOffset = Math.Min(segment.EndOffset, line.EndOffset);
return endOffset > startOffset;
}

private void ApplyChanges(VisualLineElement element, TextModificationSegment segment)
{
if (segment.Foreground != null) element.TextRunProperties.SetForegroundBrush(segment.Foreground);
Expand Down
4 changes: 4 additions & 0 deletions src/OneWare.Essentials/OneWare.Essentials.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@
<None Include="readme.md" Pack="true" PackagePath=""/>
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="OneWare.Essentials.UnitTests"/>
</ItemGroup>

</Project>
38 changes: 38 additions & 0 deletions tests/OneWare.Essentials.UnitTests/TextModificationServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using AvaloniaEdit.Document;
using OneWare.Essentials.EditorExtensions;
using Xunit;

namespace OneWare.Essentials.UnitTests;

public class TextModificationServiceTests
{
[Fact]
public void TryClampToLine_MultiLineSegment_StaysWithinEachLineAndDoesNotMutateSegment()
{
var document = new TextDocument { Text = "first\nsecond line\nthird" };
var segment = new TextModificationSegment(2, document.TextLength - 2);
var originalStart = segment.StartOffset;
var originalEnd = segment.EndOffset;

foreach (var line in document.Lines)
{
Assert.True(TextModificationService.TryClampToLine(segment, line, out var start, out var end));
Assert.InRange(start, line.Offset, line.EndOffset);
Assert.InRange(end, line.Offset, line.EndOffset);
Assert.True(end > start);
}

Assert.Equal(originalStart, segment.StartOffset);
Assert.Equal(originalEnd, segment.EndOffset);
}

[Fact]
public void TryClampToLine_SegmentOnlyTouchingLineDelimiter_ReturnsFalse()
{
var document = new TextDocument { Text = "ab\ncd" };
var firstLine = document.GetLineByNumber(1);
var segment = new TextModificationSegment(firstLine.EndOffset, firstLine.EndOffset + 1);

Assert.False(TextModificationService.TryClampToLine(segment, firstLine, out _, out _));
}
}
Loading