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: 21 additions & 2 deletions src/AvaloniaEdit.Demo/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using AvaloniaEdit.Demo.Resources;
using AvaloniaEdit.Document;
using AvaloniaEdit.Editing;
using AvaloniaEdit.Folding;
using AvaloniaEdit.Rendering;
using AvaloniaEdit.TextMate;
using AvaloniaEdit.TextMate.Grammars;
Expand All @@ -25,6 +26,7 @@ namespace AvaloniaEdit.Demo
public class MainWindow : Window
{
private readonly TextEditor _textEditor;
private FoldingManager _foldingManager;
private readonly TextMate.TextMate.Installation _textMateInstallation;
private CompletionWindow _completionWindow;
private OverloadInsightWindow _insightWindow;
Expand Down Expand Up @@ -56,7 +58,7 @@ public MainWindow()
_textEditor.TextArea.Background = this.Background;
_textEditor.TextArea.TextEntered += textEditor_TextArea_TextEntered;
_textEditor.TextArea.TextEntering += textEditor_TextArea_TextEntering;
_textEditor.Options.ConvertTabsToSpaces = true;
_textEditor.Options.ShowBoxForControlCharacters = true;
_textEditor.TextArea.IndentationStrategy = new Indentation.CSharp.CSharpIndentationStrategy(_textEditor.Options);
_textEditor.TextArea.Caret.PositionChanged += Caret_PositionChanged;
_textEditor.TextArea.RightClickMovesCaret = true;
Expand Down Expand Up @@ -86,7 +88,9 @@ public MainWindow()

string scopeName = _registryOptions.GetScopeByLanguageId(csharpLanguage.Id);

_textEditor.Document = new TextDocument(ResourceLoader.LoadSampleFile(scopeName));
_textEditor.Document = new TextDocument(
"// AvaloniaEdit supports displaying control chars: \a or \b or \v" + Environment.NewLine +
ResourceLoader.LoadSampleFile(scopeName));
_textMateInstallation.SetGrammar(_registryOptions.GetScopeByLanguageId(csharpLanguage.Id));

_statusTextBlock = this.Find<TextBlock>("StatusText");
Expand Down Expand Up @@ -117,10 +121,25 @@ private void SyntaxModeCombo_SelectionChanged(object sender, SelectionChangedEve
{
Language language = (Language)_syntaxModeCombo.SelectedItem;

if (_foldingManager != null)
{
_foldingManager.Clear();
FoldingManager.Uninstall(_foldingManager);
}

string scopeName = _registryOptions.GetScopeByLanguageId(language.Id);

_textEditor.Document = new TextDocument(ResourceLoader.LoadSampleFile(scopeName));
_textMateInstallation.SetGrammar(scopeName);

if (language.Id == "xml")
{
_foldingManager = FoldingManager.Install(_textEditor.TextArea);

var strategy = new XmlFoldingStrategy();
strategy.UpdateFoldings(_foldingManager, _textEditor.Document);
return;
}
}

private void ChangeThemeButton_Click(object sender, RoutedEventArgs e)
Expand Down
16 changes: 9 additions & 7 deletions src/AvaloniaEdit/Rendering/SingleCharacterElementGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,31 +223,33 @@ public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructio
}
}

private sealed class SpecialCharacterTextRun : FormattedTextRun
internal sealed class SpecialCharacterTextRun : FormattedTextRun
{
private static readonly ISolidColorBrush DarkGrayBrush;

internal const double BoxMargin = 3;

static SpecialCharacterTextRun()
{
DarkGrayBrush = new ImmutableSolidColorBrush(Color.FromArgb(200, 128, 128, 128));
DarkGrayBrush = new ImmutableSolidColorBrush(Color.FromArgb(200, 128, 128, 128));
}

public SpecialCharacterTextRun(FormattedTextElement element, TextRunProperties properties)
: base(element, properties)
{
}

public override Rect ComputeBoundingBox()
public override Size GetSize(double remainingParagraphWidth)
{
var r = base.ComputeBoundingBox();
return r.WithWidth(r.Width + 3);
var s = base.GetSize(remainingParagraphWidth);
return s.WithWidth(s.Width + BoxMargin);
}

public override void Draw(DrawingContext drawingContext, Point origin)
{
var newOrigin = new Point(origin.X + 1.5, origin.Y);
var newOrigin = new Point(origin.X + (BoxMargin / 2), origin.Y);
var metrics = GetSize(double.PositiveInfinity);
var r = new Rect(newOrigin.X - 0.5, newOrigin.Y, metrics.Width + 2, metrics.Height);
var r = new Rect(origin.X, origin.Y, metrics.Width, metrics.Height);
drawingContext.FillRectangle(DarkGrayBrush, r, 2.5f);
base.Draw(drawingContext, newOrigin);
}
Expand Down
8 changes: 5 additions & 3 deletions src/AvaloniaEdit/Text/TextLineImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal sealed class TextLineImpl : TextLine
{
private readonly TextLineRun[] _runs;

public TextLineRun[] LineRuns { get { return _runs; } }
public override int FirstIndex { get; }

public override int Length { get; }
Expand All @@ -31,13 +32,14 @@ internal static TextLineImpl Create(TextParagraphProperties paragraphProperties,
var visibleLength = 0;
var widthLeft = paragraphProperties.TextWrapping == TextWrapping.Wrap && paragraphLength > 0 ? paragraphLength : double.MaxValue;
TextLineRun prevRun = null;
var run = TextLineRun.Create(textSource, index, firstIndex, widthLeft);
var run = TextLineRun.Create(textSource, index, firstIndex, widthLeft, paragraphProperties);

if (!run.IsEnd && run.Width <= widthLeft)
{
index += run.Length;
widthLeft -= run.Width;
prevRun = run;
run = TextLineRun.Create(textSource, index, firstIndex, widthLeft);
run = TextLineRun.Create(textSource, index, firstIndex, widthLeft, paragraphProperties);
}

var trailing = new TrailingInfo();
Expand All @@ -59,7 +61,7 @@ internal static TextLineImpl Create(TextParagraphProperties paragraphProperties,
return new TextLineImpl(paragraphProperties, firstIndex, runs, trailing);
}

run = TextLineRun.Create(textSource, index, firstIndex, widthLeft);
run = TextLineRun.Create(textSource, index, firstIndex, widthLeft, paragraphProperties);
}
}

Expand Down
79 changes: 49 additions & 30 deletions src/AvaloniaEdit/Text/TextLineRun.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using Avalonia;
Expand All @@ -12,10 +14,11 @@ namespace AvaloniaEdit.Text
internal sealed class TextLineRun
{
private const string NewlineString = "\r\n";
private const string TabString = "\t";

private FormattedText _formattedText;
private Size _formattedTextSize;
private GlyphWidths _glyphWidths;
private IReadOnlyList<double> _glyphWidths;
public StringRange StringRange { get; private set; }

public int Length { get; set; }
Expand Down Expand Up @@ -86,19 +89,19 @@ private TextLineRun()
{
}

public static TextLineRun Create(TextSource textSource, int index, int firstIndex, double lengthLeft)
public static TextLineRun Create(TextSource textSource, int index, int firstIndex, double lengthLeft, TextParagraphProperties paragraphProperties)
{
var textRun = textSource.GetTextRun(index);
var stringRange = textRun.GetStringRange();
return Create(textSource, stringRange, textRun, index, lengthLeft);
return Create(textSource, stringRange, textRun, index, lengthLeft, paragraphProperties);
}

private static TextLineRun Create(TextSource textSource, StringRange stringRange, TextRun textRun, int index, double widthLeft)
private static TextLineRun Create(TextSource textSource, StringRange stringRange, TextRun textRun, int index, double widthLeft, TextParagraphProperties paragraphProperties)
{
if (textRun is TextCharacters)
{
return CreateRunForEol(textSource, stringRange, textRun, index) ??
CreateRunForText(stringRange, textRun, widthLeft, false, true);
return CreateRunForSpecialChars(textSource, stringRange, textRun, index, paragraphProperties) ??
CreateRunForText(stringRange, textRun, widthLeft, false, true, paragraphProperties);
}

if (textRun is TextEndOfLine)
Expand All @@ -112,10 +115,7 @@ private static TextLineRun Create(TextSource textSource, StringRange stringRange
return new TextLineRun(textRun.Length, textRun)
{
IsEmbedded = true,
_glyphWidths = new GlyphWidths(
stringRange,
textRun.Properties.Typeface.GlyphTypeface,
textRun.Properties.FontSize),
_glyphWidths = new double[] { width },
// 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 All @@ -125,7 +125,7 @@ private static TextLineRun Create(TextSource textSource, StringRange stringRange
throw new NotSupportedException("Unsupported run type");
}

private static TextLineRun CreateRunForEol(TextSource textSource, StringRange stringRange, TextRun textRun, int index)
private static TextLineRun CreateRunForSpecialChars(TextSource textSource, StringRange stringRange, TextRun textRun, int index, TextParagraphProperties paragraphProperties)
{
switch (stringRange[0])
{
Expand All @@ -150,33 +150,29 @@ private static TextLineRun CreateRunForEol(TextSource textSource, StringRange st
case '\n':
return new TextLineRun(1, textRun) { IsEnd = true };
case '\t':
return CreateRunForTab(textRun);
return CreateRunForTab(textRun, paragraphProperties);
default:
return null;
}
}

private static TextLineRun CreateRunForTab(TextRun textRun)
private static TextLineRun CreateRunForTab(TextRun textRun, TextParagraphProperties paragraphProperties)
{
var spaceRun = new TextCharacters(" ", textRun.Properties);
var stringRange = spaceRun.StringRange;
var run = new TextLineRun(1, spaceRun)
var tabRun = new TextCharacters(TabString, textRun.Properties);
var stringRange = tabRun.StringRange;
var run = new TextLineRun(1, tabRun)
{
IsTab = true,
StringRange = stringRange,
// TODO: get from para props
Width = 40
Width = paragraphProperties.DefaultIncrementalTab
};

run._glyphWidths = new GlyphWidths(
run.StringRange,
run.Typeface.GlyphTypeface,
run.FontSize);
run._glyphWidths = new double[] { run.Width };

return run;
}

internal static TextLineRun CreateRunForText(StringRange stringRange, TextRun textRun, double widthLeft, bool emergencyWrap, bool breakOnTabs)
internal static TextLineRun CreateRunForText(StringRange stringRange, TextRun textRun, double widthLeft, bool emergencyWrap, bool breakOnTabs, TextParagraphProperties paragraphProperties)
{
var run = new TextLineRun
{
Expand Down Expand Up @@ -204,7 +200,8 @@ internal static TextLineRun CreateRunForText(StringRange stringRange, TextRun te
run._glyphWidths = new GlyphWidths(
run.StringRange,
run.Typeface.GlyphTypeface,
run.FontSize);
run.FontSize,
paragraphProperties.DefaultIncrementalTab);

return run;
}
Expand Down Expand Up @@ -281,7 +278,7 @@ public bool UpdateTrailingInfo(TrailingInfo trailing)
{
while (index > 0 && IsSpace(StringRange[index - 1]))
{
trailing.SpaceWidth += _glyphWidths.GetAt(index - 1);
trailing.SpaceWidth += _glyphWidths[index - 1];
index--;
trailing.Count++;
}
Expand All @@ -304,7 +301,7 @@ public double GetDistanceFromCharacter(int index)
double distance = 0;
for (var i = 0; i < index; i++)
{
distance += _glyphWidths.GetAt(i);
distance += _glyphWidths[i];
}

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

class GlyphWidths
class GlyphWidths : IReadOnlyList<double>
{
private const double NOT_CALCULATED_YET = -1;
private double[] _glyphWidths;
private GlyphTypeface _typeFace;
private StringRange _range;
private double _scale;
private double _tabSize;

internal GlyphWidths(StringRange range, GlyphTypeface typeFace, double fontSize)
public int Count => _glyphWidths.Length;
public double this[int index] => GetAt(index);

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

InitGlyphWidths();
}

internal double GetAt(int index)
double GetAt(int index)
{
if (_glyphWidths.Length == 0)
return 0;

if (_range[index] == '\t')
return _tabSize;

if (_glyphWidths[index] == NOT_CALCULATED_YET)
_glyphWidths[index] = MeasureGlyphAt(index);

Expand Down Expand Up @@ -390,6 +398,17 @@ void InitGlyphWidths()

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

IEnumerator<double> IEnumerable<double>.GetEnumerator()
{
foreach (double value in _glyphWidths)
yield return value;
}

IEnumerator IEnumerable.GetEnumerator()
{
return _glyphWidths.GetEnumerator();
}
}
}
}
3 changes: 3 additions & 0 deletions test/AvaloniaEdit.Tests/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("AvaloniaEdit.Tests")]
37 changes: 37 additions & 0 deletions test/AvaloniaEdit.Tests/AvaloniaMocks/MockFormattedTextImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Avalonia;
using Avalonia.Media;
using Avalonia.Platform;

using System.Collections.Generic;

namespace AvaloniaEdit.Tests.AvaloniaMocks
{
internal class MockFormattedTextImpl : IFormattedTextImpl
{
Size IFormattedTextImpl.Constraint => new Size(0, 0);

Rect IFormattedTextImpl.Bounds => new Rect(0, 0, 0, 0);

string IFormattedTextImpl.Text => throw new System.NotImplementedException();

IEnumerable<FormattedTextLine> IFormattedTextImpl.GetLines()
{
return null;
}

TextHitTestResult IFormattedTextImpl.HitTestPoint(Point point)
{
return null;
}

Rect IFormattedTextImpl.HitTestTextPosition(int index)
{
return Rect.Empty;
}

IEnumerable<Rect> IFormattedTextImpl.HitTestTextRange(int index, int length)
{
return new Rect[] { };
}
}
}
Loading