diff --git a/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems b/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems index a9be4040..4b375241 100644 --- a/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems +++ b/AvaloniaVS.Shared/AvaloniaVS.Shared.projitems @@ -40,6 +40,7 @@ + @@ -101,5 +102,6 @@ + \ No newline at end of file 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 index 985b924e..149fb880 100644 --- a/AvaloniaVS.Shared/SuggestedActions/Actions/MissingAliasSuggestedAction.cs +++ b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingAliasSuggestedAction.cs @@ -1,10 +1,9 @@ -using System; -using System.Collections.Generic; +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.Imaging.Interop; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Differencing; @@ -12,74 +11,46 @@ namespace AvaloniaVS.Shared.SuggestedActions.Actions { - internal class MissingAliasSuggestedAction : ISuggestedAction + internal class MissingAliasSuggestedAction : BaseSuggestedAction, ISuggestedAction { private readonly ITrackingSpan _span; private readonly ITextSnapshot _snapshot; private readonly string _targetClassName; - private readonly KeyValuePair _targetClassMetadata; private readonly string _namespaceAlias; private readonly IWpfDifferenceViewerFactoryService _diffFactory; private readonly IDifferenceBufferFactoryService _diffBufferFactory; private readonly ITextBufferFactoryService _bufferFactory; - private readonly IReadOnlyDictionary _inverseNamespaces; private readonly ITextViewRoleSet _previewRoleSet; public MissingAliasSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, IDifferenceBufferFactoryService diffBufferFactory, ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, IReadOnlyDictionary inverseNamespaces) { - _inverseNamespaces = inverseNamespaces; _span = span; - _snapshot = span.TextBuffer.CurrentSnapshot; + _snapshot = _span.TextBuffer.CurrentSnapshot; _targetClassName = _span.GetText(_snapshot); - _targetClassMetadata = _inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _targetClassName); - _namespaceAlias = _targetClassMetadata.Value.Split(':').Last().Split('.').Last(); + 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})"; + DisplayText = $"Use {_namespaceAlias.ToLower()} ({targetClassMetadata.Value})"; } - public bool HasActionSets { get; } - public string DisplayText { get; } - public ImageMoniker IconMoniker { get; } - - public string IconAutomationText { get; } - - public string InputGestureText { get; } - - public bool HasPreview => true; - - public void Dispose() - { - } - - public Task> GetActionSetsAsync(CancellationToken cancellationToken) - { - return Task.FromResult>(null); - } - public Task GetPreviewAsync(CancellationToken cancellationToken) { - return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplyNamespaceSuggestion)); + return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplySuggestion)); } - private void ApplyNamespaceSuggestion(ITextBuffer buffer) + private void ApplySuggestion(ITextBuffer buffer) { buffer.Replace(_span.GetSpan(_snapshot), $"{_namespaceAlias.ToLower()}:{_targetClassName}"); } public void Invoke(CancellationToken cancellationToken) { - ApplyNamespaceSuggestion(_span.TextBuffer); - } - - public bool TryGetTelemetryId(out Guid telemetryId) - { - telemetryId = Guid.Empty; - return false; + ApplySuggestion(_span.TextBuffer); } } } diff --git a/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceAndAliasSuggestedAction.cs b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceAndAliasSuggestedAction.cs index e4fc4f21..1a020af2 100644 --- a/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceAndAliasSuggestedAction.cs +++ b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceAndAliasSuggestedAction.cs @@ -1,10 +1,9 @@ -using System; -using System.Collections.Generic; +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.Imaging.Interop; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Differencing; @@ -12,8 +11,7 @@ namespace AvaloniaVS.Shared.SuggestedActions.Actions { - internal class MissingNamespaceAndAliasSuggestedAction - : ISuggestedAction + internal class MissingNamespaceAndAliasSuggestedAction : BaseSuggestedAction, ISuggestedAction { private readonly ITrackingSpan _span; private readonly ITextSnapshot _snapshot; @@ -23,18 +21,22 @@ internal class MissingNamespaceAndAliasSuggestedAction private readonly IWpfDifferenceViewerFactoryService _diffFactory; private readonly IDifferenceBufferFactoryService _diffBufferFactory; private readonly ITextBufferFactoryService _bufferFactory; - private readonly IReadOnlyDictionary _inverseNamespaces; private readonly Dictionary _aliases; private readonly ITextViewRoleSet _previewRoleSet; - public MissingNamespaceAndAliasSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, IDifferenceBufferFactoryService diffBufferFactory, ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, IReadOnlyDictionary inverseNamespaces, - Dictionary aliases) + public MissingNamespaceAndAliasSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, + IDifferenceBufferFactoryService diffBufferFactory, ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, + IReadOnlyDictionary inverseNamespaces, Dictionary aliases) { _span = span; - _inverseNamespaces = inverseNamespaces; - _snapshot = span.TextBuffer.CurrentSnapshot; + _snapshot = _span.TextBuffer.CurrentSnapshot; _targetClassName = _span.GetText(_snapshot); - _targetClassMetadata = _inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _targetClassName); + _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; @@ -46,28 +48,10 @@ public MissingNamespaceAndAliasSuggestedAction(ITrackingSpan span, IWpfDifferenc public string DisplayText { get; } - public string IconAutomationText { get; } - - ImageMoniker ISuggestedAction.IconMoniker { get; } - - public string InputGestureText { get; } - - public bool HasActionSets { get; } - - public Task> GetActionSetsAsync(CancellationToken cancellationToken) - { - return Task.FromResult>(null); - } - - public bool HasPreview => true; public Task GetPreviewAsync(CancellationToken cancellationToken) { - return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplyNamespaceSuggestion)); - } - - public void Dispose() - { + return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplySuggestion)); } public void Invoke(CancellationToken cancellationToken) @@ -76,20 +60,17 @@ public void Invoke(CancellationToken cancellationToken) { return; } - ApplyNamespaceSuggestion(_span.TextBuffer); + ApplySuggestion(_span.TextBuffer); } - public bool TryGetTelemetryId(out Guid telemetryId) - { - telemetryId = Guid.Empty; - return false; - } - - private void ApplyNamespaceSuggestion(ITextBuffer buffer) + 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 index ee925fcd..33e73feb 100644 --- a/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceSuggestedAction.cs +++ b/AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceSuggestedAction.cs @@ -1,40 +1,33 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; +using System.Threading; +using System.Threading.Tasks; +using AvaloniaVS.Shared.SuggestedActions.Actions.Base; using AvaloniaVS.Shared.SuggestedActions.Helpers; -using Microsoft.VisualStudio.Imaging.Interop; using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Differencing; using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text; -using System.Threading.Tasks; -using System.Threading; namespace AvaloniaVS.Shared.SuggestedActions.Actions { - internal class MissingNamespaceSuggestedAction : ISuggestedAction + internal class MissingNamespaceSuggestedAction : BaseSuggestedAction, ISuggestedAction { private readonly ITrackingSpan _span; - private readonly ITextSnapshot _snapshot; - private readonly string _targetClassName; private readonly KeyValuePair _targetClassMetadata; private readonly IWpfDifferenceViewerFactoryService _diffFactory; private readonly IDifferenceBufferFactoryService _diffBufferFactory; private readonly ITextBufferFactoryService _bufferFactory; - private readonly IReadOnlyDictionary _inverseNamespaces; 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, + public MissingNamespaceSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, IDifferenceBufferFactoryService diffBufferFactory, + ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, IReadOnlyDictionary inverseNamespaces, Dictionary aliases, string alias) { _span = span; - _inverseNamespaces = inverseNamespaces; - _snapshot = span.TextBuffer.CurrentSnapshot; - _targetClassName = _span.GetText(_snapshot); - _targetClassMetadata = _inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _targetClassName); + _targetClassMetadata = inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _span.GetText(_span.TextBuffer.CurrentSnapshot)); DisplayText = $"Add xmlns {alias}"; _diffFactory = diffFactory; _diffBufferFactory = diffBufferFactory; @@ -46,28 +39,9 @@ public MissingNamespaceSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerF public string DisplayText { get; } - public string IconAutomationText { get; } - - ImageMoniker ISuggestedAction.IconMoniker { get; } - - public string InputGestureText { get; } - - public bool HasActionSets { get; } - - public Task> GetActionSetsAsync(CancellationToken cancellationToken) - { - return Task.FromResult>(null); - } - - public bool HasPreview => true; - public Task GetPreviewAsync(CancellationToken cancellationToken) { - return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplyNamespaceSuggestion)); - } - - public void Dispose() - { + return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplySuggestion)); } public void Invoke(CancellationToken cancellationToken) @@ -76,18 +50,13 @@ public void Invoke(CancellationToken cancellationToken) { return; } - ApplyNamespaceSuggestion(_span.TextBuffer); + ApplySuggestion(_span.TextBuffer); } - public bool TryGetTelemetryId(out Guid telemetryId) - { - telemetryId = Guid.Empty; - return false; - } - - private void ApplyNamespaceSuggestion(ITextBuffer buffer) + 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/SuggestedActionsSource.cs b/AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSource.cs index b936509f..51907953 100644 --- a/AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSource.cs +++ b/AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSource.cs @@ -45,25 +45,26 @@ public void Dispose() { } - public IEnumerable GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) + public IEnumerable GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, + CancellationToken cancellationToken) { - var tw = SuggestedActionsAreAvailable(range); - if (TryGetWordUnderCaret(out var extent) && (tw.Item1 || tw.Item2 || tw.Item3)) + 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 (tw.Item1) + if (availableSuggestedActions.Item1) { suggestedAction = new MissingNamespaceAndAliasSuggestedAction(trackingSpan, _diffFactory, _diffBufferFactory, _bufferFactory, _textEditorFactoryService, metadata.CompletionMetadata.InverseNamespace, CompletionEngine.GetNamespaceAliases(extent.Span.Snapshot.TextBuffer.CurrentSnapshot.GetText())); } - else if (tw.Item2) + else if (availableSuggestedActions.Item2) { suggestedAction = new MissingAliasSuggestedAction(trackingSpan, _diffFactory, _diffBufferFactory, _bufferFactory, _textEditorFactoryService, metadata.CompletionMetadata.InverseNamespace); } - else if (tw.Item3) + else if (availableSuggestedActions.Item3) { HasAlias(out var alias); suggestedAction = new MissingNamespaceSuggestedAction(trackingSpan, _diffFactory, _diffBufferFactory, _bufferFactory, _textEditorFactoryService, @@ -76,8 +77,8 @@ public IEnumerable GetSuggestedActions(ISuggestedActionCateg public Task HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { - var tt = SuggestedActionsAreAvailable(range); - if (tt.Item1 || tt.Item2 || tt.Item3) + var availableSuggestedActions = SuggestedActionsAreAvailable(range); + if (availableSuggestedActions.Item1 || availableSuggestedActions.Item2 || availableSuggestedActions.Item3) { return Task.FromResult(true); } @@ -102,7 +103,7 @@ private bool TryGetWordUnderCaret(out TextExtent wordExtent) } else { - wordExtent = default(TextExtent); + wordExtent = default; return false; } @@ -112,6 +113,12 @@ private bool TryGetWordUnderCaret(out TextExtent wordExtent) 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)) @@ -119,26 +126,31 @@ private bool TryGetWordUnderCaret(out TextExtent wordExtent) var span = range.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive); var snapshot = span.TextBuffer.CurrentSnapshot; var targetClassName = span.GetText(snapshot); - var tt = span.TextBuffer.Properties.TryGetProperty(typeof(XamlBufferMetadata), out var metadata); + 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); - if (targetClassMetadata.Value != null && targetClassMetadata.Key != null && !metadata.CompletionMetadata.Namespaces.First(x=>x.Key == "https://github.com/avaloniaui").Value.ContainsKey(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) && !HasAlias(out var _)) + if (!CompletionEngine.GetNamespaceAliases(span.TextBuffer.CurrentSnapshot.GetText()).ContainsValue(targetClassMetadata.Value)) { - return (true, false, false); + if (!HasAlias(out var _)) + { + return (true, false, false); + } + else + { + return (false, false, true); + } } - else if (CompletionEngine.GetNamespaceAliases(span.TextBuffer.CurrentSnapshot.GetText()).ContainsValue(targetClassMetadata.Value) && !HasAlias(out var _)) + else if (!HasAlias(out var _)) { return (false, true, false); } - else if (!CompletionEngine.GetNamespaceAliases(span.TextBuffer.CurrentSnapshot.GetText()).ContainsValue(targetClassMetadata.Value) && HasAlias(out var _)) - { - return (false, false, true); - } } } @@ -147,6 +159,7 @@ private bool TryGetWordUnderCaret(out TextExtent wordExtent) private bool HasAlias(out string alias) { + var span = _textView.Caret.ContainingTextViewLine.Extent.GetText().Trim(); var xmlReader = XmlReader.Create(new StringReader(span)); try