From 58a5013f9757cde40169dafa020609758271d352 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 28 Oct 2023 10:29:23 +0200 Subject: [PATCH 1/5] fix: When tagName is not Empty do not show XML Comment and auto close tag --- .../Completion/CompletionEngine.cs | 24 ++++++++++--------- tests/CompletionEngineTests/BasicTests.cs | 6 ++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs index 0dadde4b..08ac8f5d 100644 --- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs +++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs @@ -260,22 +260,24 @@ private static Dictionary GetNamespaceAliases(string xml) } else { - if (state.GetParentTagName(1) is string parentTag) + if (tagName.Length == 0) { - if (!state.IsInClosingTag) + if (state.GetParentTagName(1) is string parentTag) { - completions.Add(new Completion("/" + parentTag + ">", CompletionKind.Class, priority: 0)); - } - if (parentTag.IndexOf('.') == -1) - { - completions.Add(new Completion(parentTag, $"{parentTag}.", CompletionKind.Class, priority: 1) + if (!state.IsInClosingTag) + { + completions.Add(new Completion("/" + parentTag + ">", CompletionKind.Class, priority: 0)); + } + if (parentTag.IndexOf('.') == -1) { - TriggerCompletionAfterInsert = true, - }); + completions.Add(new Completion(parentTag, $"{parentTag}.", CompletionKind.Class, priority: 1) + { + TriggerCompletionAfterInsert = true, + }); + } } + completions.Add(new Completion("!--", "!---->", CompletionKind.Comment) { RecommendedCursorOffset = 3 }); } - completions.Add(new Completion("!--", "!---->", CompletionKind.Comment) { RecommendedCursorOffset = 3 }); - completions.AddRange(Helper.FilterTypes(tagName) .Where(kvp=>!kvp.Value.IsAbstract) .Select(kvp => diff --git a/tests/CompletionEngineTests/BasicTests.cs b/tests/CompletionEngineTests/BasicTests.cs index 5cc01b16..acbc9a9f 100644 --- a/tests/CompletionEngineTests/BasicTests.cs +++ b/tests/CompletionEngineTests/BasicTests.cs @@ -139,9 +139,9 @@ public void Completions_Should_Be_Sorted() { var compl = GetCompletionsFor(" Date: Sat, 28 Oct 2023 10:25:44 +0200 Subject: [PATCH 2/5] test: Generic Type should transform TypeArguments Xaml Syntax --- tests/CompletionEngineTests/BasicTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/CompletionEngineTests/BasicTests.cs b/tests/CompletionEngineTests/BasicTests.cs index acbc9a9f..58d98b02 100644 --- a/tests/CompletionEngineTests/BasicTests.cs +++ b/tests/CompletionEngineTests/BasicTests.cs @@ -154,5 +154,17 @@ public void Completions_With_Multiple_Kinds_Should_Be_Sorted() Assert.Equal("SelectableTextBlock", compl.Completions[1].DisplayText); Assert.Equal("SelectingItemsControl", compl.Completions[2].DisplayText); } + + [Fact] + public void GenericType_Should_Transform_TypeArguments() + { + var compl = GetCompletionsFor("", compl.Completions[1].DisplayText); + Assert.Equal("FuncDataTemplate x:TypeArguments=\"\"", compl.Completions[1].InsertText); + } } } From bcb5bd0c4e670f87c5436e839a0729850a7af5c3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 28 Oct 2023 11:49:13 +0200 Subject: [PATCH 3/5] feat: auto-completition with generics --- .../Completion/CompletionEngine.cs | 79 ++++++++++++++++--- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs index 08ac8f5d..2fd91876 100644 --- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs +++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs @@ -279,20 +279,15 @@ private static Dictionary GetNamespaceAliases(string xml) completions.Add(new Completion("!--", "!---->", CompletionKind.Comment) { RecommendedCursorOffset = 3 }); } completions.AddRange(Helper.FilterTypes(tagName) - .Where(kvp=>!kvp.Value.IsAbstract) + .Where(kvp => !kvp.Value.IsAbstract) .Select(kvp => { - if (kvp.Value.IsMarkupExtension) + var ci = GetElementCompletation(kvp.Key, kvp.Value); + return new Completion(ci.DisplayText, ci.InsertText, CompletionKind.Class) { - var xamlName = kvp.Key; - if (xamlName.EndsWith("extension", StringComparison.OrdinalIgnoreCase)) - { - xamlName = xamlName.Substring(0, kvp.Key.Length - 9 /* length of "extension" */); - } - return new Completion(xamlName, CompletionKind.Class); - } - - return new Completion(kvp.Key, CompletionKind.Class); + RecommendedCursorOffset = ci.RecommendedCursorOffset, + TriggerCompletionAfterInsert = ci.TriggerCompletionAfterInsert, + }; })); } } @@ -531,6 +526,68 @@ IEnumerable filterNamespaces(Func predicate) return new CompletionSet() { Completions = SortCompletions(completions), StartPosition = curStart }; return null; + + static (string DisplayText, string InsertText, string? Suffix, int? RecommendedCursorOffset, bool TriggerCompletionAfterInsert) GetElementCompletation(string key, + MetadataType? type) + { + var xamlName = key; + var insretText = xamlName; + var recommendedCursorOffset = default(int?); + var triggerCompletionAfterInsert = false; + if (type is not null) + { + if (type.IsMarkupExtension) + { + if (xamlName.EndsWith("extension", StringComparison.OrdinalIgnoreCase)) + { + xamlName = xamlName.Substring(0, key.Length - 9 /* length of "extension" */); + } + } + insretText = xamlName; + if (type.IsGeneric) + { + var targsStart = xamlName.IndexOf('`'); + if (targsStart > -1) + { + var xamlNameBuilder = new System.Text.StringBuilder(); + var insertTextBuilder = new System.Text.StringBuilder(); + xamlNameBuilder.Append(xamlName, 0, targsStart); + insertTextBuilder.Append(xamlName, 0, targsStart); + var args = xamlName.Substring(targsStart + 1); + if (int.TryParse(args + , System.Globalization.NumberStyles.Number + , System.Globalization.CultureInfo.InvariantCulture, out var nargs)) + { + if (nargs == 1) + { + xamlNameBuilder.Append(""); + insertTextBuilder.Append(" x:TypeArguments=\"\""); + recommendedCursorOffset = insertTextBuilder.Length - 1; + } + else + { + xamlNameBuilder.Append('<'); + insertTextBuilder.Append(" x:TypeArguments=\""); + recommendedCursorOffset = insertTextBuilder.Length - 1; + for (int i = 0; i < nargs; i++) + { + xamlNameBuilder.Append('T'); + xamlNameBuilder.Append(i + 1); + xamlNameBuilder.Append(','); + insertTextBuilder.Append(','); + } + xamlNameBuilder[xamlNameBuilder.Length - 1] = '>'; + insertTextBuilder[insertTextBuilder.Length - 1] = '"'; + } + xamlName = xamlNameBuilder.ToString(); + insretText = insertTextBuilder.ToString(); + triggerCompletionAfterInsert = true; + } + } + } + } + return (xamlName, insretText, default, recommendedCursorOffset, triggerCompletionAfterInsert); + } } private static List SortCompletions(List completions) From d116634f2735e8fbc1e20500cf6c6c4cbdecceef Mon Sep 17 00:00:00 2001 From: Daniil Pavliuchyk Date: Sun, 29 Oct 2023 23:09:31 +0200 Subject: [PATCH 4/5] Add support for namespace suggestions --- AvaloniaVS.Shared/AvaloniaVS.Shared.projitems | 9 + .../IntelliSense/CompletionEngineSource.cs | 15 ++ .../XamlCompletionCommandHandler.cs | 9 +- .../XamlCompletionHandlerProvider.cs | 9 +- .../IntelliSense/XamlCompletionSource.cs | 15 +- .../XamlCompletionSourceProvider.cs | 14 +- .../Actions/Base/BaseSuggestedAction.cs | 37 ++++ .../Actions/MissingAliasSuggestedAction.cs | 56 ++++++ ...MissingNamespaceAndAliasSuggestedAction.cs | 78 ++++++++ .../MissingNamespaceSuggestedAction.cs | 64 +++++++ .../Helpers/PreviewProvider.cs | 45 +++++ .../SuggestedActionsSource.cs | 181 ++++++++++++++++++ .../SuggestedActionsSourceProvider.cs | 44 +++++ .../Completion/CompletionEngine.cs | 2 +- 14 files changed, 557 insertions(+), 21 deletions(-) create mode 100644 AvaloniaVS.Shared/IntelliSense/CompletionEngineSource.cs create mode 100644 AvaloniaVS.Shared/SuggestedActions/Actions/Base/BaseSuggestedAction.cs create mode 100644 AvaloniaVS.Shared/SuggestedActions/Actions/MissingAliasSuggestedAction.cs create mode 100644 AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceAndAliasSuggestedAction.cs create mode 100644 AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceSuggestedAction.cs create mode 100644 AvaloniaVS.Shared/SuggestedActions/Helpers/PreviewProvider.cs create mode 100644 AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSource.cs create mode 100644 AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSourceProvider.cs diff --git a/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems b/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems index c120acde..4b375241 100644 --- a/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems +++ b/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems @@ -14,6 +14,7 @@ + @@ -39,6 +40,13 @@ + + + + + + + @@ -94,5 +102,6 @@ + \ No newline at end of file diff --git a/AvaloniaVS.Shared/IntelliSense/CompletionEngineSource.cs b/AvaloniaVS.Shared/IntelliSense/CompletionEngineSource.cs new file mode 100644 index 00000000..a77bd64a --- /dev/null +++ b/AvaloniaVS.Shared/IntelliSense/CompletionEngineSource.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.Composition; +using Avalonia.Ide.CompletionEngine; + +namespace AvaloniaVS.Shared.IntelliSense +{ + [Export] + public class CompletionEngineSource + { + public CompletionEngineSource() + { + CompletionEngine = new CompletionEngine(); + } + public CompletionEngine CompletionEngine { get; } + } +} diff --git a/AvaloniaVS.Shared/IntelliSense/XamlCompletionCommandHandler.cs b/AvaloniaVS.Shared/IntelliSense/XamlCompletionCommandHandler.cs index f8998fea..96e81770 100644 --- a/AvaloniaVS.Shared/IntelliSense/XamlCompletionCommandHandler.cs +++ b/AvaloniaVS.Shared/IntelliSense/XamlCompletionCommandHandler.cs @@ -37,24 +37,25 @@ internal class XamlCompletionCommandHandler : IOleCommandTarget private readonly ICompletionBroker _completionBroker; private readonly IOleCommandTarget _nextCommandHandler; private readonly ITextView _textView; + private readonly CompletionEngine _engine; private ICompletionSession _session; public XamlCompletionCommandHandler( IServiceProvider serviceProvider, ICompletionBroker completionBroker, ITextView textView, - IVsTextView textViewAdapter) + IVsTextView textViewAdapter, + CompletionEngine completionEngine) { _serviceProvider = serviceProvider; _completionBroker = completionBroker; _textView = textView; + _engine = completionEngine; // Add ourselves as a command to the text view. textViewAdapter.AddCommandFilter(this, out _nextCommandHandler); } - public CompletionEngine Engine { get; set; } - public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { ThreadHelper.ThrowIfNotOnUIThread(); @@ -255,7 +256,7 @@ private bool HandleSessionCompletion(char c) if (state == XmlParser.ParserState.AttributeValue || state == XmlParser.ParserState.AfterAttributeValue) { - var type = Engine.Helper.LookupType(parser.TagName); + var type = _engine.Helper.LookupType(parser.TagName); if (type != null && type.Events.FirstOrDefault(x => x.Name == parser.AttributeName) != null) { GenerateEventHandler(type.FullName, parser.AttributeName, selected.InsertionText); diff --git a/AvaloniaVS.Shared/IntelliSense/XamlCompletionHandlerProvider.cs b/AvaloniaVS.Shared/IntelliSense/XamlCompletionHandlerProvider.cs index 9b31aeb2..e92782bc 100644 --- a/AvaloniaVS.Shared/IntelliSense/XamlCompletionHandlerProvider.cs +++ b/AvaloniaVS.Shared/IntelliSense/XamlCompletionHandlerProvider.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.Composition; using AvaloniaVS.Models; +using AvaloniaVS.Shared.IntelliSense; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Shell; @@ -22,16 +23,19 @@ internal class XamlCompletionHandlerProvider : IVsTextViewCreationListener private readonly IServiceProvider _serviceProvider; private readonly IVsEditorAdaptersFactoryService _adapterService; private readonly ICompletionBroker _completionBroker; + private readonly CompletionEngineSource _completionEngineSource; [ImportingConstructor] public XamlCompletionHandlerProvider( [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, IVsEditorAdaptersFactoryService adapterService, - ICompletionBroker completionBroker) + ICompletionBroker completionBroker, + CompletionEngineSource completionEngineSource) { _serviceProvider = serviceProvider; _adapterService = adapterService; _completionBroker = completionBroker; + _completionEngineSource = completionEngineSource; } public void VsTextViewCreated(IVsTextView textViewAdapter) @@ -46,7 +50,8 @@ public void VsTextViewCreated(IVsTextView textViewAdapter) _serviceProvider, _completionBroker, textView, - textViewAdapter)); + textViewAdapter, + _completionEngineSource.CompletionEngine)); } } } diff --git a/AvaloniaVS.Shared/IntelliSense/XamlCompletionSource.cs b/AvaloniaVS.Shared/IntelliSense/XamlCompletionSource.cs index 469b42dc..f4e45c52 100644 --- a/AvaloniaVS.Shared/IntelliSense/XamlCompletionSource.cs +++ b/AvaloniaVS.Shared/IntelliSense/XamlCompletionSource.cs @@ -1,25 +1,24 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using AvaloniaVS.Models; +using AvaloniaVS.Shared.IntelliSense; using Microsoft.CodeAnalysis; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Serilog; -using CompletionEngine = Avalonia.Ide.CompletionEngine.CompletionEngine; namespace AvaloniaVS.IntelliSense { internal class XamlCompletionSource : ICompletionSource { private readonly ITextBuffer _buffer; - private readonly CompletionEngine _engine; + private readonly CompletionEngineSource _engine; - public XamlCompletionSource(ITextBuffer textBuffer) + public XamlCompletionSource(ITextBuffer textBuffer, CompletionEngineSource completionEngineSource) { _buffer = textBuffer; - _engine = new CompletionEngine(); + _engine = completionEngineSource; } public void AugmentCompletionSession(ICompletionSession session, IList completionSets) @@ -27,13 +26,11 @@ public void AugmentCompletionSession(ICompletionSession session, IList(typeof(XamlBufferMetadata), out var metadata) && metadata.CompletionMetadata != null) { - session.TextView.Properties.TryGetProperty(typeof(XamlCompletionCommandHandler), out var property); - property.Engine = _engine; var sw = Stopwatch.StartNew(); var pos = session.TextView.Caret.Position.BufferPosition; var text = pos.Snapshot.GetText(); _buffer.Properties.TryGetProperty("AssemblyName", out string assemblyName); - var completions = _engine.GetCompletions(metadata.CompletionMetadata, text, pos, assemblyName); + var completions = _engine.CompletionEngine.GetCompletions(metadata.CompletionMetadata, text, pos, assemblyName); if (completions?.Completions.Count > 0) { diff --git a/AvaloniaVS.Shared/IntelliSense/XamlCompletionSourceProvider.cs b/AvaloniaVS.Shared/IntelliSense/XamlCompletionSourceProvider.cs index 1a4071a9..e5b3ffa7 100644 --- a/AvaloniaVS.Shared/IntelliSense/XamlCompletionSourceProvider.cs +++ b/AvaloniaVS.Shared/IntelliSense/XamlCompletionSourceProvider.cs @@ -1,9 +1,7 @@ -using System; -using System.ComponentModel.Composition; +using System.ComponentModel.Composition; using AvaloniaVS.Models; +using AvaloniaVS.Shared.IntelliSense; using Microsoft.VisualStudio.Language.Intellisense; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Utilities; @@ -14,11 +12,17 @@ namespace AvaloniaVS.IntelliSense [Name("Avalonia XAML Completion")] internal class XamlCompletionSourceProvider : ICompletionSourceProvider { + [ImportingConstructor] + public XamlCompletionSourceProvider([Import] CompletionEngineSource completionEngineSource) + { + _completionEngineSource = completionEngineSource; + } + private readonly CompletionEngineSource _completionEngineSource; public ICompletionSource TryCreateCompletionSource(ITextBuffer textBuffer) { if (textBuffer.Properties.ContainsProperty(typeof(XamlBufferMetadata))) { - return new XamlCompletionSource(textBuffer); + return new XamlCompletionSource(textBuffer, _completionEngineSource); } return null; diff --git a/AvaloniaVS.Shared/SuggestedActions/Actions/Base/BaseSuggestedAction.cs b/AvaloniaVS.Shared/SuggestedActions/Actions/Base/BaseSuggestedAction.cs new file mode 100644 index 00000000..6a87c820 --- /dev/null +++ b/AvaloniaVS.Shared/SuggestedActions/Actions/Base/BaseSuggestedAction.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Language.Intellisense; + +namespace AvaloniaVS.Shared.SuggestedActions.Actions.Base +{ + internal class BaseSuggestedAction + { + public bool HasActionSets { get; } + + public ImageMoniker IconMoniker { get; } + + public string IconAutomationText { get; } + + public string InputGestureText { get; } + + public bool HasPreview => true; + + public void Dispose() + { + } + + public bool TryGetTelemetryId(out Guid telemetryId) + { + telemetryId = Guid.Empty; + return false; + } + + public Task> GetActionSetsAsync(CancellationToken cancellationToken) + { + return Task.FromResult>(null); + } + } +} diff --git a/AvaloniaVS.Shared/SuggestedActions/Actions/MissingAliasSuggestedAction.cs b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingAliasSuggestedAction.cs new file mode 100644 index 00000000..149fb880 --- /dev/null +++ b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingAliasSuggestedAction.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AvaloniaVS.Shared.SuggestedActions.Actions.Base; +using AvaloniaVS.Shared.SuggestedActions.Helpers; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.Text.Editor; + +namespace AvaloniaVS.Shared.SuggestedActions.Actions +{ + internal class MissingAliasSuggestedAction : BaseSuggestedAction, ISuggestedAction + { + private readonly ITrackingSpan _span; + private readonly ITextSnapshot _snapshot; + private readonly string _targetClassName; + private readonly string _namespaceAlias; + private readonly IWpfDifferenceViewerFactoryService _diffFactory; + private readonly IDifferenceBufferFactoryService _diffBufferFactory; + private readonly ITextBufferFactoryService _bufferFactory; + private readonly ITextViewRoleSet _previewRoleSet; + + public MissingAliasSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, IDifferenceBufferFactoryService diffBufferFactory, ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, IReadOnlyDictionary inverseNamespaces) + { + _span = span; + _snapshot = _span.TextBuffer.CurrentSnapshot; + _targetClassName = _span.GetText(_snapshot); + var targetClassMetadata = inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _targetClassName); + _namespaceAlias = targetClassMetadata.Value.Split(':').Last().Split('.').Last(); + _diffFactory = diffFactory; + _diffBufferFactory = diffBufferFactory; + _bufferFactory = bufferFactory; + _previewRoleSet = textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Analyzable); + DisplayText = $"Use {_namespaceAlias.ToLower()} ({targetClassMetadata.Value})"; + } + + public string DisplayText { get; } + + public Task GetPreviewAsync(CancellationToken cancellationToken) + { + return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplySuggestion)); + } + + private void ApplySuggestion(ITextBuffer buffer) + { + buffer.Replace(_span.GetSpan(_snapshot), $"{_namespaceAlias.ToLower()}:{_targetClassName}"); + } + + public void Invoke(CancellationToken cancellationToken) + { + ApplySuggestion(_span.TextBuffer); + } + } +} diff --git a/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceAndAliasSuggestedAction.cs b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceAndAliasSuggestedAction.cs new file mode 100644 index 00000000..1a020af2 --- /dev/null +++ b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceAndAliasSuggestedAction.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AvaloniaVS.Shared.SuggestedActions.Actions.Base; +using AvaloniaVS.Shared.SuggestedActions.Helpers; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.Text.Editor; + +namespace AvaloniaVS.Shared.SuggestedActions.Actions +{ + internal class MissingNamespaceAndAliasSuggestedAction : BaseSuggestedAction, ISuggestedAction + { + private readonly ITrackingSpan _span; + private readonly ITextSnapshot _snapshot; + private readonly string _namespaceAlias; + private readonly string _targetClassName; + private readonly KeyValuePair _targetClassMetadata; + private readonly IWpfDifferenceViewerFactoryService _diffFactory; + private readonly IDifferenceBufferFactoryService _diffBufferFactory; + private readonly ITextBufferFactoryService _bufferFactory; + private readonly Dictionary _aliases; + private readonly ITextViewRoleSet _previewRoleSet; + + public MissingNamespaceAndAliasSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, + IDifferenceBufferFactoryService diffBufferFactory, ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, + IReadOnlyDictionary inverseNamespaces, Dictionary aliases) + { + _span = span; + _snapshot = _span.TextBuffer.CurrentSnapshot; + _targetClassName = _span.GetText(_snapshot); + _targetClassMetadata = inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _targetClassName); + + // _targetClassMetadata.Value is the namespace of the control we are trying to add the namespace to. + // It is usually in the format using:MyNamespace.Something. + // So to get the prefix for the control we are splitting it by ':' + // Then taking the MyNamespace.Something part and splitting it by '.' and getting Something. + _namespaceAlias = _targetClassMetadata.Value.Split(':').Last().Split('.').Last(); + DisplayText = $"Add xmlns {_namespaceAlias}"; + _diffFactory = diffFactory; + _diffBufferFactory = diffBufferFactory; + _bufferFactory = bufferFactory; + _aliases = aliases; + _previewRoleSet = textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Analyzable); + } + + public string DisplayText { get; } + + + public Task GetPreviewAsync(CancellationToken cancellationToken) + { + return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplySuggestion)); + } + + public void Invoke(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + ApplySuggestion(_span.TextBuffer); + } + + private void ApplySuggestion(ITextBuffer buffer) + { + var lastNs = _aliases.Last().Value; + + buffer.Replace(_span.GetSpan(_snapshot), $"{_namespaceAlias.ToLower()}:{_targetClassName}"); + + // We get the index of the last namespace in the list and add the last namespace length without quotes and add 2. + // One for qutation mark and one to place the new namespace in an empty space. + buffer.Insert(buffer.CurrentSnapshot.GetText().IndexOf(lastNs) + lastNs.Length + 2, $"xmlns:{_namespaceAlias.ToLower()}=\"{_targetClassMetadata.Value}\""); + } + + } +} diff --git a/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceSuggestedAction.cs b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceSuggestedAction.cs new file mode 100644 index 00000000..33e73feb --- /dev/null +++ b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceSuggestedAction.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using AvaloniaVS.Shared.SuggestedActions.Actions.Base; +using AvaloniaVS.Shared.SuggestedActions.Helpers; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.Text.Editor; + +namespace AvaloniaVS.Shared.SuggestedActions.Actions +{ + internal class MissingNamespaceSuggestedAction : BaseSuggestedAction, ISuggestedAction + { + private readonly ITrackingSpan _span; + private readonly KeyValuePair _targetClassMetadata; + private readonly IWpfDifferenceViewerFactoryService _diffFactory; + private readonly IDifferenceBufferFactoryService _diffBufferFactory; + private readonly ITextBufferFactoryService _bufferFactory; + private readonly Dictionary _aliases; + private readonly string _alias; + private readonly ITextViewRoleSet _previewRoleSet; + + public MissingNamespaceSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, IDifferenceBufferFactoryService diffBufferFactory, + ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, IReadOnlyDictionary inverseNamespaces, + Dictionary aliases, string alias) + { + _span = span; + _targetClassMetadata = inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _span.GetText(_span.TextBuffer.CurrentSnapshot)); + DisplayText = $"Add xmlns {alias}"; + _diffFactory = diffFactory; + _diffBufferFactory = diffBufferFactory; + _bufferFactory = bufferFactory; + _aliases = aliases; + _alias = alias; + _previewRoleSet = textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Analyzable); + } + + public string DisplayText { get; } + + public Task GetPreviewAsync(CancellationToken cancellationToken) + { + return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplySuggestion)); + } + + public void Invoke(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + ApplySuggestion(_span.TextBuffer); + } + + private void ApplySuggestion(ITextBuffer buffer) + { + var lastNs = _aliases.Last().Value; + + buffer.Insert(buffer.CurrentSnapshot.GetText().IndexOf(lastNs) + lastNs.Length + 2, $"xmlns:{_alias}=\"{_targetClassMetadata.Value}\""); + } + + } +} diff --git a/AvaloniaVS.Shared/SuggestedActions/Helpers/PreviewProvider.cs b/AvaloniaVS.Shared/SuggestedActions/Helpers/PreviewProvider.cs new file mode 100644 index 00000000..9107fbb4 --- /dev/null +++ b/AvaloniaVS.Shared/SuggestedActions/Helpers/PreviewProvider.cs @@ -0,0 +1,45 @@ +using System; +using System.Windows; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.Text.Editor; + +namespace AvaloniaVS.Shared.SuggestedActions.Helpers +{ + internal static class PreviewProvider + { + public static FrameworkElement GetPreview(ITextBufferFactoryService bufferFactory, ITrackingSpan span, IDifferenceBufferFactoryService diffBufferFactory, IWpfDifferenceViewerFactoryService diffFactory, ITextViewRoleSet previewRoleSet, Action applyNamespaceSuggestionAction) + { + var snapshot = span.TextBuffer.CurrentSnapshot; + + var leftBuffer = bufferFactory.CreateTextBuffer(snapshot.GetText(), span.TextBuffer.ContentType); + + var rightBuffer = bufferFactory.CreateTextBuffer(snapshot.GetText(), span.TextBuffer.ContentType); + + applyNamespaceSuggestionAction(rightBuffer); + + var diffBuffer = diffBufferFactory.CreateDifferenceBuffer(leftBuffer, rightBuffer); + var diffView = diffFactory.CreateDifferenceView(diffBuffer, previewRoleSet); + diffView.ViewMode = DifferenceViewMode.Inline; + diffView.InlineView.VisualElement.Focusable = false; + + // DiffView size to content + diffView.DifferenceBuffer.SnapshotDifferenceChanged += (sender, args) => + { + diffView.InlineView.DisplayTextLineContainingBufferPosition( + new SnapshotPoint(diffView.DifferenceBuffer.CurrentInlineBufferSnapshot, 0), + 0.0, ViewRelativePosition.Top, double.MaxValue, double.MaxValue + ); + + var width = Math.Max(diffView.InlineView.MaxTextRightCoordinate * (diffView.InlineView.ZoomLevel / 100), 400); // Width of the widest line. + var height = diffView.InlineView.LineHeight * (diffView.InlineView.ZoomLevel / 100) * // Height of each line. + diffView.DifferenceBuffer.CurrentInlineBufferSnapshot.LineCount; + + diffView.VisualElement.Width = width; + diffView.VisualElement.Height = height; + }; + return diffView.VisualElement; + } + + } +} diff --git a/AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSource.cs b/AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSource.cs new file mode 100644 index 00000000..51907953 --- /dev/null +++ b/AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSource.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using Avalonia.Ide.CompletionEngine; +using AvaloniaVS.Models; +using AvaloniaVS.Shared.SuggestedActions.Actions; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Operations; + +namespace AvaloniaVS.Shared.SuggestedActions +{ + class SuggestedActionsSource : ISuggestedActionsSource + { + private readonly SuggestedActionsSourceProvider _factory; + private readonly ITextBuffer _textBuffer; + private readonly IWpfDifferenceViewerFactoryService _diffFactory; + private readonly IDifferenceBufferFactoryService _diffBufferFactory; + private readonly ITextBufferFactoryService _bufferFactory; + private readonly ITextEditorFactoryService _textEditorFactoryService; + private readonly ITextView _textView; + + public SuggestedActionsSource(SuggestedActionsSourceProvider testSuggestedActionsSourceProvider, ITextView textView, ITextBuffer textBuffer, + IWpfDifferenceViewerFactoryService diffFactory, IDifferenceBufferFactoryService diffBufferFactory, ITextBufferFactoryService bufferFactory, + ITextEditorFactoryService textEditorFactoryService) + { + _factory = testSuggestedActionsSourceProvider; + _textBuffer = textBuffer; + _diffFactory = diffFactory; + _diffBufferFactory = diffBufferFactory; + _bufferFactory = bufferFactory; + _textEditorFactoryService = textEditorFactoryService; + _textView = textView; + } + + public event EventHandler SuggestedActionsChanged; + + public void Dispose() + { + } + + public IEnumerable GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, + CancellationToken cancellationToken) + { + var availableSuggestedActions = SuggestedActionsAreAvailable(range); + if (TryGetWordUnderCaret(out var extent) && (availableSuggestedActions.Item1 || availableSuggestedActions.Item2 || availableSuggestedActions.Item3)) + { + extent.Span.Snapshot.TextBuffer.Properties.TryGetProperty(typeof(XamlBufferMetadata), out var metadata); + var trackingSpan = range.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive); + ISuggestedAction suggestedAction = null; + if (availableSuggestedActions.Item1) + { + suggestedAction = new MissingNamespaceAndAliasSuggestedAction(trackingSpan, _diffFactory, _diffBufferFactory, _bufferFactory, _textEditorFactoryService, + metadata.CompletionMetadata.InverseNamespace, CompletionEngine.GetNamespaceAliases(extent.Span.Snapshot.TextBuffer.CurrentSnapshot.GetText())); + } + else if (availableSuggestedActions.Item2) + { + suggestedAction = new MissingAliasSuggestedAction(trackingSpan, _diffFactory, _diffBufferFactory, _bufferFactory, _textEditorFactoryService, + metadata.CompletionMetadata.InverseNamespace); + } + else if (availableSuggestedActions.Item3) + { + HasAlias(out var alias); + suggestedAction = new MissingNamespaceSuggestedAction(trackingSpan, _diffFactory, _diffBufferFactory, _bufferFactory, _textEditorFactoryService, + metadata.CompletionMetadata.InverseNamespace, CompletionEngine.GetNamespaceAliases(extent.Span.Snapshot.TextBuffer.CurrentSnapshot.GetText()), alias); + } + return new SuggestedActionSet[] { new SuggestedActionSet(new ISuggestedAction[] { suggestedAction }) }; + } + return Enumerable.Empty(); + } + + public Task HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) + { + var availableSuggestedActions = SuggestedActionsAreAvailable(range); + if (availableSuggestedActions.Item1 || availableSuggestedActions.Item2 || availableSuggestedActions.Item3) + { + return Task.FromResult(true); + } + + return Task.FromResult(false); + } + + public bool TryGetTelemetryId(out Guid telemetryId) + { + telemetryId = Guid.Empty; + return false; + } + + private bool TryGetWordUnderCaret(out TextExtent wordExtent) + { + var caret = _textView.Caret; + SnapshotPoint point; + + if (caret.Position.BufferPosition > 0) + { + point = caret.Position.BufferPosition - 1; + } + else + { + wordExtent = default; + return false; + } + + var navigator = _factory.NavigatorService.GetTextStructureNavigator(_textBuffer); + + wordExtent = navigator.GetExtentOfWord(point); + return true; + } + + + /// + /// This method returns 3 bool values. First one defines whether MissingNamespaceAndAliasSuggestedAction should be applied + /// Second one defines whether MissingAliasSuggestedAction should be applied. + /// Third one defines whether MissingNamespaceSuggestedAction should be applied. + /// + private (bool, bool, bool) SuggestedActionsAreAvailable(SnapshotSpan range) + { + if (TryGetWordUnderCaret(out var extent)) + { + var span = range.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive); + var snapshot = span.TextBuffer.CurrentSnapshot; + var targetClassName = span.GetText(snapshot); + span.TextBuffer.Properties.TryGetProperty(typeof(XamlBufferMetadata), out var metadata); + if (metadata == null || metadata.CompletionMetadata?.InverseNamespace == null) + { + return (false, false, false); + } + var targetClassMetadata = metadata.CompletionMetadata.InverseNamespace.FirstOrDefault(x => x.Key.Split('.').Last() == targetClassName); + + // Exclude all classes from avaloniaui namespace because controls from this namespace are included by default. + if (targetClassMetadata.Value != null && targetClassMetadata.Key != null && !metadata.CompletionMetadata.Namespaces.First(x => x.Key == "https://github.com/avaloniaui").Value.ContainsKey(targetClassName)) + { + if (!CompletionEngine.GetNamespaceAliases(span.TextBuffer.CurrentSnapshot.GetText()).ContainsValue(targetClassMetadata.Value)) + { + if (!HasAlias(out var _)) + { + return (true, false, false); + } + else + { + return (false, false, true); + } + } + else if (!HasAlias(out var _)) + { + return (false, true, false); + } + } + + } + return (false, false, false); + } + + private bool HasAlias(out string alias) + { + + var span = _textView.Caret.ContainingTextViewLine.Extent.GetText().Trim(); + var xmlReader = XmlReader.Create(new StringReader(span)); + try + { + xmlReader.Read(); + } + catch + { + if (xmlReader.NodeType == XmlNodeType.Element && !string.IsNullOrEmpty(xmlReader.Prefix)) + { + alias = xmlReader.Prefix; + return true; + } + } + alias = null; + return false; + } + } +} diff --git a/AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSourceProvider.cs b/AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSourceProvider.cs new file mode 100644 index 00000000..6847ac7d --- /dev/null +++ b/AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSourceProvider.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Operations; +using Microsoft.VisualStudio.Utilities; + +namespace AvaloniaVS.Shared.SuggestedActions +{ + [Export(typeof(ISuggestedActionsSourceProvider))] + [Name("SuggestedActionsSourceProvider")] + [ContentType("xml")] + internal class SuggestedActionsSourceProvider : ISuggestedActionsSourceProvider + { + private readonly IWpfDifferenceViewerFactoryService _diffFactory; + private readonly IDifferenceBufferFactoryService _diffBufferFactory; + private readonly ITextBufferFactoryService _bufferFactory; + private readonly ITextEditorFactoryService _textEditorFactoryService; + + [ImportingConstructor] + public SuggestedActionsSourceProvider([Import] IWpfDifferenceViewerFactoryService diffFactory, [Import] IDifferenceBufferFactoryService diffBufferFactory, + [Import] ITextBufferFactoryService bufferFactory, [Import] ITextEditorFactoryService textEditorFactoryService) + { + _diffFactory = diffFactory; + _diffBufferFactory = diffBufferFactory; + _bufferFactory = bufferFactory; + _textEditorFactoryService = textEditorFactoryService; + } + + [Import(typeof(ITextStructureNavigatorSelectorService))] + internal ITextStructureNavigatorSelectorService NavigatorService { get; set; } + + public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) + { + if (textBuffer == null && textView == null) + { + return null; + } + return new SuggestedActionsSource(this, textView, textBuffer, _diffFactory, _diffBufferFactory, + _bufferFactory, _textEditorFactoryService); + } + } +} diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs index 0dadde4b..6ab7b955 100644 --- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs +++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs @@ -171,7 +171,7 @@ public IEnumerable FilterEventNames(string typeName, string? propName, public MetadataHelper Helper { get; set; } = new MetadataHelper(); - private static Dictionary GetNamespaceAliases(string xml) + public static Dictionary GetNamespaceAliases(string xml) { var rv = new Dictionary(); try From 31c8e0767a082b344bce9f700073024ef437fa6e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 30 Oct 2023 14:26:44 +0100 Subject: [PATCH 5/5] fix: Address review --- .../Completion/CompletionEngine.cs | 127 +++++++++--------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs index 2fd91876..72434d14 100644 --- a/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs +++ b/CompletionEngine/Avalonia.Ide.CompletionEngine/Completion/CompletionEngine.cs @@ -10,6 +10,8 @@ namespace Avalonia.Ide.CompletionEngine; public class CompletionEngine { + private record struct ElementCompletationInfo(string DisplayText, string InsertText, string? Suffix, int? RecommendedCursorOffset, bool TriggerCompletionAfterInsert); + public class MetadataHelper { private Metadata? _metadata; @@ -282,7 +284,7 @@ private static Dictionary GetNamespaceAliases(string xml) .Where(kvp => !kvp.Value.IsAbstract) .Select(kvp => { - var ci = GetElementCompletation(kvp.Key, kvp.Value); + var ci = GetElementCompletationInfo(kvp.Key, kvp.Value); return new Completion(ci.DisplayText, ci.InsertText, CompletionKind.Class) { RecommendedCursorOffset = ci.RecommendedCursorOffset, @@ -527,67 +529,6 @@ IEnumerable filterNamespaces(Func predicate) return null; - static (string DisplayText, string InsertText, string? Suffix, int? RecommendedCursorOffset, bool TriggerCompletionAfterInsert) GetElementCompletation(string key, - MetadataType? type) - { - var xamlName = key; - var insretText = xamlName; - var recommendedCursorOffset = default(int?); - var triggerCompletionAfterInsert = false; - if (type is not null) - { - if (type.IsMarkupExtension) - { - if (xamlName.EndsWith("extension", StringComparison.OrdinalIgnoreCase)) - { - xamlName = xamlName.Substring(0, key.Length - 9 /* length of "extension" */); - } - } - insretText = xamlName; - if (type.IsGeneric) - { - var targsStart = xamlName.IndexOf('`'); - if (targsStart > -1) - { - var xamlNameBuilder = new System.Text.StringBuilder(); - var insertTextBuilder = new System.Text.StringBuilder(); - xamlNameBuilder.Append(xamlName, 0, targsStart); - insertTextBuilder.Append(xamlName, 0, targsStart); - var args = xamlName.Substring(targsStart + 1); - if (int.TryParse(args - , System.Globalization.NumberStyles.Number - , System.Globalization.CultureInfo.InvariantCulture, out var nargs)) - { - if (nargs == 1) - { - xamlNameBuilder.Append(""); - insertTextBuilder.Append(" x:TypeArguments=\"\""); - recommendedCursorOffset = insertTextBuilder.Length - 1; - } - else - { - xamlNameBuilder.Append('<'); - insertTextBuilder.Append(" x:TypeArguments=\""); - recommendedCursorOffset = insertTextBuilder.Length - 1; - for (int i = 0; i < nargs; i++) - { - xamlNameBuilder.Append('T'); - xamlNameBuilder.Append(i + 1); - xamlNameBuilder.Append(','); - insertTextBuilder.Append(','); - } - xamlNameBuilder[xamlNameBuilder.Length - 1] = '>'; - insertTextBuilder[insertTextBuilder.Length - 1] = '"'; - } - xamlName = xamlNameBuilder.ToString(); - insretText = insertTextBuilder.ToString(); - triggerCompletionAfterInsert = true; - } - } - } - } - return (xamlName, insretText, default, recommendedCursorOffset, triggerCompletionAfterInsert); - } } private static List SortCompletions(List completions) @@ -620,6 +561,68 @@ private static int GetCompletionPriority(CompletionKind kind) }; } + static ElementCompletationInfo GetElementCompletationInfo(string key, + MetadataType? type) + { + var xamlName = key; + var insretText = xamlName; + var recommendedCursorOffset = default(int?); + var triggerCompletionAfterInsert = false; + if (type is not null) + { + if (type.IsMarkupExtension) + { + if (xamlName.EndsWith("extension", StringComparison.OrdinalIgnoreCase)) + { + xamlName = xamlName.Substring(0, key.Length - 9 /* length of "extension" */); + } + } + insretText = xamlName; + if (type.IsGeneric) + { + var targsStart = xamlName.IndexOf('`'); + if (targsStart > -1) + { + var xamlNameBuilder = new System.Text.StringBuilder(); + var insertTextBuilder = new System.Text.StringBuilder(); + xamlNameBuilder.Append(xamlName, 0, targsStart); + insertTextBuilder.Append(xamlName, 0, targsStart); + var args = xamlName.Substring(targsStart + 1); + if (int.TryParse(args + , System.Globalization.NumberStyles.Number + , System.Globalization.CultureInfo.InvariantCulture, out var nargs)) + { + if (nargs == 1) + { + xamlNameBuilder.Append(""); + insertTextBuilder.Append(" x:TypeArguments=\"\""); + recommendedCursorOffset = insertTextBuilder.Length - 1; + } + else + { + xamlNameBuilder.Append('<'); + insertTextBuilder.Append(" x:TypeArguments=\""); + recommendedCursorOffset = insertTextBuilder.Length - 1; + for (int i = 0; i < nargs; i++) + { + xamlNameBuilder.Append('T'); + xamlNameBuilder.Append(i + 1); + xamlNameBuilder.Append(','); + insertTextBuilder.Append(','); + } + xamlNameBuilder[xamlNameBuilder.Length - 1] = '>'; + insertTextBuilder[insertTextBuilder.Length - 1] = '"'; + } + xamlName = xamlNameBuilder.ToString(); + insretText = insertTextBuilder.ToString(); + triggerCompletionAfterInsert = true; + } + } + } + } + return new (xamlName, insretText, default, recommendedCursorOffset, triggerCompletionAfterInsert); + } + private void ProcessStyleSetter(string setterPropertyName, XmlParser state, List completions, string? currentAssemblyName) { const string selectorTypes = @"(?([\w|])+)|([:\.#/]\w+)";