Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Namespace suggestions #408

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions AvaloniaVS.Shared/AvaloniaVS.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Converters\EnumValuesConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Converters\NotNullOrEmptyToVisibilityConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Constants.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IntelliSense\CompletionEngineSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IntelliSense\TextChangeAdapter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IntelliSense\XamlCompletion.cs" />
<Compile Include="$(MSBuildThisFileDirectory)IntelliSense\XamlCompletionCommandHandler.cs" />
Expand All @@ -39,6 +40,13 @@
<Compile Include="$(MSBuildThisFileDirectory)Services\PreviewerProcess.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\SolutionService.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\Throttle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SuggestedActions\Actions\Base\BaseSuggestedAction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SuggestedActions\Actions\MissingAliasSuggestedAction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SuggestedActions\Actions\MissingNamespaceAndAliasSuggestedAction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SuggestedActions\Actions\MissingNamespaceSuggestedAction.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SuggestedActions\Helpers\PreviewProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SuggestedActions\SuggestedActionsSource.cs" />
<Compile Include="$(MSBuildThisFileDirectory)SuggestedActions\SuggestedActionsSourceProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TaskExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TextViewExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Utils\FrameworkInfoUtils.cs" />
Expand Down Expand Up @@ -94,5 +102,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)Commands\" />
<Folder Include="$(MSBuildThisFileDirectory)SuggestedActions\Actions\Base\" />
</ItemGroup>
</Project>
15 changes: 15 additions & 0 deletions AvaloniaVS.Shared/IntelliSense/CompletionEngineSource.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
Expand All @@ -46,7 +50,8 @@ public void VsTextViewCreated(IVsTextView textViewAdapter)
_serviceProvider,
_completionBroker,
textView,
textViewAdapter));
textViewAdapter,
_completionEngineSource.CompletionEngine));
}
}
}
Expand Down
15 changes: 6 additions & 9 deletions AvaloniaVS.Shared/IntelliSense/XamlCompletionSource.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
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<CompletionSet> completionSets)
{
if (_buffer.Properties.TryGetProperty<XamlBufferMetadata>(typeof(XamlBufferMetadata), out var metadata) &&
metadata.CompletionMetadata != null)
{
session.TextView.Properties.TryGetProperty<XamlCompletionCommandHandler>(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)
{
Expand Down
14 changes: 9 additions & 5 deletions AvaloniaVS.Shared/IntelliSense/XamlCompletionSourceProvider.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken)
{
return Task.FromResult<IEnumerable<SuggestedActionSet>>(null);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<string, string> 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<object> GetPreviewAsync(CancellationToken cancellationToken)
{
return Task.FromResult<object>(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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<string, string> _targetClassMetadata;
private readonly IWpfDifferenceViewerFactoryService _diffFactory;
private readonly IDifferenceBufferFactoryService _diffBufferFactory;
private readonly ITextBufferFactoryService _bufferFactory;
private readonly Dictionary<string, string> _aliases;
private readonly ITextViewRoleSet _previewRoleSet;

public MissingNamespaceAndAliasSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory,
IDifferenceBufferFactoryService diffBufferFactory, ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService,
IReadOnlyDictionary<string, string> inverseNamespaces, Dictionary<string, string> 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<object> GetPreviewAsync(CancellationToken cancellationToken)
{
return Task.FromResult<object>(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}\"");
}

}
}
Loading