Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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.Demo/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinWidth="500"
MinHeight="300"
Width="950"
Title="AvaloniaEdit Demo"
x:Class="AvaloniaEdit.Demo.MainWindow"
Background="#1E1E1E">
Expand All @@ -17,6 +18,9 @@
<ComboBox Name="syntaxModeCombo" />
<Button Name="changeThemeBtn" Content="Change theme"/>
</StackPanel>
<StackPanel Name="StatusBar" Background="Purple" Height="25" DockPanel.Dock="Bottom" Orientation="Horizontal">
<TextBlock Name="StatusText" Text="Ready" Margin="5 0 0 0" VerticalAlignment="Center" FontSize="12"/>
</StackPanel>
<AvalonEdit:TextEditor Name="Editor"
FontFamily="Consolas,Menlo,Monospace"
Margin="30"
Expand Down
10 changes: 10 additions & 0 deletions src/AvaloniaEdit.Demo/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class MainWindow : Window
private Button _clearControlBtn;
private Button _changeThemeBtn;
private ComboBox _syntaxModeCombo;
private TextBlock _statusTextBlock;
private ElementGenerator _generator = new ElementGenerator();
private int _currentTheme = (int)ThemeName.DarkPlus;

Expand All @@ -57,7 +58,9 @@ public MainWindow()
_textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered;
_textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering;
_textEditor.TextArea.IndentationStrategy = new Indentation.CSharp.CSharpIndentationStrategy();
_textEditor.TextArea.Caret.PositionChanged += Caret_PositionChanged;
_textEditor.TextArea.RightClickMovesCaret = true;

_addControlBtn = this.FindControl<Button>("addControlBtn");
_addControlBtn.Click += _addControlBtn_Click;

Expand Down Expand Up @@ -85,6 +88,8 @@ public MainWindow()
_textEditor.Document = new TextDocument(ResourceLoader.LoadSampleFile(scopeName));
_textMateInstallation.SetGrammarByLanguageId(csharpLanguage.Id);

_statusTextBlock = this.Find<TextBlock>("StatusText");

this.AddHandler(PointerWheelChangedEvent, (o, i) =>
{
if (i.KeyModifiers != KeyModifiers.Control) return;
Expand All @@ -93,6 +98,11 @@ public MainWindow()
}, RoutingStrategies.Bubble, true);
}

private void Caret_PositionChanged(object sender, EventArgs e)
{
_statusTextBlock.Text = String.Format("Line {0} Column {1}", _textEditor.TextArea.Caret.Line, _textEditor.TextArea.Caret.Column);
}

protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
Expand Down
2 changes: 1 addition & 1 deletion src/AvaloniaEdit.TextMate/AvaloniaEdit.TextMate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="TextMateSharp" Version="1.0.13" />
<PackageReference Include="TextMateSharp" Version="1.0.14" />
</ItemGroup>

</Project>
21 changes: 19 additions & 2 deletions src/AvaloniaEdit/Rendering/VisualLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ namespace AvaloniaEdit.Rendering
/// </summary>
public sealed class VisualLine
{
public const int LENGTH_LIMIT = 3000;

private enum LifetimePhase : byte
{
Generating,
Expand Down Expand Up @@ -155,6 +157,7 @@ internal void ConstructVisualElements(ITextRunConstructionContext context, Visua
private void PerformVisualElementConstruction(VisualLineElementGenerator[] generators)
{
var document = Document;
var lineLength = FirstDocumentLine.Length;
var offset = FirstDocumentLine.Offset;
var currentLineEnd = offset + FirstDocumentLine.Length;
LastDocumentLine = FirstDocumentLine;
Expand All @@ -164,7 +167,7 @@ private void PerformVisualElementConstruction(VisualLineElementGenerator[] gener
var textPieceEndOffset = currentLineEnd;
foreach (var g in generators)
{
g.CachedInterest = g.GetFirstInterestedOffset(offset + askInterestOffset);
g.CachedInterest = (lineLength > LENGTH_LIMIT) ? -1: g.GetFirstInterestedOffset(offset + askInterestOffset);
if (g.CachedInterest != -1)
{
if (g.CachedInterest < offset)
Expand All @@ -179,7 +182,21 @@ private void PerformVisualElementConstruction(VisualLineElementGenerator[] gener
if (textPieceEndOffset > offset)
{
var textPieceLength = textPieceEndOffset - offset;
_elements.Add(new VisualLineText(this, textPieceLength));
int remaining = textPieceLength;
while (true)
{
if (remaining > LENGTH_LIMIT)
{
// split in chunks of LENGTH_LIMIT
_elements.Add(new VisualLineText(this, LENGTH_LIMIT));
remaining -= LENGTH_LIMIT;
}
else
{
_elements.Add(new VisualLineText(this, remaining));
break;
}
}
offset = textPieceEndOffset;
}
// If no elements constructed / only zero-length elements constructed:
Expand Down
7 changes: 0 additions & 7 deletions src/AvaloniaEdit/Text/TextLineImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ namespace AvaloniaEdit.Text
{
internal sealed class TextLineImpl : TextLine
{
private const int MaxCharactersPerLine = 10000;

private readonly TextLineRun[] _runs;

public override int FirstIndex { get; }
Expand Down Expand Up @@ -49,11 +47,6 @@ internal static TextLineImpl Create(TextParagraphProperties paragraphProperties,
visibleLength += AddRunReturnVisibleLength(runs, prevRun);
}

if (visibleLength >= MaxCharactersPerLine)
{
throw new NotSupportedException("Too many characters per line");
}

while (true)
{
visibleLength += AddRunReturnVisibleLength(runs, run);
Expand Down
99 changes: 70 additions & 29 deletions src/AvaloniaEdit/Text/TextLineRun.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using System;
using System.Linq;

using Avalonia;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;

using AvaloniaEdit.Rendering;

namespace AvaloniaEdit.Text
{
internal sealed class TextLineRun
Expand All @@ -11,8 +15,7 @@ internal sealed class TextLineRun

private FormattedText _formattedText;
private Size _formattedTextSize;
private double[] _glyphWidths;

private GlyphWidths _glyphWidths;
public StringRange StringRange { get; private set; }

public int Length { get; set; }
Expand Down Expand Up @@ -109,7 +112,10 @@ private static TextLineRun Create(TextSource textSource, StringRange stringRange
return new TextLineRun(textRun.Length, textRun)
{
IsEmbedded = true,
_glyphWidths = new double[] { width },
_glyphWidths = new GlyphWidths(
stringRange,
textRun.Properties.Typeface.GlyphTypeface,
textRun.Properties.FontSize),
// Embedded objects must propagate their width to the container.
// Otherwise text runs after the embedded object are drawn at the same x position.
Width = width
Expand Down Expand Up @@ -162,7 +168,10 @@ private static TextLineRun CreateRunForTab(TextRun textRun)
Width = 40
};

run.SetGlyphWidths();
run._glyphWidths = new GlyphWidths(
run.StringRange,
run.Typeface.GlyphTypeface,
run.FontSize);

return run;
}
Expand Down Expand Up @@ -192,7 +201,10 @@ internal static TextLineRun CreateRunForText(StringRange stringRange, TextRun te

run.Width = size.Width;

run.SetGlyphWidths();
run._glyphWidths = new GlyphWidths(
run.StringRange,
run.Typeface.GlyphTypeface,
run.FontSize);

return run;
}
Expand All @@ -203,27 +215,6 @@ private TextLineRun(int length, TextRun textRun)
TextRun = textRun;
}

private void SetGlyphWidths()
{
var result = new double[StringRange.Length];

for (var i = 0; i < StringRange.Length; i++)
{
// TODO: is there a better way of getting glyph metrics?
var tf = Typeface;
var size = new FormattedText
{
Text = StringRange[i].ToString(),
Typeface = new Typeface(tf.FontFamily, tf.Style, tf.Weight),
FontSize = FontSize
}.Bounds.Size;

result[i] = size.Width;
}

_glyphWidths = result;
}

public void Draw(DrawingContext drawingContext, double x, double y)
{
if (IsEmbedded)
Expand Down Expand Up @@ -290,7 +281,7 @@ public bool UpdateTrailingInfo(TrailingInfo trailing)
{
while (index > 0 && IsSpace(StringRange[index - 1]))
{
trailing.SpaceWidth += _glyphWidths[index - 1];
trailing.SpaceWidth += _glyphWidths.GetAt(index - 1);
index--;
trailing.Count++;
}
Expand All @@ -313,7 +304,7 @@ public double GetDistanceFromCharacter(int index)
double distance = 0;
for (var i = 0; i < index; i++)
{
distance += _glyphWidths[i];
distance += _glyphWidths.GetAt(i);
}

return distance;
Expand All @@ -332,7 +323,7 @@ public double GetDistanceFromCharacter(int index)
double width = 0;
for (; index < Length; index++)
{
width = IsTab ? Width / Length : _glyphWidths[index];
width = IsTab ? Width / Length : _glyphWidths.GetAt(index);
if (distance < width)
{
break;
Expand All @@ -350,5 +341,55 @@ private static bool IsSpace(char ch)
{
return ch == ' ' || ch == '\u00a0';
}

class GlyphWidths
{
private const double NOT_CALCULATED_YET = -1;
private double[] _glyphWidths;
private GlyphTypeface _typeFace;
private StringRange _range;
private double _scale;

internal GlyphWidths(StringRange range, GlyphTypeface typeFace, double fontSize)
{
_range = range;
_typeFace = typeFace;
_scale = fontSize / _typeFace.DesignEmHeight;

InitGlyphWidths();
}

internal double GetAt(int index)
{
if (_glyphWidths[index] == NOT_CALCULATED_YET)
_glyphWidths[index] = MeasureGlyphAt(index);

return _glyphWidths[index];
}

double MeasureGlyphAt(int index)
{
return _typeFace.GetGlyphAdvance(
_typeFace.GetGlyph(_range[index])) * _scale;
}

void InitGlyphWidths()
{
int capacity = _range.Length;

bool useCheapGlyphMeasurement =
capacity >= VisualLine.LENGTH_LIMIT &&
_typeFace.IsFixedPitch;

if (useCheapGlyphMeasurement)
{
double size = MeasureGlyphAt(0);
_glyphWidths = Enumerable.Repeat<double>(size, capacity).ToArray();
return;
}

_glyphWidths = Enumerable.Repeat<double>(NOT_CALCULATED_YET, capacity).ToArray();
}
}
}
}