Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions src/AvaloniaEdit.TextMate/TextMateColoringTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ protected override void TransformLine(DocumentLine line, ITextRunConstructionCon
if (model == null || document == null || theme == null || brushes == null)
return;

// for long lines, do not tokenize and colorize for performance reasons
if (line.Length > VisualLine.LENGTH_LIMIT)
return;

int lineNumber = line.LineNumber;

var tokens = model.GetLineTokens(lineNumber - 1);
Expand Down
4 changes: 2 additions & 2 deletions src/AvaloniaEdit/Editing/EditingCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ public static void OnTab(object target, RoutedEventArgs args)
while (true)
{
var offset = current.Offset;
if (textArea.ReadOnlySectionProvider.CanInsert(offset))
if (textArea.CanInsert(offset))
textArea.Document.Replace(offset, 0, textArea.Options.IndentationString,
OffsetChangeMappingType.KeepAnchorBeforeInsertion);
if (current == end)
Expand Down Expand Up @@ -499,7 +499,7 @@ private static void CanPaste(object target, CanExecuteRoutedEventArgs args)
var textArea = GetTextArea(target);
if (textArea is { Document: not null })
{
args.CanExecute = textArea.ReadOnlySectionProvider.CanInsert(textArea.Caret.Offset);
args.CanExecute = textArea.CanInsert(textArea.Caret.Offset);
args.Handled = true;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/AvaloniaEdit/Editing/EmptySelection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public override void ReplaceSelectionWithText(string newText)
newText = AddSpacesIfRequired(newText, TextArea.Caret.Position, TextArea.Caret.Position);
if (newText.Length > 0)
{
if (TextArea.ReadOnlySectionProvider.CanInsert(TextArea.Caret.Offset))
if (TextArea.CanInsert(TextArea.Caret.Offset))
{
TextArea.Document.Insert(TextArea.Caret.Offset, newText);
}
Expand Down
74 changes: 74 additions & 0 deletions src/AvaloniaEdit/Editing/LongLineEditProtection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;

using AvaloniaEdit.Document;
using AvaloniaEdit.Rendering;

namespace AvaloniaEdit.Editing
{
/// <summary>
/// Disallow edits in the collapsed tail of long lines (see <see cref="VisualLine.LENGTH_LIMIT"/>):
/// </summary>
internal static class LongLineEditProtection
{
internal static bool CanInsert(TextDocument document, int offset)
{
if (document == null)
return true;

DocumentLine line = document.GetLineByOffset(offset);
return offset - line.Offset < VisualLine.LENGTH_LIMIT;
}

internal static IEnumerable<ISegment> GetDeletableSegments(
TextDocument document, ISegment segment)
{
if (segment == null)
throw new ArgumentNullException(nameof(segment));

if (document == null)
{
yield return segment;
yield break;
}

if (segment.Length == 0)
{
if (CanInsert(document, segment.Offset))
yield return segment;
yield break;
}

int deletableStart = segment.Offset;
int segmentEnd = segment.EndOffset;

DocumentLine line = document.GetLineByOffset(segment.Offset);
while (line != null && line.Offset < segmentEnd)
{
if (line.Length > VisualLine.LENGTH_LIMIT)
{
// protect the hidden text plus the line delimiter; deleting the
// delimiter would merge the next line into the hidden region
int protectedStart = line.Offset + VisualLine.LENGTH_LIMIT;
int protectedEnd = line.Offset + line.TotalLength;

if (protectedStart < segmentEnd && protectedEnd > deletableStart)
{
if (protectedStart > deletableStart)
{
yield return new SimpleSegment(
deletableStart, protectedStart - deletableStart);
}

deletableStart = Math.Min(protectedEnd, segmentEnd);
}
}

line = line.NextLine;
}

if (deletableStart < segmentEnd)
yield return new SimpleSegment(deletableStart, segmentEnd - deletableStart);
}
}
}
2 changes: 1 addition & 1 deletion src/AvaloniaEdit/Editing/RectangleSelection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ private void ReplaceSingleLineText(TextArea textArea, SelectionSegment lineSegme
{
if (lineSegment.Length == 0)
{
if (newText.Length > 0 && textArea.ReadOnlySectionProvider.CanInsert(lineSegment.StartOffset))
if (newText.Length > 0 && textArea.CanInsert(lineSegment.StartOffset))
{
newText = AddSpacesIfRequired(newText, new TextViewPosition(_document.GetLocation(lineSegment.StartOffset), lineSegment.StartVisualColumn), new TextViewPosition(_document.GetLocation(lineSegment.EndOffset), lineSegment.EndVisualColumn));
textArea.Document.Insert(lineSegment.StartOffset, newText);
Expand Down
2 changes: 1 addition & 1 deletion src/AvaloniaEdit/Editing/SelectionMouseHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ DragDropEffects GetEffect(DragEventArgs e)
{
TextArea.Caret.Position = new TextViewPosition(TextArea.Document.GetLocation(offset), visualColumn) { IsAtEndOfLine = isAtEndOfLine };
TextArea.Caret.DesiredXPos = double.NaN;
if (TextArea.ReadOnlySectionProvider.CanInsert(offset))
if (TextArea.CanInsert(offset))
{
if ((e.DragEffects & DragDropEffects.Move) == DragDropEffects.Move
&& (e.KeyModifiers & KeyModifiers.Control) != KeyModifiers.Control)
Expand Down
12 changes: 10 additions & 2 deletions src/AvaloniaEdit/Editing/TextArea.cs
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ internal void RemoveSelectedText()
{
foreach (var s in _selection.Segments)
{
Debug.Assert(!ReadOnlySectionProvider.GetDeletableSegments(s).Any());
Debug.Assert(GetDeletableSegments(s).Length == 0);
}
}
#endif
Expand All @@ -940,12 +940,20 @@ internal void ReplaceSelectionWithText(string newText)
_selection.ReplaceSelectionWithText(newText);
}

internal bool CanInsert(int offset)
{
return ReadOnlySectionProvider.CanInsert(offset) &&
LongLineEditProtection.CanInsert(Document, offset);
}

internal ISegment[] GetDeletableSegments(ISegment segment)
{
var deletableSegments = ReadOnlySectionProvider.GetDeletableSegments(segment);
if (deletableSegments == null)
throw new InvalidOperationException("ReadOnlySectionProvider.GetDeletableSegments returned null");
var array = deletableSegments.ToArray();
var array = deletableSegments
.SelectMany(s => LongLineEditProtection.GetDeletableSegments(Document, s))
.ToArray();
var lastIndex = segment.Offset;
foreach (var t in array)
{
Expand Down
6 changes: 5 additions & 1 deletion src/AvaloniaEdit/Rendering/VisualLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ namespace AvaloniaEdit.Rendering
/// </summary>
public sealed class VisualLine
{
public const string LENGTH_LIMIT_MARKER = "...";
public const int LENGTH_LIMIT = 3000;

private enum LifetimePhase : byte
Expand Down Expand Up @@ -172,7 +173,10 @@ void PerformVisualElementConstruction(IReadOnlyList<VisualLineElementGenerator>
// for long lines, do not run the element generators for performance reasons
if (lineLength > LENGTH_LIMIT)
{
_elements.Add(new VisualLineText(this, lineLength));
// shape only the first LENGTH_LIMIT characters and collapse the remainder
// into a marker. Otherwise stalls the UI thread.
_elements.Add(new VisualLineText(this, LENGTH_LIMIT));
_elements.Add(new FormattedTextElement(LENGTH_LIMIT_MARKER, lineLength - LENGTH_LIMIT));
return;
}

Expand Down