diff --git a/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems b/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems
index d216565f..e872b56b 100644
--- a/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems
+++ b/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems
@@ -41,6 +41,7 @@
+
AvaloniaDesigner.xaml
diff --git a/AvaloniaVS.Shared/IntelliSense/XamlCompletion.cs b/AvaloniaVS.Shared/IntelliSense/XamlCompletion.cs
index fccda632..0b589636 100644
--- a/AvaloniaVS.Shared/IntelliSense/XamlCompletion.cs
+++ b/AvaloniaVS.Shared/IntelliSense/XamlCompletion.cs
@@ -21,7 +21,8 @@ public XamlCompletion(Completion completion)
completion.InsertText,
completion.Description,
GetImage(completion.Kind),
- completion.Kind.ToString())
+ completion.Kind.ToString(),
+ suffix: string.IsNullOrWhiteSpace(completion.Suffix) ? string.Empty : $"({completion.Suffix})")
{
if (completion.RecommendedCursorOffset.HasValue)
{
@@ -29,6 +30,23 @@ public XamlCompletion(Completion completion)
}
Kind = completion.Kind;
+ DeleteTextOffset = completion.DeleteTextOffset;
+ }
+
+ public int? DeleteTextOffset { get; }
+
+ public override string InsertionText
+ {
+ get
+ {
+ if (HasFlag(Kind, CompletionKind.Name) && !string.IsNullOrEmpty(Suffix))
+ {
+ return $"{Suffix.Substring(1,Suffix.Length-2)}#{base.InsertionText}";
+ }
+ return base.InsertionText;
+ }
+
+ set => base.InsertionText = value;
}
public int CursorOffset { get; }
@@ -49,7 +67,6 @@ private static ImageMoniker GetImage(CompletionKind kind)
{
LoadImages();
}
-
if (HasFlag(kind, CompletionKind.DataProperty))
{
return s_images[(int)CompletionKind.DataProperty];
@@ -62,13 +79,20 @@ private static ImageMoniker GetImage(CompletionKind kind)
{
return s_images[(int)CompletionKind.Enum];
}
-
- return s_images[(int)kind];
-
- bool HasFlag(CompletionKind test, CompletionKind expected)
+ else if (HasFlag(kind, CompletionKind.Selector))
+ {
+ return s_images[(int)CompletionKind.Enum];
+ }
+ else if (HasFlag(kind, CompletionKind.Name))
{
- return (test & expected) == expected;
+ return s_images[(int)CompletionKind.Class];
}
+ return s_images[(int)kind];
+ }
+
+ private static bool HasFlag(CompletionKind test, CompletionKind expected)
+ {
+ return (test & expected) == expected;
}
private static void LoadImages()
@@ -90,6 +114,7 @@ private static void LoadImages()
s_images[(int)CompletionKind.MarkupExtension] = KnownMonikers.Namespace;
s_images[(int)CompletionKind.DataProperty] = KnownMonikers.DatabaseProperty;
s_images[(int)CompletionKind.TargetTypeClass] = KnownMonikers.ClassPublic;
+ s_images[(int)CompletionKind.Selector] = KnownMonikers.Namespace;
}
}
}
diff --git a/AvaloniaVS.Shared/IntelliSense/XamlCompletionCommandHandler.cs b/AvaloniaVS.Shared/IntelliSense/XamlCompletionCommandHandler.cs
index 220a1d81..4f76353d 100644
--- a/AvaloniaVS.Shared/IntelliSense/XamlCompletionCommandHandler.cs
+++ b/AvaloniaVS.Shared/IntelliSense/XamlCompletionCommandHandler.cs
@@ -81,9 +81,7 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv
return VSConstants.S_OK;
}
}
-
var result = _nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
-
if (HandleSessionStart(c))
{
return VSConstants.S_OK;
@@ -107,7 +105,7 @@ private bool HandleSessionStart(char c)
{
if (_session == null || _session.IsDismissed)
{
- if (TriggerCompletion() && c != '<' && c != '.' && c != ' ')
+ if (TriggerCompletion() && c != '<' && c != '.' && c != ' ' && c != '[' && c != '(' && c != '|' && c != '#' && c != '/')
{
_session?.Filter();
}
@@ -127,7 +125,6 @@ private bool HandleSessionStart(char c)
return true;
}
}
-
return false;
}
@@ -159,7 +156,7 @@ private bool HandleSessionCompletion(char c)
// So we only trigger on ' ' or '\t', and swallow that so it doesn't get
// inserted into the text buffer
if (_session != null && !_session.IsDismissed)
- {
+ {
var text = line.Snapshot.GetText(start, end - start);
if (text.Contains("xmlns"))
@@ -190,14 +187,28 @@ private bool HandleSessionCompletion(char c)
// Also adding '#' for Selectors
- if (char.IsWhiteSpace(c) || c == '\'' || c == '"' || c == '=' || c == '>' || c == '.' || c == '#')
+ if (char.IsWhiteSpace(c)
+ || c == '\'' || c == '"' || c == '=' || c == '>' || c == '.'
+ || c == '#' || c == ')' || c == ']')
{
if (_session != null && !_session.IsDismissed &&
_session.SelectedCompletionSet.SelectionStatus.IsSelected)
{
var selected = _session.SelectedCompletionSet.SelectionStatus.Completion as XamlCompletion;
+ var bufferPos = _textView.Caret.Position.BufferPosition;
+
_session.Commit();
+
+ if (selected.DeleteTextOffset is int rof)
+ {
+ var newCursorPos = bufferPos.Add(rof);
+ SnapshotSpan deleteSpan = newCursorPos < bufferPos
+ ? new(newCursorPos, -rof)
+ : new(bufferPos, rof);
+ _textView.TextBuffer.Delete(deleteSpan);
+ }
+
if (selected?.CursorOffset > 0)
{
// Offset the cursor if necessary e.g. to place it within the quotation
@@ -215,23 +226,24 @@ private bool HandleSessionCompletion(char c)
var state = parser.State;
bool skip = c != '>';
- if (state == XmlParser.ParserState.StartElement &&
+ if (state == XmlParser.ParserState.StartElement &&
(c == '.' || c == ' '))
{
// Don't swallow the '.' or ' ' if this is an Xml element, like
- // Window.Resources. However do swallow tab
+ // Window.Resources. However do swallow tab
skip = false;
}
- if (state == XmlParser.ParserState.AttributeValue ||
+ if (state == XmlParser.ParserState.AttributeValue ||
state == XmlParser.ParserState.AfterAttributeValue)
{
+ var isSelector = parser.AttributeName?.Equals("Selector") == true;
if (char.IsWhiteSpace(c))
{
// For most xml attributes, swallow the space upon completion
// For selector, allow it to go into the buffer
// Also if in a markupextention
- skip = !(parser.AttributeName?.Equals("Selector") == true);
+ skip = !(isSelector && c != '\n' && c != '\t');
// If we're in a markup extension, only swallow the space if the
// completion isn't on the Markup extension
@@ -286,11 +298,14 @@ private bool HandleSessionCompletion(char c)
skip = false;
}
+ var lastInsertionChar = (selected.InsertionText?.Length ?? 0) > 0
+ ? selected.InsertionText[selected.InsertionText.Length - 1]
+ : default;
+
// Cases like {Binding Path= result in {Binding Path==
// as the completion includes the '=', if the entered char
// is the same as the last char here, swallow the entered char
- if (!skip && (selected.InsertionText?.Length > 0 &&
- selected.InsertionText[selected.InsertionText.Length - 1] == c))
+ if (!skip && lastInsertionChar == c)
{
skip = true;
@@ -300,6 +315,12 @@ private bool HandleSessionCompletion(char c)
if (c == '=')
TriggerCompletion();
}
+ else if (isSelector && lastInsertionChar is '=' or '.')
+ {
+ // Trigger Selector property Value Completation
+ if (c is not '=' or '.')
+ TriggerCompletion();
+ }
}
else if (state != XmlParser.ParserState.StartElement)
{
@@ -319,7 +340,7 @@ private bool HandleSessionCompletion(char c)
var parser = XmlParser.Parse(_textView.TextSnapshot.GetText().AsMemory(), 0, end);
var state = parser.State;
- if (state == XmlParser.ParserState.AttributeValue &&
+ if (state == XmlParser.ParserState.AttributeValue &&
parser.AttributeName?.Equals("Selector") == true)
{
// Force new session to start to suggest pseudoclasses
@@ -327,6 +348,17 @@ private bool HandleSessionCompletion(char c)
return false;
}
}
+ else if (c == '(' && _session?.IsDismissed == false)
+ {
+ var parser = XmlParser.Parse(_textView.TextSnapshot.GetText().AsMemory(), 0, end);
+ var state = parser.State;
+ if ((state == XmlParser.ParserState.AttributeValue || state == XmlParser.ParserState.AfterAttributeValue)
+ && parser.AttributeName?.Equals("Selector") == true)
+ {
+ _session.Dismiss();
+ return false;
+ }
+ }
else if (c == '{' && (_session != null && !_session.IsDismissed))
{
var parser = XmlParser.Parse(_textView.TextSnapshot.GetText().AsMemory(), 0, end);
diff --git a/AvaloniaVS.Shared/TextViewExtensions.cs b/AvaloniaVS.Shared/TextViewExtensions.cs
new file mode 100644
index 00000000..3d456e92
--- /dev/null
+++ b/AvaloniaVS.Shared/TextViewExtensions.cs
@@ -0,0 +1,28 @@
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text;
+using System.Linq;
+
+namespace AvaloniaVS;
+
+internal static class TextViewExtensions
+{
+ public static NormalizedSnapshotSpanCollection GetSpanInView(this ITextView textView, SnapshotSpan span)
+=> textView.BufferGraph.MapUpToSnapshot(span, SpanTrackingMode.EdgeInclusive, textView.TextSnapshot);
+
+ public static void SetSelection(
+ this ITextView textView, VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint)
+ {
+ var isReversed = activePoint < anchorPoint;
+ var start = isReversed ? activePoint : anchorPoint;
+ var end = isReversed ? anchorPoint : activePoint;
+ SetSelection(textView, new SnapshotSpan(start.Position, end.Position), isReversed);
+ }
+
+ public static void SetSelection(
+ this ITextView textView, SnapshotSpan span, bool isReversed = false)
+ {
+ var spanInView = textView.GetSpanInView(span).Single();
+ textView.Selection.Select(spanInView, isReversed);
+ textView.Caret.MoveTo(isReversed ? spanInView.Start : spanInView.End);
+ }
+}
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine.DnlibMetadataProvider/Wrappers.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine.DnlibMetadataProvider/Wrappers.cs
index c2babf19..77068c99 100644
--- a/CompletionEngine/Avalonia.Ide.CompletionEngine.DnlibMetadataProvider/Wrappers.cs
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine.DnlibMetadataProvider/Wrappers.cs
@@ -36,6 +36,9 @@ public IEnumerable InternalsVisibleTo
public Stream GetManifestResourceStream(string name)
=> _asm.ManifestModule.Resources.FindEmbeddedResource(name).CreateReader().AsStream();
+ public string PublicKey
+ => _asm.PublicKey.ToString();
+
public override string ToString() => Name;
}
@@ -81,6 +84,7 @@ private TypeWrapper(TypeDef type)
public bool IsGeneric => _type.HasGenericParameters;
public bool IsAbstract => _type.IsAbstract && !_type.IsSealed;
public bool IsInternal => _type.IsNotPublic && !_type.IsNestedPrivate;
+
public IEnumerable EnumValues
{
get
@@ -119,6 +123,22 @@ public IEnumerable Pseudoclasses
public override string ToString() => Name;
public IEnumerable NestedTypes =>
_type.HasNestedTypes ? _type.NestedTypes.Select(t => new TypeWrapper(t)) : Array.Empty();
+
+ public IEnumerable<(ITypeInformation Type, string Name)> TemplateParts
+ {
+ get
+ {
+ var attributes = _type.CustomAttributes
+ .Where(a => a.TypeFullName.EndsWith("TemplatePartAttribute", StringComparison.OrdinalIgnoreCase)
+ && a.HasConstructorArguments);
+ foreach (var attr in attributes)
+ {
+ var name = attr.ConstructorArguments[0].Value.ToString()!;
+ ITypeInformation type = TypeWrapper.FromDef(((ClassSig)attr.ConstructorArguments[1].Value).TypeDef)!;
+ yield return (type, name);
+ }
+ }
+ }
}
internal class CustomAttributeWrapper : ICustomAttributeInformation
@@ -150,11 +170,12 @@ public ConstructorArgumentWrapper(CAArgument ca)
internal class PropertyWrapper : IPropertyInformation
{
private readonly PropertyDef _prop;
- private readonly Func _isVisbleTo;
+ private readonly Func _isVisbleTo;
public PropertyWrapper(PropertyDef prop)
{
Name = prop.Name;
+
var setMethod = prop.SetMethod;
var getMethod = prop.GetMethod;
@@ -169,7 +190,7 @@ public PropertyWrapper(PropertyDef prop)
{
type = getMethod.ReturnType;
}
- else if(setMethod is not null)
+ else if (setMethod is not null)
{
type = setMethod.Parameters[setMethod.IsStatic ? 0 : 1].Type;
}
@@ -180,7 +201,7 @@ public PropertyWrapper(PropertyDef prop)
TypeFullName = type.FullName;
QualifiedTypeFullName = type.AssemblyQualifiedName;
-
+
_prop = prop;
if (HasPublicGetter || HasPublicSetter)
{
@@ -188,36 +209,52 @@ public PropertyWrapper(PropertyDef prop)
}
else
{
- _isVisbleTo = static (property, targetAssemblyName) =>
+ _isVisbleTo = static (property, targetAssembly) =>
{
if (property.DeclaringType.DefinitionAssembly is AssemblyDef assembly)
{
- if (string.Equals(targetAssemblyName, assembly.GetFullNameWithPublicKeyToken(), StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(targetAssembly.AssemblyName, assembly.GetFullNameWithPublicKeyToken(), StringComparison.OrdinalIgnoreCase))
{
return true;
}
- var nameParts = targetAssemblyName.Split(',');
var enumerator = assembly.GetVisibleTo()?.GetEnumerator();
+ var targetPublicKey = targetAssembly.PublicKey;
+ var targetName = targetAssembly.Name;
while (enumerator?.MoveNext() == true)
{
- if (string.Equals(targetAssemblyName, enumerator.Current, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- else
+ var current = enumerator.Current;
+ if (current.StartsWith(targetName, StringComparison.OrdinalIgnoreCase))
{
- var attParts = enumerator.Current.Split(',');
- var min = Math.Min(attParts.Length, nameParts.Length);
- var i = 0;
- for (; i < min && attParts[i] == nameParts[i]; i++)
+ if (!string.IsNullOrEmpty(targetPublicKey))
{
-
- }
- if (i == min)
- {
- return true;
+ var startIndex = current.IndexOf("PublicKey", StringComparison.OrdinalIgnoreCase);
+ if (startIndex > -1)
+ {
+ startIndex += 9;
+ if (startIndex > current.Length)
+ {
+ return false;
+ }
+ while (startIndex < current.Length && current[startIndex] is ' ' or '=')
+ {
+ startIndex++;
+ }
+
+ if (targetPublicKey.Length != current.Length - startIndex)
+ {
+ return false;
+ }
+ for (int i = startIndex; i < current.Length; i++)
+ {
+ if (current[i] != targetPublicKey[i - startIndex])
+ {
+ return false;
+ }
+ }
+ }
}
+ return true;
}
}
}
@@ -235,8 +272,8 @@ public PropertyWrapper(PropertyDef prop)
public string QualifiedTypeFullName { get; }
public string Name { get; }
- public bool IsVisbleTo(string assemblyName) =>
- _isVisbleTo(_prop, assemblyName);
+ public bool IsVisbleTo(IAssemblyInformation assembly) =>
+ _isVisbleTo(_prop, assembly);
public override string ToString() => Name;
}
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/IAssemblyInformation.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/IAssemblyInformation.cs
index 4aa1c3a7..c355d661 100644
--- a/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/IAssemblyInformation.cs
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/IAssemblyInformation.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.IO;
+#nullable enable
namespace Avalonia.Ide.CompletionEngine.AssemblyMetadata;
@@ -12,6 +13,7 @@ public interface IAssemblyInformation
Stream GetManifestResourceStream(string name);
IEnumerable InternalsVisibleTo { get; }
string AssemblyName { get; }
+ string PublicKey { get; }
}
public interface ICustomAttributeInformation
@@ -38,6 +40,7 @@ public interface ITypeInformation
IEnumerable Events { get; }
IEnumerable Fields { get; }
IEnumerable Pseudoclasses { get; }
+ IEnumerable<(ITypeInformation Type,string Name)> TemplateParts { get; }
bool IsEnum { get; }
bool IsStatic { get; }
@@ -86,7 +89,7 @@ public interface IPropertyInformation
string TypeFullName { get; }
string QualifiedTypeFullName { get; }
string Name { get; }
- bool IsVisbleTo(string assemblyName);
+ bool IsVisbleTo(IAssemblyInformation assembly);
}
public interface IEventInformation
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/Metadata.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/Metadata.cs
index 55e89247..027f2229 100644
--- a/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/Metadata.cs
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/Metadata.cs
@@ -6,9 +6,17 @@ namespace Avalonia.Ide.CompletionEngine;
public class Metadata
{
- public Dictionary> Namespaces { get; } = new Dictionary>();
+ readonly Dictionary> _namespaces = new();
+ readonly Dictionary _inverseNamespace = new();
- public void AddType(string ns, MetadataType type) => Namespaces.GetOrCreate(ns)[type.Name] = type;
+ public IReadOnlyDictionary> Namespaces => _namespaces;
+ public IReadOnlyDictionary InverseNamespace => _inverseNamespace;
+
+ public void AddType(string ns, MetadataType type)
+ {
+ _namespaces.GetOrCreate(ns)[type.Name] = type;
+ _inverseNamespace[type.FullName] = ns;
+ }
}
[DebuggerDisplay("{Name}")]
@@ -20,6 +28,9 @@ public record MetadataType(string Name)
public bool HasHintValues { get; set; }
public string[]? HintValues { get; set; }
+ public string[] PseudoClasses { get; set; } = Array.Empty();
+ public bool HasPseudoClasses { get; set; }
+
//assembly, type, property
public Func? IsValidForXamlContextFunc { get; set; }
//assembly, type, property
@@ -37,6 +48,10 @@ public record MetadataType(string Name)
public bool IsGeneric { get; set; }
public bool IsXamlDirective { get; set; }
public string? AssemblyQualifiedName { get; set; }
+ public bool IsNullable { get; init; }
+ public MetadataType? UnderlyingType { get; init; }
+ public IEnumerable<(MetadataType Type,string Name)> TemplateParts { get; internal set; } =
+ Array.Empty<(MetadataType Type, string Name)>();
}
public enum MetadataTypeCtorArgument
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/MetadataConverter.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/MetadataConverter.cs
index 318e12e8..ec64b6a8 100644
--- a/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/MetadataConverter.cs
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/AssemblyMetadata/MetadataConverter.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
using System.Xml.Linq;
using Avalonia.Ide.CompletionEngine.AssemblyMetadata;
@@ -27,7 +28,12 @@ public static class MetadataConverter
"Avalonia.Markup.Xaml.Styling.StyleInclude,",
"Avalonia.Markup.Xaml.Styling.StyleIncludeExtension,",
};
-
+ private readonly static Regex extractType = new Regex(
+ "System.Nullable`1<(?.*)>|System.Nullable`1\\[\\[(?.*)]].*",
+ RegexOptions.CultureInvariant
+ | RegexOptions.Compiled
+ );
internal static bool IsMarkupExtension(ITypeInformation type)
{
@@ -87,13 +93,14 @@ public static Metadata ConvertMetadata(IMetadataReaderSession provider)
var resourceUrls = new List();
var avaresValues = new List();
var pseudoclasses = new HashSet();
+ var typepseudoclasses = new HashSet();
var ignoredResExt = new[] { ".resources", ".rd.xml", "!AvaloniaResources" };
bool skipRes(string res) => ignoredResExt.Any(r => res.EndsWith(r, StringComparison.OrdinalIgnoreCase));
PreProcessTypes(types, metadata);
-
+ var targetAssembly = provider.Assemblies.First();
foreach (var asm in provider.Assemblies)
{
var aliases = new Dictionary();
@@ -105,23 +112,53 @@ public static Metadata ConvertMetadata(IMetadataReaderSession provider)
if (asm.AssemblyName == provider.TargetAssemblyName || asm.InternalsVisibleTo.Any(att =>
{
- var attParts = att.Split(',');
- var nameParts = provider.TargetAssemblyName.Split(',');
- var min = Math.Min(attParts.Length, nameParts.Length);
- var i = 0;
- for (; i < min && string.Equals(attParts[i], nameParts[i], StringComparison.OrdinalIgnoreCase); i++)
+ var endNameIndex = att.IndexOf(',');
+ var assemblyName = att;
+ var targetPublicKey = targetAssembly.PublicKey;
+ if (endNameIndex > 0)
{
-
+ assemblyName = att.Substring(0, endNameIndex);
+ }
+ if (assemblyName == targetAssembly.Name)
+ {
+ if (endNameIndex == -1)
+ {
+ return true;
+ }
+ var publicKeyIndex = att.IndexOf("PublicKey", endNameIndex, StringComparison.OrdinalIgnoreCase);
+ if (publicKeyIndex > 0)
+ {
+ publicKeyIndex += 9;
+ if (publicKeyIndex > att.Length)
+ {
+ return false;
+ }
+ while (publicKeyIndex < att.Length && att[publicKeyIndex] is ' ' or '=')
+ {
+ publicKeyIndex++;
+ }
+ if (targetPublicKey.Length == att.Length - publicKeyIndex)
+ {
+ for (int i = publicKeyIndex; i < att.Length; i++)
+ {
+ if (att[i] != targetPublicKey[i - publicKeyIndex])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
}
- return i == min;
+ return false;
}))
{
typeFilter = type => type.Name != "" && !type.IsInterface && !type.IsAbstract;
}
- var asmTypes = asm.Types.ToArray();
+ var asmTypes = asm.Types.Where(typeFilter).ToArray();
- foreach (var type in asmTypes.Where(typeFilter))
+ foreach (var type in asmTypes)
{
var mt = types[type.AssemblyQualifiedName] = ConvertTypeInfomation(type);
typeDefs[mt] = type;
@@ -146,7 +183,8 @@ public static Metadata ConvertMetadata(IMetadataReaderSession provider)
resourceUrls.AddRange(asm.ManifestResourceNames.Where(r => !skipRes(r)).Select(r => $"resm:{r}?assembly={asm.Name}"));
}
- foreach (var type in types.Values)
+ var at = types.Values.ToArray();
+ foreach (var type in at)
{
typeDefs.TryGetValue(type, out var typeDef);
@@ -164,21 +202,29 @@ public static Metadata ConvertMetadata(IMetadataReaderSession provider)
}
int level = 0;
+ typepseudoclasses.Clear();
+
+ type.TemplateParts = (typeDef?.TemplateParts ??
+ Array.Empty<(ITypeInformation, string)>())
+ .Select(item => (Type: ConvertTypeInfomation(item.Type), item.Name));
+
while (typeDef != null)
{
- var typePseudoclasses = typeDef.Pseudoclasses;
-
- foreach (var pc in typePseudoclasses)
+ foreach (var pc in typeDef.Pseudoclasses)
{
+ typepseudoclasses.Add(pc);
pseudoclasses.Add(pc);
}
var currentType = types.GetValueOrDefault(typeDef.AssemblyQualifiedName);
foreach (var prop in typeDef.Properties)
{
- if (!prop.IsVisbleTo(provider.TargetAssemblyName))
+ if (!prop.IsVisbleTo(targetAssembly))
continue;
- var p = new MetadataProperty(prop.Name, types.GetValueOrDefault(prop.TypeFullName, prop.QualifiedTypeFullName),
+
+ var propertyType = GetType(types, prop.TypeFullName, prop.QualifiedTypeFullName);
+
+ var p = new MetadataProperty(prop.Name, propertyType,
currentType, false, prop.IsStatic, prop.HasPublicGetter,
prop.HasPublicSetter);
@@ -187,28 +233,14 @@ public static Metadata ConvertMetadata(IMetadataReaderSession provider)
foreach (var eventDef in typeDef.Events)
{
- var e = new MetadataEvent(eventDef.Name, types.GetValueOrDefault(eventDef.TypeFullName, eventDef.QualifiedTypeFullName),
+ var e = new MetadataEvent(eventDef.Name, GetType(types, eventDef.TypeFullName, eventDef.QualifiedTypeFullName),
types.GetValueOrDefault(typeDef.FullName, typeDef.AssemblyQualifiedName), false);
type.Events.Add(e);
}
- //check for attached properties only on top level
if (level == 0)
{
- foreach (var methodDef in typeDef.Methods)
- {
- if (methodDef.Name.StartsWith("Set", StringComparison.OrdinalIgnoreCase) && methodDef.IsStatic && methodDef.IsPublic
- && methodDef.Parameters.Count == 2)
- {
- var name = methodDef.Name.Substring(3);
- type.Properties.Add(new MetadataProperty(name,
- types.GetValueOrDefault(methodDef.Parameters[1].TypeFullName, methodDef.Parameters[1].QualifiedTypeFullName),
- types.GetValueOrDefault(typeDef.FullName, typeDef.AssemblyQualifiedName),
- true, false, true, true));
- }
- }
-
foreach (var fieldDef in typeDef.Fields)
{
if (fieldDef.IsStatic && fieldDef.IsPublic)
@@ -226,6 +258,44 @@ public static Metadata ConvertMetadata(IMetadataReaderSession provider)
types.GetValueOrDefault(typeDef.FullName, typeDef.AssemblyQualifiedName),
true));
}
+ else if (fieldDef.Name.EndsWith("Property", StringComparison.OrdinalIgnoreCase)
+ && fieldDef.ReturnTypeFullName.StartsWith("Avalonia.AttachedProperty`1")
+ )
+ {
+ var name = fieldDef.Name.Substring(0, fieldDef.Name.Length - "Property".Length);
+
+ IMethodInformation? setMethod = null;
+ IMethodInformation? getMethod = null;
+
+ foreach (var methodDef in typeDef.Methods)
+ {
+ if (methodDef.Name.StartsWith("Set", StringComparison.OrdinalIgnoreCase) && methodDef.IsStatic && methodDef.IsPublic
+ && methodDef.Parameters.Count == 2)
+ {
+ setMethod = methodDef;
+ }
+ if (methodDef.IsStatic
+ && methodDef.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase)
+ && methodDef.IsPublic
+ && methodDef.Parameters.Count == 1
+ && !string.IsNullOrEmpty(methodDef.ReturnTypeFullName)
+ )
+ {
+ getMethod = methodDef;
+ }
+ }
+
+ if (getMethod is not null)
+ {
+ type.Properties.Add(new MetadataProperty(name,
+ Type: types.GetValueOrDefault(getMethod.ReturnTypeFullName, getMethod.QualifiedReturnTypeFullName),
+ DeclaringType: types.GetValueOrDefault(typeDef.FullName, typeDef.AssemblyQualifiedName),
+ IsAttached: true,
+ IsStatic: false,
+ HasGetter: true,
+ HasSetter: setMethod is not null));
+ }
+ }
else if (type.IsStatic)
{
type.Properties.Add(new MetadataProperty(fieldDef.Name, null, type, false, true, true, false));
@@ -247,6 +317,11 @@ public static Metadata ConvertMetadata(IMetadataReaderSession provider)
type.HasAttachedEvents = type.Events.Any(e => e.IsAttached);
type.HasStaticGetProperties = type.Properties.Any(p => p.IsStatic && p.HasGetter);
type.HasSetProperties = type.Properties.Any(p => !p.IsStatic && p.HasSetter);
+ if (typepseudoclasses.Count > 0)
+ {
+ type.HasPseudoClasses = true;
+ type.PseudoClasses = typepseudoclasses.ToArray();
+ }
if (ctors?.Any() == true)
{
@@ -273,6 +348,35 @@ public static Metadata ConvertMetadata(IMetadataReaderSession provider)
PostProcessTypes(types, metadata, resourceUrls, avaresValues, pseudoclasses);
+ MetadataType? GetType(Dictionary types, params string[] keys)
+ {
+ MetadataType? type = default;
+ foreach (var key in keys)
+ {
+ if (types.TryGetValue(key, out type))
+ {
+ break;
+ }
+ else if (key.StartsWith("System.Nullable`1", StringComparison.OrdinalIgnoreCase))
+ {
+ var typeName = extractType.Match(key);
+ if (typeName.Success && types.TryGetValue(typeName.Groups[1].Value, out type))
+ {
+ type = new MetadataType(key)
+ {
+ AssemblyQualifiedName = type.AssemblyQualifiedName,
+ FullName = $"System.Nullable`1<{type.FullName}>",
+ IsNullable = true,
+ UnderlyingType = type
+ };
+ types.Add(key, type);
+ break;
+ }
+ }
+ }
+ return type;
+ }
+
return metadata;
}
@@ -436,6 +540,12 @@ private static void PreProcessTypes(Dictionary types, Meta
HasHintValues = true,
HintValues = new[] { "True", "False" }
}),
+ new MetadataType("System.Nullable`1")
+ {
+ HasHintValues = true,
+ IsNullable = true,
+ UnderlyingType = boolType,
+ },
new MetadataType(typeof(System.Uri).FullName!),
(typeType = new MetadataType(typeof(System.Type).FullName!)),
new MetadataType("Avalonia.Media.IBrush"),
@@ -699,6 +809,7 @@ IEnumerable filterLocalRes(MetadataType type, string? currentAssemblyNam
brushType.HintValues = brushes.Properties.Where(p => p.IsStatic && p.HasGetter).Select(p => p.Name).ToArray();
}
+ //TODO: Remove
if (avaloniaBaseType.TryGetValue("Avalonia.Styling.Selector", out MetadataType? styleSelector))
{
styleSelector.HasHintValues = true;
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Avalonia.Ide.CompletionEngine.csproj b/CompletionEngine/Avalonia.Ide.CompletionEngine/Avalonia.Ide.CompletionEngine.csproj
index 77bea80f..51eb2590 100644
--- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Avalonia.Ide.CompletionEngine.csproj
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Avalonia.Ide.CompletionEngine.csproj
@@ -19,4 +19,9 @@
+
+
+
+
+
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/Completion.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/Completion.cs
index a7f4adb0..c1c26151 100644
--- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/Completion.cs
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/Completion.cs
@@ -31,14 +31,31 @@ public enum CompletionKind
///
/// xmlns list in visual studio (uses enum icon instead of namespace icon)
///
- VS_XMLNS = 0x800
+ VS_XMLNS = 0x800,
+
+ Selector = 0x1000,
+ Name = 0x2000,
}
-public record Completion(string DisplayText, string InsertText, string Description, CompletionKind Kind, int? RecommendedCursorOffset = null)
+public record Completion(string DisplayText,
+ string InsertText,
+ string Description,
+ CompletionKind Kind,
+ int? RecommendedCursorOffset = null,
+ string? Suffix = null,
+ int? DeleteTextOffset = null
+ )
{
public override string ToString() => DisplayText;
- public Completion(string insertText, CompletionKind kind) : this(insertText, insertText, insertText, kind)
+ public Completion(string insertText, CompletionKind kind, string? suffix = default) :
+ this(insertText, insertText, insertText, kind, Suffix: suffix)
+ {
+
+ }
+
+ public Completion(string displayText, string insertText, CompletionKind kind, string? suffix = default) :
+ this(displayText, insertText, displayText, kind)
{
}
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs
index 85f96d70..f2e7a10d 100644
--- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs
@@ -1,4 +1,5 @@
-using System;
+#nullable enable
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -17,6 +18,9 @@ private class MetadataHelper
private Dictionary? _types;
private string? _currentAssemblyName;
+ private static Regex? _findElementByNameRegex;
+ internal static Regex FindElementByNameRegex => _findElementByNameRegex ??=
+ new($"\\s(?:(x\\:)?Name)=\"(?[\\w\\:\\s\\|\\.]+)\"", RegexOptions.Compiled);
public void SetMetadata(Metadata metadata, string xml, string? currentAssemblyName = null)
{
@@ -49,7 +53,6 @@ public void SetMetadata(Metadata metadata, string xml, string? currentAssemblyNa
var types = new Dictionary();
foreach (var alias in Aliases.Concat(new[] { new KeyValuePair("", "") }))
{
-
var aliasValue = alias.Value ?? "";
if (!string.IsNullOrEmpty(_currentAssemblyName) && aliasValue.StartsWith("clr-namespace:") && !aliasValue.Contains(";assembly="))
@@ -119,15 +122,24 @@ public IEnumerable FilterPropertyNames(string typeName, string? propName
return MetadataHelper.FilterPropertyNames(t, propName, attached, hasSetter, staticGetter);
}
- public static IEnumerable FilterPropertyNames(MetadataType? t, string? propName,
+ public static IEnumerable FilterPropertyNames(MetadataType? t,
+ string? propName,
bool? attached,
bool hasSetter,
bool staticGetter = false)
{
+ return FilterProperty(t, propName, attached, hasSetter, staticGetter).Select(p => p.Name);
+ }
+ public static IEnumerable FilterProperty(MetadataType? t, string? propName,
+ bool? attached,
+ bool hasSetter,
+ bool staticGetter = false
+ )
+ {
propName ??= "";
if (t == null)
- return Array.Empty();
+ return Array.Empty();
var e = t.Properties.Where(p => p.Name.StartsWith(propName, StringComparison.OrdinalIgnoreCase) && (hasSetter ? p.HasSetter : p.HasGetter));
@@ -138,9 +150,10 @@ public static IEnumerable FilterPropertyNames(MetadataType? t, string? p
else
e = e.Where(p => !p.IsStatic);
- return e.Select(p => p.Name);
+ return e;
}
+
public IEnumerable FilterEventNames(string typeName, string? propName,
bool attached)
{
@@ -257,7 +270,7 @@ private static Dictionary GetNamespaceAliases(string xml)
return new Completion(kvp.Key, CompletionKind.Class);
}));
- }
+ }
}
else if (state.State == XmlParser.ParserState.InsideElement ||
state.State == XmlParser.ParserState.StartAttribute)
@@ -297,7 +310,7 @@ private static Dictionary GetNamespaceAliases(string xml)
// this up to be dealt with in the future
if (state.TagName.Equals("On"))
{
- completions.Add(new Completion("Options", "Options=\"\"", "Options",
+ completions.Add(new Completion("Options", "Options=\"\"", "Options",
CompletionKind.Property, 9 /*recommendedCursorOffset*/));
}
@@ -345,25 +358,34 @@ private static Dictionary GetNamespaceAliases(string xml)
if (prop?.Type?.HasHintValues == true && state.CurrentValueStart.HasValue)
{
var search = textToCursor.Substring(state.CurrentValueStart.Value);
+ var hintCompletions = true;
if (prop.Type.IsCompositeValue)
{
- var last = search.Split(' ', ',').Last();
- curStart = curStart + search.Length - last?.Length ?? 0;
- search = last;
-
// Special case for pseudoclasses within the current edit
- if (state.AttributeName!.Equals("Selector") && search!.Contains(':'))
+ if (state.AttributeName!.Equals("Selector"))
+ {
+ hintCompletions = false;
+ if (ProcesssSelector(search.AsSpan(), state, completions, currentAssemblyName, fullText) is int delta)
+ {
+ curStart = curStart + delta;
+ }
+ }
+ else
{
- search = ":";
+ var last = search.Split(' ', ',').Last();
+ search = last;
+ curStart = curStart + search.Length - last?.Length ?? 0;
}
}
-
- completions.AddRange(GetHintCompletions(prop.Type, search, currentAssemblyName));
+ if (hintCompletions)
+ {
+ completions.AddRange(GetHintCompletions(prop.Type, search, currentAssemblyName));
+ }
}
else if (prop?.Type?.Name == typeof(Type).FullName)
{
var cKind = CompletionKind.Class;
- if (state?.AttributeName?.Equals("TargetType") == true ||
+ if (state?.AttributeName?.Equals("TargetType") == true ||
state?.AttributeName?.Equals("Selector") == true)
{
cKind |= CompletionKind.TargetTypeClass;
@@ -641,9 +663,8 @@ IEnumerable forProperties(string? filterType, string? filter, Func[\\w\\:\\s\\|\\.]+)\"");
-
- if (nameMatch.Count > 0)
+ var nameMatch = MetadataHelper.FindElementByNameRegex.Matches(fullText);
+ if (nameMatch is { Count: > 0 })
{
var result = new List();
foreach (Match m in nameMatch)
@@ -791,7 +812,7 @@ private int BuildCompletionsForMarkupExtension(MetadataProperty? property, List<
if (prop?.Type?.HasHintValues == true)
{
completions.AddRange(GetHintCompletions(prop.Type, null, currentAssemblyName));
- }
+ }
}
return forcedStart ?? ext.CurrentValueStart;
@@ -885,9 +906,351 @@ public static bool ShouldTriggerCompletionListOn(char typedChar)
return char.IsLetterOrDigit(typedChar) || typedChar == '/' || typedChar == '<'
|| typedChar == ' ' || typedChar == '.' || typedChar == ':' || typedChar == '$'
|| typedChar == '#' || typedChar == '-' || typedChar == '^' || typedChar == '{'
- || typedChar == '=';
+ || typedChar == '=' || typedChar == '[' || typedChar == '|' || typedChar == '(';
}
public static CompletionKind GetCompletionKindForHintValues(MetadataType type)
=> type.IsEnum ? CompletionKind.Enum : CompletionKind.StaticProperty;
+
+
+ public int? ProcesssSelector(ReadOnlySpan text, XmlParser state, List completions, string? currentAssemblyName, string? fullText)
+ {
+ int? parsered = default;
+ var parser = SelectorParser.Parse(text);
+ var previusStatment = parser.PreviousStatement;
+ switch (parser.Statement)
+ {
+ case SelectorStatement.Colon:
+ case SelectorStatement.FunctionArgs:
+ {
+ var fn = parser.FunctionName;
+ var tn = parser.TypeName;
+ var isEmptyTn = string.IsNullOrEmpty(tn);
+ if (previusStatment <= SelectorStatement.Middle && isEmptyTn)
+ {
+ completions.Add(new Completion(":is()", ":is(", CompletionKind.Selector | CompletionKind.Enum));
+ }
+ else if (string.IsNullOrEmpty(fn))
+ {
+ completions.Add(new Completion(":not()", ":not(", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion(":nth-child()", ":nth-child(", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion(":nth-last-child()", ":nth-last-child(", CompletionKind.Selector | CompletionKind.Enum));
+ }
+ if (isEmptyTn)
+ {
+ var pseudoClasses = _helper.FilterTypes(default)
+ .Select(kvp => kvp.Value)
+ .Where(m => m.HasPseudoClasses)
+ .SelectMany(m => m.PseudoClasses)
+ .Distinct(StringComparer.OrdinalIgnoreCase);
+ completions.AddRange(pseudoClasses.Select(v => new Completion(v, CompletionKind.Selector | CompletionKind.Enum)));
+ }
+ else
+ {
+ var typeFullName = GetFullName(parser);
+ if (_helper.LookupType(typeFullName) is MetadataType { HasPseudoClasses: true } type)
+ {
+ completions.AddRange(type.PseudoClasses.Select(v => new Completion(v, CompletionKind.Selector | CompletionKind.Enum)));
+ }
+ }
+ if (fn == "is")
+ {
+ var types = _helper.FilterTypes(default)
+ .Where(t => t.Value.IsAvaloniaObjectType)
+ .Select(t => t.Value);
+ if (types?.Any() == true)
+ {
+ parsered = text.Length - (parser.LastParsedPosition + 1);
+ completions.AddRange(types.Select(v =>
+ {
+ var name = GetXmlnsFullName(v);
+ return new Completion(name, name + ".", CompletionKind.Class | CompletionKind.TargetTypeClass);
+ }));
+ }
+ }
+ if (completions.Count > 0)
+ {
+ parsered = parser.LastParsedPosition ?? 0;
+ }
+ }
+ break;
+ case SelectorStatement.Name:
+ {
+ if (parser.IsTemplate)
+ {
+ var ton = parser.TemplateOwner;
+ if (!string.IsNullOrEmpty(ton))
+ {
+ //If it hat TemplateOwner
+ if (_helper.FilterTypes(ton)
+ .Where(kvp => kvp.Value.TemplateParts.Any())
+ .Select(kvp => kvp.Value)
+ .FirstOrDefault() is MetadataType ownerType)
+ {
+ var parts = ownerType.TemplateParts;
+ var fullName = GetFullName(parser);
+ var partType = string.IsNullOrEmpty(fullName)
+ ? default(MetadataType?)
+ : _helper.FilterTypes(fullName)
+ .Select(kvp => kvp.Value)
+ .FirstOrDefault();
+ if (partType is not null)
+ {
+ parts = parts
+ .Where(p => p.Type.AssemblyQualifiedName == partType.AssemblyQualifiedName);
+ }
+ if (parts.Any())
+ {
+ parsered = parser.LastParsedPosition ?? 0;
+ var x = (parser.LastParsedPosition ?? 0) - parser.LastSegmentStartPosition - 1;
+ if (string.IsNullOrEmpty(fullName) == false)
+ {
+ x += fullName.Length + 1;
+ }
+ completions.AddRange(parts!.Select(p => new Completion(p.Name, CompletionKind.Name | CompletionKind.Class, p.Type?.Name)
+ {
+ RecommendedCursorOffset = (p.Name.Length + (p.Type?.Name.Length > 0 ? p.Type.Name.Length + 3 : 0)),
+ DeleteTextOffset = -x,
+ }));
+ }
+ }
+ }
+ }
+ else if (fullText is not null)
+ {
+ var nameMatch = MetadataHelper
+ .FindElementByNameRegex
+ .Matches(fullText);
+ if (nameMatch is { Count: > 0 })
+ {
+ var filterName = nameMatch.OfType();
+ var elementName = parser.ElementName;
+ if (!string.IsNullOrEmpty(elementName))
+ {
+ filterName = filterName
+ .Where(m => m.Groups["AttribValue"].Value.StartsWith(elementName, StringComparison.OrdinalIgnoreCase));
+ }
+ foreach (Match m in filterName)
+ {
+ if (m.Success)
+ {
+ parsered = (parser.LastParsedPosition ?? 0);
+ var name = m.Groups["AttribValue"].Value;
+ completions.Add(new Completion(name, CompletionKind.Name | CompletionKind.Class));
+ }
+ }
+ }
+
+ }
+ }
+ break;
+ case SelectorStatement.CanHaveType:
+ case SelectorStatement.TypeName:
+ {
+ var tn = parser.TypeName;
+ if (GetFullName(parser) is string typeFullName)
+ {
+ var len = typeFullName.Length;
+ if (len > 0)
+ {
+ if (typeFullName[len - 1] == ':')
+ {
+ var ns = typeFullName.Substring(0, len - 1);
+
+ if (_helper.Aliases?.TryGetValue(ns!, out var ans) == true
+ && _helper.Metadata?.Namespaces.TryGetValue(ans, out var types) == true)
+ {
+ IEnumerable ft = types.Values;
+ ft = ft
+ .Where(t => t.IsGeneric == false)
+ .Where(t => t.IsMarkupExtension == false)
+ .Where(t => t.IsAvaloniaObjectType || t.HasAttachedProperties);
+ completions.AddRange(ft.Select(v => new Completion(v.Name, $"{ns}|{v.Name}", CompletionKind.Class | CompletionKind.TargetTypeClass)));
+ parsered = (parser.LastParsedPosition ?? 0) - (tn?.Length ?? 0);
+ }
+ }
+ else if (_helper.FilterTypes(typeFullName).Select(kvp => kvp.Value) is { } types)
+ {
+ types = types
+ .Where(t => t.IsGeneric == false)
+ .Where(t => t.IsMarkupExtension == false)
+ .Where(t => t.IsAvaloniaObjectType || t.HasAttachedProperties);
+ completions.AddRange(types.Select(v =>
+ {
+ var name = GetXmlnsFullName(v);
+ return new Completion(name, CompletionKind.Class | CompletionKind.TargetTypeClass);
+ }));
+ parsered = (parser.LastParsedPosition ?? 0) - (tn?.Length ?? 0);
+ }
+ }
+ }
+ }
+ break;
+ case SelectorStatement.Property:
+ {
+ var typeFullName = GetFullName(parser);
+ if (_helper.LookupType(typeFullName) is MetadataType type)
+ {
+ var propertyName = parser.PropertyName;
+ var selectorElementProperties = MetadataHelper.FilterProperty(type,
+ propName: propertyName,
+ attached: default,
+ hasSetter: false
+ );
+ if (selectorElementProperties?.Any() == true)
+ {
+ parsered = (parser.LastParsedPosition ?? 0) - (propertyName?.Length ?? 0);
+ completions.AddRange(selectorElementProperties.Select(v => new Completion(v.Name, v.Name + "=", v.IsAttached ? CompletionKind.AttachedProperty : CompletionKind.Property)));
+ }
+ }
+ }
+ break;
+ case SelectorStatement.AttachedProperty:
+ {
+ var typeFullName = GetFullName(parser);
+ if (_helper.LookupType(typeFullName) is { HasAttachedProperties: true } type)
+ {
+ var propertyName = parser.PropertyName;
+ var selectorElementProperties = MetadataHelper.FilterProperty(type,
+ propName: propertyName,
+ attached: true,
+ hasSetter: false
+ );
+ if (selectorElementProperties?.Any() == true)
+ {
+ var lenPropertyName = propertyName?.Length ?? 0;
+ var lenType = lenPropertyName == 0 || typeFullName is null
+ ? 0
+ : typeFullName.Length + 1;
+ parsered = (parser.LastParsedPosition ?? 0) - lenType - lenType + 1;
+ completions.AddRange(selectorElementProperties.Select(v => new Completion(v.Name, v.Name + ")", v.IsAttached ? CompletionKind.AttachedProperty : CompletionKind.Property)));
+ }
+ }
+ else
+ {
+ var types = _helper.FilterTypes(default)
+ .Where(t => t.Value.HasAttachedProperties)
+ .Select(t => t.Value);
+ if (types?.Any() == true)
+ {
+ parsered = (parser.LastParsedPosition ?? 0) + 1;
+ completions.AddRange(types.Select(v =>
+ {
+ var name = GetXmlnsFullName(v);
+ return new Completion(name, name + ".", CompletionKind.Class);
+ }));
+ }
+ }
+ }
+ break;
+ case SelectorStatement.Template:
+ {
+ completions.Add(new("/template/", "/template/", CompletionKind.Selector | CompletionKind.Enum));
+ parsered = parser.LastParsedPosition;
+ }
+ break;
+ case SelectorStatement.Traversal:
+ case SelectorStatement.Start:
+ {
+ if (!parser.IsError)
+ {
+ parsered = (parser.LastParsedPosition ?? 0);
+ // TODO: Crowling Selector operator from Attribute of the Selector
+ completions.Add(new Completion("^", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion(":", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion(">", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion(".", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion("#", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion(":is()", ":is(", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion(":not()", ":not(", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion(":nth-child()", ":nth-child(", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion(":nth-last-child()", ":nth-last-child(", CompletionKind.Selector | CompletionKind.Enum));
+ completions.Add(new Completion("/template/", "/template/", CompletionKind.Selector | CompletionKind.Enum));
+ var types = _helper.FilterTypes(default)
+ .Where(t => t.Value.IsAvaloniaObjectType || t.Value.HasAttachedProperties)
+ .Select(t => new Completion(t.Value.Name.Replace(":", "|"), CompletionKind.Class | CompletionKind.TargetTypeClass));
+ completions.AddRange(types);
+ }
+ }
+ break;
+ case SelectorStatement.Value:
+ {
+ var typeFullName = GetFullName(parser);
+ if (_helper.LookupType(typeFullName) is MetadataType type)
+ {
+ var propertyName = parser.PropertyName;
+ var prop = MetadataHelper.FilterProperty(type,
+ propName: propertyName,
+ attached: default,
+ hasSetter: false
+ ).FirstOrDefault();
+ var propType = prop?.Type;
+ if (propType?.IsNullable == true)
+ {
+ propType = propType.UnderlyingType;
+ }
+ if (propType is { HasHintValues: true } pt)
+ {
+ var kind = pt.IsEnum
+ ? CompletionKind.Enum
+ : CompletionKind.StaticProperty;
+ IEnumerable values = pt.HintValues!;
+ var value = parser.Value;
+ if (!string.IsNullOrEmpty(value))
+ {
+ values = values
+ .Where(v => v.StartsWith(value, StringComparison.OrdinalIgnoreCase));
+ }
+ completions.AddRange(values.Select(v => new Completion(v, kind)));
+ parsered = parser.LastParsedPosition - (parser.Value?.Length ?? 0);
+ }
+ }
+ }
+ break;
+ case SelectorStatement.Function:
+ case SelectorStatement.Class:
+ case SelectorStatement.Middle:
+ case SelectorStatement.End:
+ default:
+ break;
+ }
+ return parsered;
+
+ string GetFullName(SelectorParser parser)
+ {
+ var ns = parser.Namespace;
+ var typename = parser.TypeName
+ ?? GetTypeFromControlTheme();
+ var typeFullName = string.IsNullOrEmpty(ns)
+ ? typename
+ : $"{ns}:{typename}";
+ return typeFullName ?? string.Empty;
+ }
+
+ string GetXmlnsFullName(MetadataType type, char namespaceSeparator = '|')
+ {
+ if (_helper.Metadata?.InverseNamespace.TryGetValue(type.FullName, out var ns) == true
+ && !string.IsNullOrEmpty(ns))
+ {
+ var alias = _helper.Aliases?.FirstOrDefault(a => Equals(a.Value, ns));
+ if (alias is not null && !string.IsNullOrEmpty(alias.Value.Key))
+ {
+ return $"{alias.Value.Key}{namespaceSeparator}{type.Name}";
+ }
+ }
+ return type.Name!;
+ }
+
+ string? GetTypeFromControlTheme()
+ {
+ if (state.GetParentTagName(1)?.Equals("ControlTheme") == true)
+ {
+ if (state.FindParentAttributeValue("TargetType", 1, maxLevels: 0) is string implicitSelectorTypeName)
+ {
+ return implicitSelectorTypeName;
+ }
+ }
+ return default;
+ }
+ }
}
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/SelectorParser.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/SelectorParser.cs
new file mode 100644
index 00000000..5356e6ef
--- /dev/null
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/SelectorParser.cs
@@ -0,0 +1,804 @@
+using System;
+using System.Globalization;
+
+namespace Avalonia.Ide.CompletionEngine;
+
+internal enum SelectorStatement
+{
+ Start,
+ Middle,
+ Colon,
+ Class,
+ Name,
+ CanHaveType,
+ Traversal,
+ TypeName,
+ Property,
+ AttachedProperty,
+ Template,
+ Value,
+ Function,
+ FunctionArgs,
+ End,
+}
+
+internal ref struct SelectorParser
+{
+ private ref struct ParserContext
+ {
+ private ReadOnlySpan _data;
+ private ReadOnlySpan _original;
+ public ParserContext(ReadOnlySpan data) :
+ this()
+ {
+ _data = data;
+ _original = data;
+ }
+ private SelectorStatement statement = SelectorStatement.Start;
+ public int NamespaceStart = -1;
+ public int NamespaceEnd = -1;
+ public int TypeNameStart = -1;
+ public int TypeNameEnd = -1;
+ public int ClassNameStart = -1;
+ public int ClassNameEnd = -1;
+ public int PropertyNameStart = -1;
+ public int PropertyNameEnd = -1;
+ public int ValueStart = -1;
+ public int ValueEnd = -1;
+ public int NameStart = -1;
+ public int NameEnd = -1;
+ public int FunctionNameStart = -1;
+ public int FunctionNameEnd = -1;
+ public int Position { get; private set; }
+ public bool IsError = false;
+ public char Peek => _data[0];
+ public bool End =>
+ _data.IsEmpty;
+ public int? LastParsedPosition = default;
+ public bool IsTemplate;
+ public int TemplateOwnerStart = -1;
+ public int TemplateOwnerEnd = -1;
+ public int NamespaceTemplateOwnerStart = -1;
+ public int NamespaceTemplateOwnerEnd = -1;
+ public int LastSegmentStartPosition;
+
+ public SelectorStatement PreviousStatement { get; private set; }
+
+ public SelectorStatement Statement
+ {
+ get => statement;
+ set
+ {
+ if (statement != value)
+ {
+ if (value is SelectorStatement.Start or SelectorStatement.Middle)
+ {
+ LastSegmentStartPosition = Position;
+ }
+ if (value is SelectorStatement.Start)
+ {
+ (NamespaceStart, NamespaceEnd, TypeNameStart, TypeNameEnd, ClassNameStart, ClassNameEnd, PropertyNameStart, PropertyNameEnd, NameStart, NameEnd, ValueStart, ValueEnd, FunctionNameStart, FunctionNameEnd) =
+ (-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1);
+ }
+ PreviousStatement = statement;
+ }
+ statement = value;
+ }
+ }
+
+ public char Take()
+ {
+ Position++;
+ var take = _data[0];
+ _data = _data.Slice(1);
+ return take;
+ }
+
+ public void SkipWhitespace()
+ {
+ var trimmed = _data.TrimStart();
+ Position += _data.Length - trimmed.Length;
+ _data = trimmed;
+ }
+
+ public bool TakeIf(char c)
+ {
+ if (!End && Peek == c)
+ {
+ Take();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public bool TakeIf(Func condition)
+ {
+ if (condition(Peek))
+ {
+ Take();
+ return true;
+ }
+ return false;
+ }
+
+ public ReadOnlySpan TakeUntil(char c)
+ {
+ int len;
+ for (len = 0; len < _data.Length && _data[len] != c; len++)
+ {
+ }
+ var span = _data.Slice(0, len);
+ _data = _data.Slice(len);
+ Position += len;
+ return span;
+ }
+
+ public ReadOnlySpan TakeWhile(Func condition)
+ {
+ int len;
+ for (len = 0; len < _data.Length && condition(_data[len]); len++)
+ {
+ }
+ var span = _data.Slice(0, len);
+ _data = _data.Slice(len);
+ Position += len;
+ return span;
+ }
+
+ public ReadOnlySpan TryPeek(int count)
+ {
+ if (_data.Length < count)
+ return ReadOnlySpan.Empty;
+ return _data.Slice(0, count);
+ }
+
+ public ReadOnlySpan PeekWhitespace()
+ {
+ var trimmed = _data.TrimStart();
+ return _data.Slice(0, _data.Length - trimmed.Length);
+ }
+
+ public void Skip(int count)
+ {
+ if (_data.Length < count)
+ throw new IndexOutOfRangeException();
+ _data = _data.Slice(count);
+ }
+
+ public ReadOnlySpan ParseStyleClass()
+ {
+ if (!End && IsValidIdentifierStart(Peek))
+ {
+ return TakeWhile(c => IsValidIdentifierChar(c));
+ }
+ return ReadOnlySpan.Empty;
+ }
+
+ public ReadOnlySpan ParseIdentifier()
+ {
+ if (!End && IsValidIdentifierStart(Peek))
+ {
+ return TakeWhile(c => IsValidIdentifierChar(c));
+ }
+ return ReadOnlySpan.Empty;
+ }
+
+ private static bool IsValidIdentifierStart(char c)
+ {
+ return char.IsLetter(c) || c == '_';
+ }
+
+ private static bool IsValidIdentifierChar(char c)
+ {
+ if (IsValidIdentifierStart(c) || c == '-')
+ {
+ return true;
+ }
+ else
+ {
+ var cat = CharUnicodeInfo.GetUnicodeCategory(c);
+ return cat == UnicodeCategory.NonSpacingMark ||
+ cat == UnicodeCategory.SpacingCombiningMark ||
+ cat == UnicodeCategory.ConnectorPunctuation ||
+ cat == UnicodeCategory.Format ||
+ cat == UnicodeCategory.DecimalDigitNumber;
+ }
+ }
+
+ public ReadOnlySpan GetRange(int from, int to)
+ {
+ if (from < 0 || from > _original.Length)
+ {
+ return ReadOnlySpan.Empty;
+ }
+ if (to < 0 || to > _original.Length)
+ {
+ return _original.Slice(from);
+ }
+ else
+ {
+ return _original.Slice(from, to - from);
+ }
+ }
+
+ }
+
+ ParserContext _context;
+
+ private SelectorParser(ReadOnlySpan data)
+ {
+ _context = new ParserContext(data);
+ }
+
+ public string? Namespace =>
+ _context.GetRange(_context.NamespaceStart, _context.NamespaceEnd).ToString();
+
+ public string? TypeName =>
+ _context.GetRange(_context.TypeNameStart, _context.TypeNameEnd).ToString();
+
+ public string? Class =>
+ _context.GetRange(_context.ClassNameStart, _context.ClassNameEnd).ToString();
+
+ public string? PropertyName =>
+ _context.GetRange(_context.PropertyNameStart, _context.PropertyNameEnd).ToString();
+
+ public string? Value =>
+ _context.GetRange(_context.ValueStart, _context.ValueEnd).ToString();
+
+ public string? ElementName =>
+ _context.GetRange(_context.NameStart, _context.NameEnd).ToString();
+
+ public string? FunctionName =>
+ _context.GetRange(_context.FunctionNameStart, _context.FunctionNameEnd).ToString();
+
+ public SelectorStatement Statement =>
+ _context.Statement;
+
+ public bool IsError =>
+ _context.IsError;
+
+ public int? LastParsedPosition =>
+ _context.LastParsedPosition;
+
+ public SelectorStatement PreviousStatement =>
+ _context.PreviousStatement;
+
+ public bool IsTemplate =>
+ _context.IsTemplate;
+
+ public int LastSegmentStartPosition =>
+ _context.LastSegmentStartPosition;
+
+ public string? TemplateOwner
+ {
+ get
+ {
+ var sb = new System.Text.StringBuilder();
+ if (_context.NamespaceTemplateOwnerEnd > -1)
+ {
+#if NET5_0_OR_GREATER
+ sb.Append(_context.GetRange(_context.NamespaceTemplateOwnerStart, _context.NamespaceTemplateOwnerEnd));
+#else
+ sb.Append(_context.GetRange(_context.NamespaceTemplateOwnerStart, _context.NamespaceTemplateOwnerEnd).ToArray());
+#endif
+ sb.Append(':');
+ }
+#if NET5_0_OR_GREATER
+ sb.Append(_context.GetRange(_context.TemplateOwnerStart, _context.TemplateOwnerEnd));
+#else
+ sb.Append(_context.GetRange(_context.TemplateOwnerStart, _context.TemplateOwnerEnd).ToArray());
+#endif
+ return sb.ToString();
+ }
+ }
+
+ public static SelectorParser Parse(ReadOnlySpan data)
+ {
+ var selector = new SelectorParser(data);
+ selector.Parse();
+ return selector;
+ }
+
+
+ private void Parse()
+ {
+ Parse(ref _context);
+ }
+
+ private static void Parse(ref ParserContext context, char? end = default)
+ {
+ while (!context.End && !context.IsError && context.Statement != SelectorStatement.End)
+ {
+ switch (context.Statement)
+ {
+ case SelectorStatement.Start:
+ ParseStart(ref context);
+ break;
+ case SelectorStatement.Middle:
+ ParseMiddle(ref context, end);
+ break;
+ case SelectorStatement.Colon:
+ ParseColon(ref context);
+ break;
+ case SelectorStatement.Class:
+ ParseClass(ref context);
+ break;
+ case SelectorStatement.Name:
+ ParseName(ref context);
+ break;
+ case SelectorStatement.CanHaveType:
+ ParseCanHaveType(ref context);
+ break;
+ case SelectorStatement.Traversal:
+ ParseTraversal(ref context);
+ break;
+ case SelectorStatement.TypeName:
+ ParseTypeName(ref context);
+ break;
+ case SelectorStatement.Property:
+ ParseProperty(ref context);
+ break;
+ case SelectorStatement.AttachedProperty:
+ ParseAttachedProperty(ref context);
+ break;
+ case SelectorStatement.Template:
+ ParseTemplate(ref context);
+ break;
+ case SelectorStatement.FunctionArgs:
+ ParseFunctionArgs(ref context);
+ break;
+ case SelectorStatement.End:
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private static void ParseFunctionArgs(ref ParserContext context)
+ {
+ context.Statement = SelectorStatement.Middle;
+ }
+
+ private static void ParseStart(ref ParserContext context)
+ {
+ context.SkipWhitespace();
+ if (context.End)
+ {
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.End;
+ }
+
+ if (context.TakeIf(':'))
+ {
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.Colon;
+ }
+ else if (context.TakeIf('.'))
+ {
+
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.Class;
+ }
+ else if (context.TakeIf('#'))
+ {
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.Name;
+ }
+ else if (context.TakeIf('^'))
+ {
+
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.CanHaveType;
+ }
+ else if (!context.End)
+ {
+ context.Statement = SelectorStatement.Middle;
+ }
+ }
+
+ private static void ParseMiddle(ref ParserContext context, char? end)
+ {
+ if (context.TakeIf(':'))
+ {
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.Colon;
+ }
+ else if (context.TakeIf('.'))
+ {
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.Class;
+ }
+ else if (context.TakeIf(char.IsWhiteSpace) || context.Peek == '>')
+ {
+
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.Traversal;
+ }
+ else if (context.TakeIf('/'))
+ {
+
+ context.Statement = SelectorStatement.Template;
+ }
+ else if (context.TakeIf('#'))
+ {
+
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.Name;
+ }
+ else if (context.TakeIf(','))
+ {
+
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.Start;
+ }
+ else if (context.TakeIf('^'))
+ {
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.CanHaveType;
+ }
+ else if (end.HasValue && !context.End && context.Peek == end.Value)
+ {
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.End;
+ }
+ else
+ {
+ context.LastParsedPosition = context.Position;
+ context.Statement = SelectorStatement.TypeName;
+ }
+ }
+
+ private static void ParseColon(ref ParserContext r)
+ {
+ var start = r.Position;
+ var identifier = r.ParseStyleClass();
+
+ if (identifier.IsEmpty)
+ {
+ r.IsError = true;
+ return;
+ }
+
+ const string IsKeyword = "is";
+ const string NotKeyword = "not";
+ const string NthChildKeyword = "nth-child";
+ const string NthLastChildKeyword = "nth-last-child";
+
+ if (identifier.SequenceEqual(IsKeyword.AsSpan()))
+ {
+ r.FunctionNameStart = start;
+ r.Statement = SelectorStatement.Function;
+ r.LastParsedPosition = r.Position;
+ if (r.TakeIf('('))
+ {
+ r.Statement = SelectorStatement.FunctionArgs;
+ r.FunctionNameEnd = r.Position - 1;
+ if (r.End)
+ {
+ return;
+ }
+ r.Statement = SelectorStatement.TypeName;
+ ParseType(ref r);
+ if (!Expect(ref r, ')'))
+ {
+ return;
+ }
+ r.Statement = SelectorStatement.Middle;
+ }
+ }
+ else if (identifier.SequenceEqual(NotKeyword.AsSpan()))
+ {
+ r.FunctionNameStart = start;
+ r.Statement = SelectorStatement.Function;
+ r.LastParsedPosition = r.Position;
+ if (r.TakeIf('('))
+ {
+ r.FunctionNameEnd = r.Position - 1;
+ r.Statement = SelectorStatement.FunctionArgs;
+ Parse(ref r, ')');
+ if (r.IsError)
+ {
+ return;
+ }
+ r.Statement = SelectorStatement.FunctionArgs;
+ Expect(ref r, ')');
+ if (r.IsError)
+ {
+ return;
+ }
+ r.Statement = SelectorStatement.Middle;
+ }
+ }
+ else if (identifier.SequenceEqual(NthChildKeyword.AsSpan()))
+ {
+ r.FunctionNameStart = start;
+ r.Statement = SelectorStatement.Function;
+ r.LastParsedPosition = r.Position;
+ if (r.TakeIf('('))
+ {
+ r.FunctionNameEnd = r.Position - 1;
+ r.Statement = SelectorStatement.FunctionArgs;
+ r.TakeUntil(')');
+ Expect(ref r, ')');
+ if (r.IsError)
+ {
+ return;
+ }
+ r.Statement = SelectorStatement.Middle;
+ r.LastParsedPosition = r.Position;
+ return;
+ }
+
+ }
+ else if (identifier.SequenceEqual(NthLastChildKeyword.AsSpan()))
+ {
+ r.FunctionNameStart = start;
+ r.Statement = SelectorStatement.Function;
+ r.LastParsedPosition = r.Position;
+ if (r.TakeIf('('))
+ {
+ r.FunctionNameEnd = r.Position - 1;
+ r.Statement = SelectorStatement.FunctionArgs;
+ r.TakeUntil(')');
+ Expect(ref r, ')');
+ if (r.IsError)
+ {
+ return;
+ }
+ r.LastParsedPosition = r.Position;
+ r.Statement = SelectorStatement.Middle;
+ }
+ }
+ else
+ {
+ r.ClassNameStart = start;
+ r.ClassNameEnd = r.Position;
+ r.LastParsedPosition = r.Position;
+ r.Statement = SelectorStatement.CanHaveType;
+ }
+ }
+
+ private static void ParseClass(ref ParserContext r)
+ {
+ r.ClassNameStart = r.Position;
+ var @class = r.ParseStyleClass();
+ if (@class.IsEmpty)
+ {
+ r.IsError = true;
+ return;
+ }
+ r.ClassNameEnd = r.Position;
+ r.LastParsedPosition = r.Position;
+ r.Statement = SelectorStatement.CanHaveType;
+ }
+
+ private static void ParseName(ref ParserContext r)
+ {
+ r.NameStart = r.Position;
+ var name = r.ParseIdentifier();
+ if (name.IsEmpty)
+ {
+ r.IsError = true;
+ return;
+ }
+ r.NameEnd = r.Position;
+ if (!r.End)
+ r.Statement = SelectorStatement.CanHaveType;
+ }
+
+ private static void ParseCanHaveType(ref ParserContext r)
+ {
+ if (r.TakeIf('['))
+ {
+ r.LastParsedPosition = r.Position;
+ r.Statement = SelectorStatement.Property;
+ }
+ else
+ {
+ r.Statement = SelectorStatement.Middle;
+ }
+ }
+
+ private static void ParseTraversal(ref ParserContext r)
+ {
+ r.SkipWhitespace();
+ if (r.TakeIf('>'))
+ {
+ r.SkipWhitespace();
+ r.Statement = SelectorStatement.Middle;
+ }
+ else if (r.TakeIf('/'))
+ {
+ r.LastParsedPosition = r.Position;
+ r.Statement = SelectorStatement.Template;
+ }
+ else if (!r.End)
+ {
+ r.Statement = SelectorStatement.Middle;
+ }
+ else
+ {
+ r.LastParsedPosition = r.Position;
+ r.Statement = SelectorStatement.End;
+ }
+ }
+
+ private static void ParseTypeName(ref ParserContext r)
+ {
+ ParseType(ref r);
+ if (r.IsError)
+ {
+ return;
+ }
+ r.LastParsedPosition = r.Position;
+ r.Statement = SelectorStatement.CanHaveType;
+ }
+
+ private static void ParseProperty(ref ParserContext r)
+ {
+ r.LastParsedPosition = r.Position;
+ r.PropertyNameStart = r.Position;
+ var property = r.ParseIdentifier();
+
+ if (r.End)
+ {
+ r.IsError = true;
+ return;
+ }
+
+ if (r.TakeIf('('))
+ {
+ r.Statement = SelectorStatement.AttachedProperty;
+ return;
+ }
+ else if (!r.TakeIf('='))
+ {
+ r.IsError = true;
+ }
+ r.PropertyNameEnd = r.Position - 1;
+ r.LastParsedPosition = r.Position;
+ r.Statement = SelectorStatement.Value;
+ r.ValueStart = r.Position;
+ _ = r.TakeUntil(']');
+ if (!Expect(ref r, ']'))
+ {
+ return;
+ }
+ r.ValueEnd = r.Position;
+ r.Statement = SelectorStatement.Property;
+ r.LastParsedPosition = r.Position;
+ if (!r.End)
+ {
+ r.Statement = SelectorStatement.Middle;
+ }
+ }
+
+ private static void ParseAttachedProperty(ref ParserContext r)
+ {
+ r.LastParsedPosition = r.Position;
+ ParseType(ref r);
+ if (r.IsError)
+ {
+ return;
+ }
+ r.LastParsedPosition = r.Position;
+ if (r.End || !r.TakeIf('.'))
+ {
+ r.IsError = true;
+ return;
+ }
+ r.PropertyNameStart = r.Position;
+ if (r.End)
+ {
+ r.IsError = true;
+ return;
+ }
+ var property = r.ParseIdentifier();
+ if (r.End || property.IsEmpty)
+ {
+ r.IsError = true;
+ return;
+ }
+ r.PropertyNameEnd = r.Position;
+
+ if (!r.TakeIf(')'))
+ {
+ r.IsError = true;
+ return;
+ }
+ r.SkipWhitespace();
+ r.LastParsedPosition = r.Position;
+
+ if (r.End || !r.TakeIf('='))
+ {
+ r.IsError = true;
+ return;
+ }
+ r.ValueStart = r.Position;
+ r.Statement = SelectorStatement.Value;
+ _ = r.TakeUntil(']');
+ r.ValueEnd = r.Position;
+ if (Expect(ref r, ']'))
+ {
+ r.IsError = true;
+ return;
+ }
+ r.LastParsedPosition = r.Position;
+ if (!r.End)
+ {
+ r.Statement = SelectorStatement.Middle;
+ }
+ }
+
+ private static void ParseTemplate(ref ParserContext r)
+ {
+ var template = r.ParseIdentifier();
+ const string TemplateKeyword = "template";
+ if (!template.SequenceEqual(TemplateKeyword.AsSpan()))
+ {
+ r.LastParsedPosition = r.Position;
+ r.IsError = true;
+ return;
+ }
+ else if (!r.TakeIf('/'))
+ {
+ r.LastParsedPosition = r.Position;
+ r.IsError = true;
+ return;
+ }
+ r.LastParsedPosition = r.Position;
+ r.IsTemplate = true;
+ (r.TemplateOwnerStart, r.TemplateOwnerEnd, r.NamespaceTemplateOwnerStart, r.NamespaceTemplateOwnerEnd) =
+ (r.TypeNameStart, r.TypeNameEnd, r.NamespaceStart, r.NamespaceEnd);
+ r.Statement = SelectorStatement.Start;
+ }
+
+ private static void ParseType(ref ParserContext r)
+ {
+ r.LastParsedPosition = r.Position;
+ ReadOnlySpan ns = default;
+ var startPosition = r.Position;
+ var namespaceOrTypeName = r.ParseIdentifier();
+
+ if (namespaceOrTypeName.IsEmpty)
+ {
+ r.IsError = true;
+ return;
+ }
+
+ if (!r.End && r.TakeIf('|'))
+ {
+ ns = namespaceOrTypeName;
+ r.NamespaceStart = startPosition;
+ r.NamespaceEnd = r.Position - 1;
+ if (r.End)
+ {
+ r.IsError = true;
+ return;
+ }
+ r.TypeNameStart = r.Position;
+ _ = r.ParseIdentifier();
+ r.TypeNameEnd = r.Position;
+ }
+ else
+ {
+ r.TypeNameStart = startPosition;
+ r.TypeNameEnd = r.Position;
+ }
+ r.LastParsedPosition = r.Position;
+ }
+
+ private static bool Expect(ref ParserContext r, char c)
+ {
+ if (r.End || !r.TakeIf(c))
+ {
+ r.IsError = true;
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/Directory.Build.props b/Directory.Build.props
index 7e89c189..697905a6 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,6 +5,10 @@
11.0
+
+ 0024000004800000940000000602000000240000525341310004000001000100b111cf707bc11956645caffc0d3749fde7a81ee7bdca74d0a3f3f7f599aab8d6c256db70757a96a4589c353e81c8d521d7b72e3fbc59f90715f362057c4d06cd35a5c3956b4d38f12251cbf1d53db778c94dd6fb652a3fb27a03256a9df604bf9bb4c435e5163b605e00f18200433e354459c8a812fa1dee5b6b90efa3100db2
+
+
all
diff --git a/tests/CompletionEngineTests/AdvancedTests.cs b/tests/CompletionEngineTests/AdvancedTests.cs
index bba11f28..a0ec45c1 100644
--- a/tests/CompletionEngineTests/AdvancedTests.cs
+++ b/tests/CompletionEngineTests/AdvancedTests.cs
@@ -1,5 +1,7 @@
using System;
+using System.Collections;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using Xunit;
@@ -60,25 +62,25 @@ public void Extension_With_CtorArgument_Enum_Should_Be_Completed()
{
AssertSingleCompletion(" v.InsertText == ":pointerover");
Assert.Contains(compl, v => v.InsertText == ":disabled");
@@ -183,7 +185,7 @@ public void Style_Attached_Property_Name_Should_Be_Completed()
{
var xaml = "