diff --git a/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems b/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems
index c2a7e4e..5cd8933 100644
--- a/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems
+++ b/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems
@@ -14,6 +14,7 @@
+
@@ -24,6 +25,7 @@
+
diff --git a/AvaloniaVS.Shared/EnumerableExtesions.cs b/AvaloniaVS.Shared/EnumerableExtesions.cs
new file mode 100644
index 0000000..51d6ddd
--- /dev/null
+++ b/AvaloniaVS.Shared/EnumerableExtesions.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+
+namespace AvaloniaVS
+{
+ static class EnumerableExtesions
+ {
+ public static TSource FirstOrDefault(this IEnumerable source, Func predicate, TArg arg)
+ {
+ if (source is null)
+ throw new ArgumentNullException(nameof(source));
+ if (predicate is null)
+ throw new ArgumentNullException(nameof(predicate));
+
+ var enumerator = source.GetEnumerator();
+ while (enumerator.MoveNext())
+ {
+ TSource item = enumerator.Current;
+ if (predicate(item, arg))
+ {
+ return item;
+ }
+ }
+ return default;
+ }
+ }
+}
diff --git a/AvaloniaVS.Shared/IntelliSense/XamlCompletionHandlerProvider.cs b/AvaloniaVS.Shared/IntelliSense/XamlCompletionHandlerProvider.cs
index e92782b..0cd8064 100644
--- a/AvaloniaVS.Shared/IntelliSense/XamlCompletionHandlerProvider.cs
+++ b/AvaloniaVS.Shared/IntelliSense/XamlCompletionHandlerProvider.cs
@@ -6,6 +6,7 @@
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Utilities;
@@ -23,6 +24,7 @@ internal class XamlCompletionHandlerProvider : IVsTextViewCreationListener
private readonly IServiceProvider _serviceProvider;
private readonly IVsEditorAdaptersFactoryService _adapterService;
private readonly ICompletionBroker _completionBroker;
+ private readonly ITextUndoHistoryRegistry _textUndoHistoryRegistry;
private readonly CompletionEngineSource _completionEngineSource;
[ImportingConstructor]
@@ -30,11 +32,13 @@ public XamlCompletionHandlerProvider(
[Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider,
IVsEditorAdaptersFactoryService adapterService,
ICompletionBroker completionBroker,
+ ITextUndoHistoryRegistry textUndoHistoryRegistry,
CompletionEngineSource completionEngineSource)
{
_serviceProvider = serviceProvider;
_adapterService = adapterService;
_completionBroker = completionBroker;
+ _textUndoHistoryRegistry = textUndoHistoryRegistry;
_completionEngineSource = completionEngineSource;
}
@@ -52,6 +56,14 @@ public void VsTextViewCreated(IVsTextView textViewAdapter)
textView,
textViewAdapter,
_completionEngineSource.CompletionEngine));
+
+ textView.Properties.GetOrCreateSingletonProperty(
+ () => new XamlPasteCommandHandler(
+ _serviceProvider,
+ textView,
+ textViewAdapter,
+ _textUndoHistoryRegistry,
+ _completionEngineSource.CompletionEngine));
}
}
}
diff --git a/AvaloniaVS.Shared/IntelliSense/XamlPasteCommandHandler.cs b/AvaloniaVS.Shared/IntelliSense/XamlPasteCommandHandler.cs
new file mode 100644
index 0000000..1e85f45
--- /dev/null
+++ b/AvaloniaVS.Shared/IntelliSense/XamlPasteCommandHandler.cs
@@ -0,0 +1,264 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using Avalonia.Ide.CompletionEngine;
+using Avalonia.Ide.CompletionEngine.Parsing;
+using Microsoft.VisualStudio;
+using Microsoft.VisualStudio.OLE.Interop;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Text;
+using Microsoft.VisualStudio.Text.Editor;
+using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods;
+using Microsoft.VisualStudio.Text.Formatting;
+using Microsoft.VisualStudio.Text.Operations;
+using Microsoft.VisualStudio.TextManager.Interop;
+using IServiceProvider = System.IServiceProvider;
+using OleConstants = Microsoft.VisualStudio.OLE.Interop.Constants;
+
+
+namespace AvaloniaVS.IntelliSense
+{
+ ///
+ /// Handles key presses for the Avalonia XAML intellisense completion.
+ ///
+ ///
+ /// Adds a command handler to text views and listens for keypresses which should cause a
+ /// completion to be opened or comitted.
+ ///
+ /// Yes, this is horrible, but it's apparently the official way to do this. Eurgh.
+ ///
+ internal class XamlPasteCommandHandler : IOleCommandTarget
+ {
+ private readonly IServiceProvider _serviceProvider;
+ private readonly IOleCommandTarget _nextCommandHandler;
+ private readonly IWpfTextView _textView;
+ private readonly ITextUndoHistoryRegistry _textUndoHistoryRegistry;
+ private readonly CompletionEngine _engine;
+ private readonly uint _pasteCommandId = (uint)VSConstants.VSStd97CmdID.Paste;
+ public const string Avalonia_DevTools_Selector = nameof(Avalonia_DevTools_Selector);
+
+
+ public XamlPasteCommandHandler(
+ IServiceProvider serviceProvider,
+ IWpfTextView textView,
+ IVsTextView textViewAdapter,
+ ITextUndoHistoryRegistry textUndoHistoryRegistry,
+ CompletionEngine completionEngine
+ )
+ {
+ _serviceProvider = serviceProvider;
+ _textView = textView;
+ _textUndoHistoryRegistry = textUndoHistoryRegistry;
+ _engine = completionEngine;
+
+ // Add ourselves as a command to the text view.
+ textViewAdapter.AddCommandFilter(this, out _nextCommandHandler);
+ }
+
+ public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+ if (Clipboard.ContainsData(Avalonia_DevTools_Selector))
+ {
+ if (pguidCmdGroup == VSConstants.GUID_VSStandardCommandSet97)
+ {
+ for (int i = 0; i < cCmds; i++)
+ {
+ if (prgCmds[i].cmdID == _pasteCommandId)
+ {
+ if (_engine.Helper.Metadata is not null)
+ {
+ var line = _textView.GetTextViewLineContainingBufferPosition(_textView.Caret.Position.BufferPosition);
+ var end = Math.Min(line.End, _textView.Caret.Position.BufferPosition);
+ var parser = XmlParser.Parse(_textView.TextSnapshot.GetText().AsMemory(), 0, end);
+ var state = parser.State;
+ if (state == XmlParser.ParserState.AttributeValue ||
+ state == XmlParser.ParserState.AfterAttributeValue)
+ {
+ if (parser.AttributeName?.Equals("Selector") == true)
+ {
+ prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED);
+ }
+ }
+ }
+ return VSConstants.S_OK;
+ }
+ }
+ }
+ }
+ return _nextCommandHandler?.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText) ?? (int)OleConstants.OLECMDERR_E_NOTSUPPORTED;
+ }
+
+ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
+ {
+ ThreadHelper.ThrowIfNotOnUIThread();
+
+ // If we're in an automation function, move to the next command.
+ if (!VsShellUtilities.IsInAutomationFunction(_serviceProvider) && HandlePasteCommand(pguidCmdGroup, nCmdID))
+ {
+ return VSConstants.S_OK;
+ }
+ var result = _nextCommandHandler?.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut) ?? VSConstants.S_OK;
+ return result;
+ }
+
+ private bool HandlePasteCommand(Guid pguidCmdGroup, uint nCmdID)
+ {
+ if (pguidCmdGroup == VSConstants.GUID_VSStandardCommandSet97
+ && nCmdID == _pasteCommandId
+ )
+ {
+ if (Clipboard.GetData(Avalonia_DevTools_Selector) is MemoryStream @do)
+ {
+ var bytes = @do.ToArray();
+ var seletcorText = System.Text.Encoding.Unicode.GetChars(bytes, 0, bytes.Length - 2);
+ var x = _textView.Selection.SelectedSpans;
+ var p = _textView.Caret.Position.BufferPosition;
+
+ _ = TranslateSelectorAsync(_textView, _textView.TextSnapshot, p, seletcorText)
+ .ConfigureAwait(false);
+
+ return true;
+ }
+
+ }
+ return false;
+ }
+
+ private async Task TranslateSelectorAsync(ITextView textView, ITextSnapshot snapshot, int postion, char[] seletcorChars)
+ {
+ var selectorsInfo = DevToolsSelectorParser.Parse(seletcorChars);
+
+ if (_engine.Helper.Metadata is { } metadata && selectorsInfo.Count > 0)
+ {
+ var aliases = _engine.Helper.Aliases;
+ var sb = new StringBuilder();
+
+ Index index = default;
+ Dictionary aliasesToAdd = new();
+ var aliasFounded = false;
+ foreach (var si in selectorsInfo)
+ {
+ if (si.AssemblyName.Start.Value - 1 > index.Value)
+ {
+ sb.Append(seletcorChars, index.Value, si.AssemblyName.Start.Value - 1 - index.Value);
+ }
+ if (si.AssemblyName.End.Value > index.Value)
+ {
+ index = si.AssemblyName.End.Value + 1;
+ }
+ sb.Append(seletcorChars[index..si.Namespace.Start]);
+ var fn = DevToolsSelectorInfo.GetFullName(seletcorChars, si);
+ if (metadata.InverseNamespace.TryGetValue(fn, out var namespaces) && namespaces.Length > 0)
+ {
+ aliasFounded = false;
+ foreach (var item in namespaces)
+ {
+ if (aliases.FirstOrDefault((a, arg) => string.Equals(a.Value, arg), item) is { Key: not null } kv)
+ {
+ aliasFounded = true;
+ if (!string.IsNullOrEmpty(kv.Value))
+ {
+ sb.Append(kv.Key);
+ sb.Append('|');
+ }
+ break;
+ }
+ }
+ if (aliasFounded == false)
+ {
+ var @namespace = string.Concat(seletcorChars[si.Namespace]);
+ var xmlsns = CompletionEngine.GetXmlnsFromNamespace(seletcorChars[si.Namespace]);
+ aliasesToAdd.Add(xmlsns, @namespace);
+ sb.Append(xmlsns);
+ sb.Append('|');
+ }
+ sb.Append(seletcorChars[si.ElementType]);
+ index = si.ElementType.End.Value;
+ }
+ }
+ sb.Append(seletcorChars[index..seletcorChars.Length]);
+
+ if (sb.Length > 0)
+ {
+ var undoHistory = _textUndoHistoryRegistry.RegisterHistory(textView);
+ using (var transaction = undoHistory.CreateTransaction("Paste Style Selector"))
+ {
+ using (var edit = snapshot.TextBuffer.CreateEdit())
+ {
+ edit.Insert(postion, sb.ToString());
+
+ if (aliasesToAdd.Count > 0)
+ {
+ int i = 0;
+ for (; edit.Snapshot[i] != '>' && i < edit.Snapshot.Length; i++)
+ {
+
+ }
+ if (i > 0 && i < edit.Snapshot.Length)
+ {
+ var b = _textView.FormattedLineSource.BaseIndentation;
+
+ var line = edit.Snapshot.GetLineFromPosition(i);
+ var indentation_Space = CalculateLeftOfFirstChar(line, _textView.FormattedLineSource);
+ var tabSize = _textView.Options.GetTabSize();
+ var indentationStyle = _textView.Options.GetIndentStyle();
+ var newline = _textView.Options.GetNewLineCharacter();
+ var options = _textView.Options.SupportedOptions.ToArray();
+ var convert = _textView.Options.IsConvertTabsToSpacesEnabled();
+
+ var indentationBuilder = new StringBuilder(indentation_Space);
+ if (convert)
+ {
+ indentationBuilder.Append(' ', indentation_Space);
+ }
+ else
+ {
+ var ntabs = Math.DivRem(indentation_Space, tabSize, out var remainder);
+ indentationBuilder.Append('\t', ntabs);
+ indentationBuilder.Append(' ', remainder);
+ }
+
+ foreach (var item in aliasesToAdd)
+ {
+
+ edit.Insert(i, $"{newline}{indentationBuilder}xmlns:{item.Key}=\"using:{item.Value}\"");
+ }
+ }
+ }
+ edit.Apply();
+ }
+ if (snapshot != textView.TextSnapshot)
+ transaction.Complete();
+ }
+
+ }
+ }
+
+ await Task.CompletedTask;
+ }
+
+ private static int CalculateLeftOfFirstChar(ITextSnapshotLine line, IFormattedLineSource fls)
+ {
+ var nspace = 0;
+ var start = line.Start;
+ while (start.GetChar() is { } ch && char.IsWhiteSpace(ch))
+ {
+ if (ch == ' ')
+ {
+ nspace++;
+ }
+ else if (ch == '\t')
+ {
+ nspace += fls.TabSize;
+ }
+ start += 1;
+ }
+ return nspace;
+ }
+ }
+}
diff --git a/AvaloniaVS.Shared/TextViewExtensions.cs b/AvaloniaVS.Shared/TextViewExtensions.cs
index 3d456e9..8e1f88c 100644
--- a/AvaloniaVS.Shared/TextViewExtensions.cs
+++ b/AvaloniaVS.Shared/TextViewExtensions.cs
@@ -1,13 +1,13 @@
-using Microsoft.VisualStudio.Text.Editor;
+using System.Linq;
using Microsoft.VisualStudio.Text;
-using System.Linq;
+using Microsoft.VisualStudio.Text.Editor;
namespace AvaloniaVS;
internal static class TextViewExtensions
{
public static NormalizedSnapshotSpanCollection GetSpanInView(this ITextView textView, SnapshotSpan span)
-=> textView.BufferGraph.MapUpToSnapshot(span, SpanTrackingMode.EdgeInclusive, textView.TextSnapshot);
+ => textView.BufferGraph.MapUpToSnapshot(span, SpanTrackingMode.EdgeInclusive, textView.TextSnapshot);
public static void SetSelection(
this ITextView textView, VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint)
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Avalonia.Ide.CompletionEngine.csproj b/CompletionEngine/Avalonia.Ide.CompletionEngine/Avalonia.Ide.CompletionEngine.csproj
index 51eb259..726e4fd 100644
--- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Avalonia.Ide.CompletionEngine.csproj
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Avalonia.Ide.CompletionEngine.csproj
@@ -21,7 +21,7 @@
-
+
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Compatibility/Range.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Compatibility/Range.cs
new file mode 100644
index 0000000..3036986
--- /dev/null
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Compatibility/Range.cs
@@ -0,0 +1,278 @@
+// https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Private.CoreLib/src/System/Index.cs
+// https://github.com/dotnet/runtime/blob/419e949d258ecee4c40a460fb09c66d974229623/src/libraries/System.Private.CoreLib/src/System/Range.cs
+
+#if NETSTANDARD2_0
+
+using System.Runtime.CompilerServices;
+
+namespace System
+{
+ /// Represent a type can be used to index a collection either from the start or the end.
+ ///
+ /// Index is used by the C# compiler to support the new index syntax
+ ///
+ /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ;
+ /// int lastElement = someArray[^1]; // lastElement = 5
+ ///
+ ///
+ public readonly struct Index : IEquatable
+ {
+ private readonly int _value;
+
+ /// Construct an Index using a value and indicating if the index is from the start or from the end.
+ /// The index value. it has to be zero or positive number.
+ /// Indicating if the index is from the start or from the end.
+ ///
+ /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Index(int value, bool fromEnd = false)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ if (fromEnd)
+ _value = ~value;
+ else
+ _value = value;
+ }
+
+ // The following private constructors mainly created for perf reason to avoid the checks
+ private Index(int value)
+ {
+ _value = value;
+ }
+
+ /// Create an Index pointing at first element.
+ public static Index Start => new Index(0);
+
+ /// Create an Index pointing at beyond last element.
+ public static Index End => new Index(~0);
+
+ /// Create an Index from the start at the position indicated by the value.
+ /// The index value from the start.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Index FromStart(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(value);
+ }
+
+ /// Create an Index from the end at the position indicated by the value.
+ /// The index value from the end.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Index FromEnd(int value)
+ {
+ if (value < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative");
+ }
+
+ return new Index(~value);
+ }
+
+ /// Returns the index value.
+ public int Value
+ {
+ get
+ {
+ if (_value < 0)
+ {
+ return ~_value;
+ }
+ else
+ {
+ return _value;
+ }
+ }
+ }
+
+ /// Indicates whether the index is from the start or the end.
+ public bool IsFromEnd => _value < 0;
+
+ /// Calculate the offset from the start using the giving collection length.
+ /// The length of the collection that the Index will be used with. length has to be a positive value
+ ///
+ /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values.
+ /// we don't validate either the returned offset is greater than the input length.
+ /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and
+ /// then used to index a collection will get out of range exception which will be same affect as the validation.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetOffset(int length)
+ {
+ var offset = _value;
+ if (IsFromEnd)
+ {
+ // offset = length - (~value)
+ // offset = length + (~(~value) + 1)
+ // offset = length + value + 1
+
+ offset += length + 1;
+ }
+ return offset;
+ }
+
+ /// Indicates whether the current Index object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? value) => value is Index && _value == ((Index)value)._value;
+
+ /// Indicates whether the current Index object is equal to another Index object.
+ /// An object to compare with this object
+ public bool Equals(Index other) => _value == other._value;
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode() => _value;
+
+ /// Converts integer number to an Index.
+ public static implicit operator Index(int value) => FromStart(value);
+
+ /// Converts the value of the current Index object to its equivalent string representation.
+ public override string ToString()
+ {
+ if (IsFromEnd)
+ return "^" + ((uint)Value).ToString();
+
+ return ((uint)Value).ToString();
+ }
+ }
+
+ /// Represent a range has start and end indexes.
+ ///
+ /// Range is used by the C# compiler to support the range syntax.
+ ///
+ /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 };
+ /// int[] subArray1 = someArray[0..2]; // { 1, 2 }
+ /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 }
+ ///
+ ///
+ public readonly struct Range : IEquatable
+ {
+ /// Represent the inclusive start index of the Range.
+ public Index Start { get; }
+
+ /// Represent the exclusive end index of the Range.
+ public Index End { get; }
+
+ /// Construct a Range object using the start and end indexes.
+ /// Represent the inclusive start index of the range.
+ /// Represent the exclusive end index of the range.
+ public Range(Index start, Index end)
+ {
+ Start = start;
+ End = end;
+ }
+
+ /// Indicates whether the current Range object is equal to another object of the same type.
+ /// An object to compare with this object
+ public override bool Equals(object? value) =>
+ value is Range r &&
+ r.Start.Equals(Start) &&
+ r.End.Equals(End);
+
+ /// Indicates whether the current Range object is equal to another Range object.
+ /// An object to compare with this object
+ public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End);
+
+ /// Returns the hash code for this instance.
+ public override int GetHashCode()
+ {
+ return Start.GetHashCode() * 31 + End.GetHashCode();
+ }
+
+ /// Converts the value of the current Range object to its equivalent string representation.
+ public override string ToString()
+ {
+ return Start + ".." + End;
+ }
+
+ /// Create a Range object starting from start index to the end of the collection.
+ public static Range StartAt(Index start) => new Range(start, Index.End);
+
+ /// Create a Range object starting from first element in the collection to the end Index.
+ public static Range EndAt(Index end) => new Range(Index.Start, end);
+
+ /// Create a Range object starting from first element to the end.
+ public static Range All => new Range(Index.Start, Index.End);
+
+ /// Calculate the start offset and length of range object using a collection length.
+ /// The length of the collection that the range will be used with. length has to be a positive value.
+ ///
+ /// For performance reason, we don't validate the input length parameter against negative values.
+ /// It is expected Range will be used with collections which always have non negative length/count.
+ /// We validate the range is inside the length scope though.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public (int Offset, int Length) GetOffsetAndLength(int length)
+ {
+ int start;
+ var startIndex = Start;
+ if (startIndex.IsFromEnd)
+ start = length - startIndex.Value;
+ else
+ start = startIndex.Value;
+
+ int end;
+ var endIndex = End;
+ if (endIndex.IsFromEnd)
+ end = length - endIndex.Value;
+ else
+ end = endIndex.Value;
+
+ if ((uint)end > (uint)length || (uint)start > (uint)end)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ return (start, end - start);
+ }
+ }
+}
+
+namespace System.Runtime.CompilerServices
+{
+ public static class RuntimeHelpers
+ {
+ ///
+ /// Slices the specified array using the specified range.
+ ///
+ public static T[] GetSubArray(T[] array, Range range)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ (int offset, int length) = range.GetOffsetAndLength(array.Length);
+
+ if (default(T) != null || typeof(T[]) == array.GetType())
+ {
+ // We know the type of the array to be exactly T[].
+
+ if (length == 0)
+ {
+ return Array.Empty();
+ }
+
+ var dest = new T[length];
+ Array.Copy(array, offset, dest, 0, length);
+ return dest;
+ }
+ else
+ {
+ // The array is actually a U[] where U:T.
+ var dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length);
+ Array.Copy(array, offset, dest, 0, length);
+ return dest;
+ }
+ }
+ }
+}
+
+#endif
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs
index 66e8a54..faf4583 100644
--- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs
@@ -648,7 +648,7 @@ private void ProcessStyleSetter(string setterPropertyName, XmlParser state, List
{
if (state.FindParentAttributeValue("Selector", 1, maxLevels: 0)?.Trim() is { Length: > 0 } selector)
{
- if (selector[0]=='^')
+ if (selector[0] == '^')
{
selectorTypeName = state.FindParentAttributeValue("TargetType", 2, maxLevels: 0);
}
@@ -1342,7 +1342,7 @@ string GetFullName(SelectorParser parser)
if (string.IsNullOrEmpty(typename))
{
typename = GetTypeFromControlTheme();
- }
+ }
var typeFullName = string.IsNullOrEmpty(ns)
? typename
: $"{ns}:{typename}";
@@ -1375,4 +1375,40 @@ string GetXmlnsFullName(MetadataType type, char namespaceSeparator = '|')
return default;
}
}
+
+ 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!;
+ }
+
+ public static readonly IEnumerable Default = new INamespaceTrasformation[]
+ {
+ new NamespaceTrasformations.ToLowerTrasformation(),
+ new NamespaceTrasformations.ReplaceDot('_'),
+ };
+
+ public static string GetXmlnsFromNamespace(string @namespace) =>
+ GetXmlnsFromNamespace(@namespace.ToCharArray(), Default);
+
+ public static string GetXmlnsFromNamespace(char[] @namespace) =>
+ GetXmlnsFromNamespace(@namespace, Default);
+
+ public static string GetXmlnsFromNamespace(char[] @namespace, IEnumerable trasformations)
+ {
+ IEnumerable source = @namespace;
+ foreach (var trasformation in trasformations)
+ {
+ source = trasformation.Apply(source);
+ }
+ return string.Concat(source);
+ }
}
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/INamespaceTrasformation.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/INamespaceTrasformation.cs
new file mode 100644
index 0000000..8295bc1
--- /dev/null
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/INamespaceTrasformation.cs
@@ -0,0 +1,8 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Ide.CompletionEngine;
+
+public interface INamespaceTrasformation
+{
+ public IEnumerable Apply(IEnumerable input);
+}
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/NamespaceTrasformations/ReplaceDot.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/NamespaceTrasformations/ReplaceDot.cs
new file mode 100644
index 0000000..e448e37
--- /dev/null
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/NamespaceTrasformations/ReplaceDot.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Ide.CompletionEngine.NamespaceTrasformations;
+
+internal class ReplaceDot : INamespaceTrasformation
+{
+ private readonly char _sobstituion;
+
+ public ReplaceDot(char sobstituion)
+ {
+ _sobstituion = sobstituion;
+ }
+
+ public IEnumerable Apply(IEnumerable input)
+ {
+ foreach (char c in input)
+ {
+ if (c == '.')
+ yield return _sobstituion;
+ else
+ {
+ yield return c;
+ }
+ }
+ }
+}
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/NamespaceTrasformations/ToLowerTrasformation.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/NamespaceTrasformations/ToLowerTrasformation.cs
new file mode 100644
index 0000000..fde0eb4
--- /dev/null
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/NamespaceTrasformations/ToLowerTrasformation.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Ide.CompletionEngine.NamespaceTrasformations;
+
+internal class ToLowerTrasformation : INamespaceTrasformation
+{
+ public IEnumerable Apply(IEnumerable input)
+ {
+ foreach (char c in input)
+ {
+ yield return char.ToLowerInvariant(c);
+ }
+ }
+}
+
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/DevToolsSelectorInfo.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/DevToolsSelectorInfo.cs
new file mode 100644
index 0000000..8dc83ab
--- /dev/null
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/DevToolsSelectorInfo.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Text;
+
+namespace Avalonia.Ide.CompletionEngine.Parsing;
+
+public record struct DevToolsSelectorInfo(Range ElementType, Range Namespace, Range AssemblyName = default)
+{
+ public static string GetFullName(char[] buffer, DevToolsSelectorInfo info)
+ {
+ var sb = new StringBuilder();
+ if (info.Namespace.Start.Value < info.Namespace.End.Value)
+ {
+ sb.Append(buffer[info.Namespace]);
+ sb.Append('.');
+ }
+ sb.Append(buffer[info.ElementType]);
+ return sb.ToString();
+ }
+}
diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/DevToolsSelectorParser.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/DevToolsSelectorParser.cs
new file mode 100644
index 0000000..0d5dbb6
--- /dev/null
+++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Parsing/DevToolsSelectorParser.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+
+namespace Avalonia.Ide.CompletionEngine.Parsing;
+
+public class DevToolsSelectorParser
+{
+ private enum SelectorInfoPart
+ {
+ AssemblyName = 0,
+ Namespace = 1,
+ Element = 2,
+ }
+
+ public static IReadOnlyList Parse(string input)
+ => Parse(input.ToCharArray());
+
+ public static IReadOnlyList Parse(char[] chars)
+ {
+ Range[] parts = new Range[3];
+ List selectorsInfo = new();
+ var partStartIndex = -1;
+ var partName = SelectorInfoPart.Namespace;
+
+ for (int i = 0; i < chars.Length; i++)
+ {
+ var c = chars[i];
+ switch (c)
+ {
+ case '{':
+ partName = SelectorInfoPart.AssemblyName;
+ partStartIndex = i + 1;
+ break;
+ case '}' when partName == SelectorInfoPart.AssemblyName:
+ parts[(int)SelectorInfoPart.AssemblyName] = new(partStartIndex, i);
+ partStartIndex = -1;
+ partName = SelectorInfoPart.Namespace;
+ break;
+ case '|' when partName == SelectorInfoPart.Namespace && partStartIndex > -1:
+ parts[(int)SelectorInfoPart.Namespace] = new(partStartIndex, i);
+ partName = SelectorInfoPart.Element;
+ partStartIndex = -1;
+ break;
+ case '.' or '#' or ':' or ' ' when partName == SelectorInfoPart.Element:
+ parts[(int)SelectorInfoPart.Element] = new(partStartIndex, i);
+ selectorsInfo.Add(new(parts[2], parts[1], parts[0]));
+ parts[0] = default;
+ parts[1] = default;
+ parts[2] = default;
+ partName = SelectorInfoPart.Namespace;
+ break;
+ default:
+ if (partName is SelectorInfoPart.Namespace or SelectorInfoPart.Element
+ && partStartIndex == -1
+ && !char.IsWhiteSpace(c))
+ {
+ partStartIndex = i;
+ }
+ break;
+ }
+ }
+ if (partStartIndex > -1 && partName == SelectorInfoPart.Element)
+ {
+ parts[(int)SelectorInfoPart.Element] = new(partStartIndex, chars.Length);
+ selectorsInfo.Add(new(parts[2], parts[1], parts[0]));
+ parts[0] = default;
+ parts[1] = default;
+ parts[2] = default;
+ }
+ return selectorsInfo;
+ }
+}