Skip to content
Open
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
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