├── .editorconfig ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── .gitignore ├── AvaloniaVS.Shared ├── AvaloniaPackage.cs ├── AvaloniaVS.Shared.projitems ├── AvaloniaVS.Shared.shproj ├── Constants.cs ├── Converters │ ├── EnumToIntConverter.cs │ ├── EnumValuesConverter.cs │ └── NotNullOrEmptyToVisibilityConverter.cs ├── EnumerableExtesions.cs ├── IntelliSense │ ├── CompletionEngineSource.cs │ ├── TextChangeAdapter.cs │ ├── XamlCompletion.cs │ ├── XamlCompletionCommandHandler.cs │ ├── XamlCompletionHandlerProvider.cs │ ├── XamlCompletionSource.cs │ ├── XamlCompletionSourceProvider.cs │ ├── XamlErrorTableEntry.cs │ ├── XamlErrorTagger.cs │ ├── XamlErrorTaggerProvider.cs │ ├── XamlPasteCommandHandler.cs │ ├── XamlTextManipulatorRegistrar.cs │ └── XamlTextViewCreationListener.cs ├── Key.snk ├── Models │ ├── DesignerRunTarget.cs │ ├── ProjectInfo.cs │ ├── ProjectOutputInfo.cs │ └── XamlBufferMetadata.cs ├── ProjectSystem │ └── AvaloniaProject.cs ├── Resources │ └── AvaloniaPackage.ico ├── ServiceProviderExtensions.cs ├── Services │ ├── AnalyticsService.cs │ ├── AvaloniaVSSettings.cs │ ├── EditorFactory.cs │ ├── IAvaloniaVSSettings.cs │ ├── IVsFindTarget3.cs │ ├── OutputPaneEventSink.cs │ ├── PreviewerProcess.cs │ ├── SolutionService.cs │ ├── Throttle.cs │ └── VsProjectAssembliesProvider.cs ├── Snippets │ ├── Avalonia │ │ ├── Avalonia_AP00.snippet │ │ ├── Avalonia_AP01.snippet │ │ ├── Avalonia_DP00.snippet │ │ ├── Avalonia_DP01.snippet │ │ ├── Avalonia_RE00.snippet │ │ ├── Avalonia_RE01.snippet │ │ ├── Avalonia_RE02.snippet │ │ ├── Avalonia_SP00.snippet │ │ └── Avalonia_SP01.snippet │ └── snippets.pkgdef ├── SuggestedActions │ ├── Actions │ │ ├── Base │ │ │ └── BaseSuggestedAction.cs │ │ ├── MissingAliasSuggestedAction.cs │ │ ├── MissingNamespaceAndAliasSuggestedAction.cs │ │ └── MissingNamespaceSuggestedAction.cs │ ├── Helpers │ │ └── PreviewProvider.cs │ ├── SuggestedActionsSource.cs │ └── SuggestedActionsSourceProvider.cs ├── TaskExtensions.cs ├── TextViewExtensions.cs ├── Utils │ └── FrameworkInfoUtils.cs ├── VSPackage.resx ├── Views │ ├── AvaloniaDesigner.xaml │ ├── AvaloniaDesigner.xaml.cs │ ├── AvaloniaPreviewer.xaml │ ├── AvaloniaPreviewer.xaml.cs │ ├── EditorPane.cs │ ├── GridLines.cs │ ├── OptionsDialogPage.cs │ ├── OptionsView.xaml │ ├── OptionsView.xaml.cs │ ├── TextEditorHost.cs │ └── VsTheme.cs ├── ZoomLevels.cs ├── icon.ico └── icons.pkgdef ├── AvaloniaVS.VS2022 ├── AvaloniaVS.VS2022.csproj ├── Extension.vsext ├── Properties │ └── AssemblyInfo.cs ├── Templates.pkgdef ├── overview.md ├── publishManifest.json └── source.extension.vsixmanifest ├── AvaloniaVS.sln ├── CompletionEngine ├── Avalonia.Ide.CompletionEngine.DnlibMetadataProvider │ ├── Avalonia.Ide.CompletionEngine.DnlibMetadataProvider.csproj │ ├── DnlibExtension.cs │ ├── DnlibMetadataProvider.cs │ └── Wrappers.cs └── Avalonia.Ide.CompletionEngine │ ├── AssemblyMetadata │ ├── DepsJsonAssemblyListLoader.cs │ ├── DepsJsonFileAssemblyProvider.cs │ ├── IAssemblyInformation.cs │ ├── IAssemblyProvider.cs │ ├── IMetadataProvider.cs │ ├── Metadata.cs │ ├── MetadataConverter.cs │ ├── MetadataReader.cs │ └── ReferenceFileAssemblyProvider.cs │ ├── Avalonia.Ide.CompletionEngine.csproj │ ├── Compatibility │ └── Range.cs │ ├── Completion │ ├── Completion.cs │ ├── CompletionEngine.cs │ └── CompletionSet.cs │ ├── INamespaceTrasformation.cs │ ├── ITextChange.cs │ ├── ITextChangeExtensions.cs │ ├── Manipulation │ ├── CloseXmlTagManipulation.cs │ ├── ManipulationType.cs │ ├── TextManipulation.cs │ └── TextManipulator.cs │ ├── NamespaceTrasformations │ ├── ReplaceDot.cs │ └── ToLowerTrasformation.cs │ ├── Parsing │ ├── DevToolsSelectorInfo.cs │ ├── DevToolsSelectorParser.cs │ ├── MarkupExtensionParser.cs │ ├── SelectorParser.cs │ └── XmlParser.cs │ └── Utils.cs ├── Directory.Build.props ├── Key.snk ├── Props ├── NetAnalyzers.props ├── NullableEnable.props └── TrimmingEnable.props ├── azure-pipelines.yml ├── license.md ├── readme.md ├── templates └── ProjectTemplatesDownloader │ └── ProjectTemplatesDownloader.csproj └── tests ├── CompletionEngineTests ├── AdvancedTests.cs ├── BasicTests.cs ├── CompletionEngineTests.csproj ├── KnownBugTests.cs ├── Manipulator │ ├── ClosingTagsTests.cs │ ├── ManipulatorBasicTests.SynchronizeStartAndEndTag.cs │ ├── ManipulatorBasicTests.cs │ └── Util │ │ ├── ManipulatorTestBase.cs │ │ └── ManipulatorTestUtils.cs ├── Metadata │ ├── MetadataConverterTests.cs │ └── MetadataTestClass.cs ├── Models.cs ├── Parsing │ ├── Fragemts │ │ ├── Should_GetParentTagName_At_Level_One_Level.xml │ │ ├── Should_GetParentTagName_At_Level_One_Level_WithComment.xml │ │ ├── Should_GetParentTagName_At_Level_One_Level_With_CDATA.xml │ │ ├── Should_GetParentTagName_At_Level_One_Level_With_Comment.xml │ │ ├── Should_GetParentTagName_At_Level_Two_Level.xml │ │ ├── Should_GetParentTagName_At_Level_Two_Level_With_CDATA.xml │ │ └── Should_GetParentTagName_At_Level_Two_Level_With_Comment.xml │ ├── SelectorParserTest.cs │ └── XmlParserTests.cs ├── Scenario.cs ├── Test.bmp ├── Test.xaml ├── TestCompiledTheme.xaml ├── TestCompiledTheme.xaml.cs ├── TestUserControl.cs └── XamlCompletionTestBase.cs ├── TestAssembly1 └── TestAssembly1.csproj └── TestAssembly2 └── TestAssembly2.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: ["bug"] 4 | body: 5 | - type: textarea 6 | id: what-happened 7 | attributes: 8 | label: Describe the bug 9 | description: A clear and concise description of what the bug is. 10 | validations: 11 | required: true 12 | - type: textarea 13 | id: steps 14 | attributes: 15 | label: To Reproduce 16 | description: Steps to reproduce the behavior. 17 | validations: 18 | required: true 19 | - type: input 20 | id: plugin-version 21 | attributes: 22 | label: AvaloniaVS plugin version 23 | placeholder: e.g. 0.10.18.3 24 | validations: 25 | required: true 26 | - type: input 27 | id: avalonia-version 28 | attributes: 29 | label: Avalonia version 30 | placeholder: e.g. 0.10.18, 11.0 31 | - type: input 32 | id: vs-version 33 | attributes: 34 | label: Visual Studio version 35 | placeholder: e.g. 17.7.0 Preview 1.0 36 | - type: textarea 37 | id: logs 38 | attributes: 39 | label: Relevant log output 40 | description: Please attach Visual Studio logs from Output -> Avalonia Diagnostics tab if possible. This will be automatically formatted into code, so no need for backticks. 41 | render: shell 42 | validations: 43 | required: true 44 | - type: textarea 45 | id: additional-info 46 | attributes: 47 | label: Additional context 48 | description: | 49 | Add any other context about the problem here. 50 | If applicable, add screenshots to help explain your problem. 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions, Discussions 4 | url: https://github.com/AvaloniaUI/AvaloniaVS/discussions/new 5 | about: Please ask and answer questions here. 6 | - name: Avalonia Community Support on Telegram 7 | url: https://t.me/Avalonia 8 | about: Please ask and answer questions here. 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | labels: ["enhancement"] 4 | body: 5 | - type: textarea 6 | id: problem 7 | attributes: 8 | label: Is your feature request related to a problem? Please describe. 9 | description: A clear and concise description of what the problem is. 10 | validations: 11 | required: true 12 | - type: textarea 13 | id: solution 14 | attributes: 15 | label: Describe the solution you'd like 16 | description: A clear and concise description of what you want to happen. 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: alternatives 21 | attributes: 22 | label: Describe alternatives you've considered 23 | description: A clear and concise description of any alternative solutions or features you've considered. 24 | - type: textarea 25 | id: additional-context 26 | attributes: 27 | label: Additional context 28 | description: Add any other context or screenshots about the feature request here. 29 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/AvaloniaPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading; 4 | using AvaloniaVS.Services; 5 | using AvaloniaVS.Shared.Services; 6 | using AvaloniaVS.Views; 7 | using dnlib; 8 | using EnvDTE; 9 | using Microsoft.VisualStudio; 10 | using Microsoft.VisualStudio.Shell; 11 | using Microsoft.VisualStudio.Shell.Interop; 12 | using Serilog; 13 | using Serilog.Core; 14 | using Task = System.Threading.Tasks.Task; 15 | 16 | namespace AvaloniaVS 17 | { 18 | [Guid(Constants.PackageGuidString)] 19 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] 20 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 21 | [ProvideEditorExtension( 22 | typeof(EditorFactory), 23 | "." + Constants.axaml, 24 | 100, 25 | NameResourceID = 113, 26 | EditorFactoryNotify = true, 27 | ProjectGuid = VSConstants.UICONTEXT.CSharpProject_string, 28 | DefaultName = Constants.PackageName)] 29 | [ProvideEditorExtension( 30 | typeof(EditorFactory), 31 | "." + Constants.paml, 32 | 100, 33 | NameResourceID = 113, 34 | EditorFactoryNotify = true, 35 | ProjectGuid = VSConstants.UICONTEXT.CSharpProject_string, 36 | DefaultName = Constants.PackageName)] 37 | [ProvideEditorExtension( 38 | typeof(EditorFactory), 39 | "." + Constants.xaml, 40 | 0x40, 41 | NameResourceID = 113, 42 | EditorFactoryNotify = true, 43 | ProjectGuid = VSConstants.UICONTEXT.CSharpProject_string, 44 | DefaultName = Constants.PackageName)] 45 | [ProvideEditorFactory(typeof(EditorFactory), 113, TrustLevel = __VSEDITORTRUSTLEVEL.ETL_AlwaysTrusted)] 46 | [ProvideEditorLogicalView(typeof(EditorFactory), LogicalViewID.Designer)] 47 | [ProvideXmlEditorChooserDesignerView(Constants.PackageName, 48 | Constants.xaml, 49 | LogicalViewID.Designer, 50 | 10001, 51 | Namespace = "https://github.com/avaloniaui", 52 | MatchExtensionAndNamespace = false, 53 | CodeLogicalViewEditor = typeof(EditorFactory), 54 | DesignerLogicalViewEditor = typeof(EditorFactory), 55 | DebuggingLogicalViewEditor = typeof(EditorFactory), 56 | TextLogicalViewEditor = typeof(EditorFactory))] 57 | [ProvideXmlEditorChooserDesignerView(Constants.PackageName, 58 | Constants.axaml, 59 | LogicalViewID.Designer, 60 | 10000, 61 | Namespace = "https://github.com/avaloniaui", 62 | MatchExtensionAndNamespace = false, 63 | CodeLogicalViewEditor = typeof(EditorFactory), 64 | DesignerLogicalViewEditor = typeof(EditorFactory), 65 | DebuggingLogicalViewEditor = typeof(EditorFactory), 66 | TextLogicalViewEditor = typeof(EditorFactory))] 67 | [ProvideOptionPage(typeof(OptionsDialogPage), Constants.PackageName, "General", 113, 0, supportsAutomation: true)] 68 | [ProvideBindingPath] 69 | internal sealed class AvaloniaPackage : AsyncPackage 70 | { 71 | public static SolutionService SolutionService { get; private set; } 72 | 73 | protected override async Task InitializeAsync( 74 | CancellationToken cancellationToken, 75 | IProgress progress) 76 | { 77 | await base.InitializeAsync(cancellationToken, progress); 78 | 79 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 80 | 81 | InitializeLogging(); 82 | RegisterEditorFactory(new EditorFactory(this)); 83 | 84 | var dte = (DTE)await GetServiceAsync(typeof(DTE)); 85 | SolutionService = new SolutionService(dte); 86 | 87 | var shell = await GetServiceAsync(typeof(SVsShell)) as IVsShell; 88 | var ans = new AnalyticsService(this.GetMefService(), dte, shell); 89 | await ans.TrackLaunchAsync(); 90 | 91 | Log.Logger.Information("Avalonia Package initialized"); 92 | } 93 | 94 | private void InitializeLogging() 95 | { 96 | const string format = "{Timestamp:HH:mm:ss.fff} [{Level}] {Pid} {Message}{NewLine}{Exception}"; 97 | var ouput = this.GetService(); 98 | var settings = this.GetMefService(); 99 | var levelSwitch = new LoggingLevelSwitch() { MinimumLevel = settings.MinimumLogVerbosity }; 100 | 101 | settings.PropertyChanged += (s, e) => levelSwitch.MinimumLevel = settings.MinimumLogVerbosity; 102 | 103 | var sink = new OutputPaneEventSink(ouput, outputTemplate: format); 104 | Log.Logger = new LoggerConfiguration() 105 | .MinimumLevel.ControlledBy(levelSwitch) 106 | .WriteTo.Sink(sink, levelSwitch: levelSwitch) 107 | .WriteTo.Trace(outputTemplate: format) 108 | .CreateLogger(); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/AvaloniaVS.Shared.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 40d9809e-d179-44b2-bf42-61d781fb74f6 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AvaloniaVS; 4 | 5 | internal static class Constants 6 | { 7 | public const string PackageGuidString = "865ba8d5-1180-4bf8-8821-345f72a4cb79"; 8 | public static readonly Guid PackageGuid = new (PackageGuidString); 9 | public const string PackageName = "Avalonia Xaml Editor"; 10 | public const string axaml = nameof(axaml); 11 | public const string xaml = nameof(xaml); 12 | public const string paml = nameof(paml); 13 | 14 | public const string AvaloviaFactoryEditorGuidString = @"6D5344A2-2FCD-49DE-A09D-6A14FD1B1224"; 15 | public static readonly Guid AvaloviaFactoryEditorGuid = new (AvaloviaFactoryEditorGuidString); 16 | 17 | public const string AvaloniaCapability = nameof(Avalonia); 18 | } 19 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Converters/EnumToIntConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace AvaloniaVS.Converters 6 | { 7 | public class EnumToIntConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | if (targetType.IsEnum && value is int i) 12 | { 13 | return Enum.ToObject(targetType, value); 14 | } 15 | else if (value?.GetType().IsEnum == true && targetType == typeof(int)) 16 | { 17 | return (int)value; 18 | } 19 | 20 | return value; 21 | } 22 | 23 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 24 | { 25 | return Convert(value, targetType, parameter, culture); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Converters/EnumValuesConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace AvaloniaVS.Converters 6 | { 7 | public class EnumValuesConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | if (value is Type t) 12 | { 13 | return Enum.GetValues(t); 14 | } 15 | 16 | return value; 17 | } 18 | 19 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Converters/NotNullOrEmptyToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Globalization; 4 | using System.Windows; 5 | using System.Windows.Data; 6 | 7 | namespace AvaloniaVS.Converters 8 | { 9 | internal class NotNullOrEmptyToVisibilityConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | return (value as IList)?.Count > 0 ? Visibility.Visible : Visibility.Collapsed; 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/EnumerableExtesions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace AvaloniaVS 5 | { 6 | static class EnumerableExtesions 7 | { 8 | public static TSource FirstOrDefault(this IEnumerable source, Func predicate, TArg arg) 9 | { 10 | if (source is null) 11 | throw new ArgumentNullException(nameof(source)); 12 | if (predicate is null) 13 | throw new ArgumentNullException(nameof(predicate)); 14 | 15 | var enumerator = source.GetEnumerator(); 16 | while (enumerator.MoveNext()) 17 | { 18 | TSource item = enumerator.Current; 19 | if (predicate(item, arg)) 20 | { 21 | return item; 22 | } 23 | } 24 | return default; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/IntelliSense/CompletionEngineSource.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Composition; 2 | using Avalonia.Ide.CompletionEngine; 3 | 4 | namespace AvaloniaVS.Shared.IntelliSense 5 | { 6 | [Export] 7 | public class CompletionEngineSource 8 | { 9 | public CompletionEngineSource() 10 | { 11 | CompletionEngine = new CompletionEngine(); 12 | } 13 | public CompletionEngine CompletionEngine { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/IntelliSense/TextChangeAdapter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text; 2 | using AvaloniaTextChange = Avalonia.Ide.CompletionEngine.ITextChange; 3 | 4 | namespace AvaloniaVS.IntelliSense 5 | { 6 | public class TextChangeAdapter : AvaloniaTextChange 7 | { 8 | private readonly ITextChange _textChange; 9 | 10 | public TextChangeAdapter(ITextChange textChange) 11 | { 12 | _textChange = textChange; 13 | } 14 | 15 | /// 16 | public int NewPosition => _textChange.NewPosition; 17 | 18 | /// 19 | public string NewText => _textChange.NewText; 20 | 21 | /// 22 | public int OldPosition => _textChange.OldPosition; 23 | 24 | /// 25 | public string OldText => _textChange.OldText; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/IntelliSense/XamlCompletionHandlerProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Composition; 3 | using AvaloniaVS.Models; 4 | using AvaloniaVS.Shared.IntelliSense; 5 | using Microsoft.VisualStudio.Editor; 6 | using Microsoft.VisualStudio.Language.Intellisense; 7 | using Microsoft.VisualStudio.Shell; 8 | using Microsoft.VisualStudio.Text.Editor; 9 | using Microsoft.VisualStudio.Text.Operations; 10 | using Microsoft.VisualStudio.TextManager.Interop; 11 | using Microsoft.VisualStudio.Utilities; 12 | 13 | namespace AvaloniaVS.IntelliSense 14 | { 15 | /// 16 | /// Registers a with newly-created text views. 17 | /// 18 | [Export(typeof(IVsTextViewCreationListener))] 19 | [Name("Avalonia XAML completion handler")] 20 | [ContentType("xml")] 21 | [TextViewRole(PredefinedTextViewRoles.Editable)] 22 | internal class XamlCompletionHandlerProvider : IVsTextViewCreationListener 23 | { 24 | private readonly IServiceProvider _serviceProvider; 25 | private readonly IVsEditorAdaptersFactoryService _adapterService; 26 | private readonly ICompletionBroker _completionBroker; 27 | private readonly ITextUndoHistoryRegistry _textUndoHistoryRegistry; 28 | private readonly CompletionEngineSource _completionEngineSource; 29 | 30 | [ImportingConstructor] 31 | public XamlCompletionHandlerProvider( 32 | [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, 33 | IVsEditorAdaptersFactoryService adapterService, 34 | ICompletionBroker completionBroker, 35 | ITextUndoHistoryRegistry textUndoHistoryRegistry, 36 | CompletionEngineSource completionEngineSource) 37 | { 38 | _serviceProvider = serviceProvider; 39 | _adapterService = adapterService; 40 | _completionBroker = completionBroker; 41 | _textUndoHistoryRegistry = textUndoHistoryRegistry; 42 | _completionEngineSource = completionEngineSource; 43 | } 44 | 45 | public void VsTextViewCreated(IVsTextView textViewAdapter) 46 | { 47 | var textView = _adapterService.GetWpfTextView(textViewAdapter); 48 | 49 | // If the buffer contains Avalonia XAML, register a completion handler on it. 50 | if (textView.TextBuffer.Properties.ContainsProperty(typeof(XamlBufferMetadata))) 51 | { 52 | textView.Properties.GetOrCreateSingletonProperty( 53 | () => new XamlCompletionCommandHandler( 54 | _serviceProvider, 55 | _completionBroker, 56 | textView, 57 | textViewAdapter, 58 | _completionEngineSource.CompletionEngine)); 59 | 60 | textView.Properties.GetOrCreateSingletonProperty( 61 | () => new XamlPasteCommandHandler( 62 | _serviceProvider, 63 | textView, 64 | textViewAdapter, 65 | _textUndoHistoryRegistry, 66 | _completionEngineSource.CompletionEngine)); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/IntelliSense/XamlCompletionSource.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using AvaloniaVS.Models; 5 | using AvaloniaVS.Shared.IntelliSense; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.VisualStudio.Language.Intellisense; 8 | using Microsoft.VisualStudio.Text; 9 | using Serilog; 10 | 11 | namespace AvaloniaVS.IntelliSense 12 | { 13 | internal class XamlCompletionSource : ICompletionSource 14 | { 15 | private readonly ITextBuffer _buffer; 16 | private readonly CompletionEngineSource _engine; 17 | 18 | public XamlCompletionSource(ITextBuffer textBuffer, CompletionEngineSource completionEngineSource) 19 | { 20 | _buffer = textBuffer; 21 | _engine = completionEngineSource; 22 | } 23 | 24 | public void AugmentCompletionSession(ICompletionSession session, IList completionSets) 25 | { 26 | if (_buffer.Properties.TryGetProperty(typeof(XamlBufferMetadata), out var metadata) && 27 | metadata.CompletionMetadata != null) 28 | { 29 | var sw = Stopwatch.StartNew(); 30 | var pos = session.TextView.Caret.Position.BufferPosition; 31 | var text = pos.Snapshot.GetText(); 32 | _buffer.Properties.TryGetProperty("AssemblyName", out string assemblyName); 33 | var completions = _engine.CompletionEngine.GetCompletions(metadata.CompletionMetadata, text, pos, assemblyName); 34 | 35 | if (completions?.Completions.Count > 0) 36 | { 37 | var start = completions.StartPosition; 38 | 39 | // TODO: this should be handled in the completion engine 40 | // pseudoclasses should only be returned in a Selector, so this is an easy filter 41 | // We need to offset the start though for pseudoclasses to remove what they're 42 | // attached to: Control:pointerover -> :pointerover 43 | if (completions.Completions[0].DisplayText.StartsWith(":")) 44 | { 45 | for (int i = pos - 1; i >= 0; i--) 46 | { 47 | if (char.IsWhiteSpace(text[i]) || text[i] == ':') 48 | { 49 | start = i; 50 | break; 51 | } 52 | } 53 | } 54 | 55 | var span = new SnapshotSpan(pos.Snapshot, start, pos.Position - start); 56 | var applicableTo = pos.Snapshot.CreateTrackingSpan(span, SpanTrackingMode.EdgeInclusive); 57 | 58 | completionSets.Insert(0, new CompletionSet( 59 | "Avalonia", 60 | "Avalonia", 61 | applicableTo, 62 | XamlCompletion.Create(completions.Completions), 63 | null)); 64 | 65 | // This selects the first item in the completion popup - otherwise you have to physically 66 | // interact with the completion list (either via mouse or keyboard arrows) otherwise tab 67 | // or space won't trigger it 68 | // TODO: We should really try to find the best match of the completion list and select that 69 | // instead, but that's more than I want to do right now 70 | if (completions.Completions.Count > 0) 71 | { 72 | completionSets[0].SelectionStatus = new CompletionSelectionStatus( 73 | completionSets[0].Completions[0], true, false); 74 | } 75 | 76 | string completionHint = completions.Completions.Count == 0 ? 77 | "no completions found" : 78 | $"{completions.Completions.Count} completions found (First:{completions.Completions.FirstOrDefault()?.DisplayText})"; 79 | 80 | Log.Logger.Verbose("XAML completion took {Time}, {CompletionHint}", sw.Elapsed, completionHint); 81 | } 82 | 83 | sw.Stop(); 84 | } 85 | } 86 | 87 | public void Dispose() 88 | { 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/IntelliSense/XamlCompletionSourceProvider.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Composition; 2 | using AvaloniaVS.Models; 3 | using AvaloniaVS.Shared.IntelliSense; 4 | using Microsoft.VisualStudio.Language.Intellisense; 5 | using Microsoft.VisualStudio.Text; 6 | using Microsoft.VisualStudio.Utilities; 7 | 8 | namespace AvaloniaVS.IntelliSense 9 | { 10 | [Export(typeof(ICompletionSourceProvider))] 11 | [ContentType("xml")] 12 | [Name("Avalonia XAML Completion")] 13 | internal class XamlCompletionSourceProvider : ICompletionSourceProvider 14 | { 15 | [ImportingConstructor] 16 | public XamlCompletionSourceProvider([Import] CompletionEngineSource completionEngineSource) 17 | { 18 | _completionEngineSource = completionEngineSource; 19 | } 20 | private readonly CompletionEngineSource _completionEngineSource; 21 | public ICompletionSource TryCreateCompletionSource(ITextBuffer textBuffer) 22 | { 23 | if (textBuffer.Properties.ContainsProperty(typeof(XamlBufferMetadata))) 24 | { 25 | return new XamlCompletionSource(textBuffer, _completionEngineSource); 26 | } 27 | 28 | return null; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/IntelliSense/XamlErrorTableEntry.cs: -------------------------------------------------------------------------------- 1 | using Avalonia.Remote.Protocol.Designer; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | using Microsoft.VisualStudio.Shell.TableManager; 4 | 5 | namespace AvaloniaVS.IntelliSense 6 | { 7 | internal class XamlErrorTableEntry : TableEntryBase 8 | { 9 | private readonly string _projectName; 10 | private readonly string _fileName; 11 | private readonly ExceptionDetails _error; 12 | 13 | public XamlErrorTableEntry( 14 | string projectName, 15 | string path, 16 | ExceptionDetails error) 17 | { 18 | _projectName = projectName; 19 | _fileName = path; 20 | _error = error; 21 | } 22 | 23 | public override bool TryGetValue(string keyName, out object content) 24 | { 25 | switch (keyName) 26 | { 27 | case StandardTableKeyNames.Column: 28 | content = (_error.LinePosition ?? 1) - 1; 29 | return _error.LinePosition.HasValue; 30 | case StandardTableKeyNames.ErrorSeverity: 31 | content = __VSERRORCATEGORY.EC_ERROR; 32 | return true; 33 | case StandardTableKeyNames.DocumentName: 34 | content = _fileName; 35 | return true; 36 | case StandardTableKeyNames.Line: 37 | content = (_error.LineNumber ?? 1) - 1; 38 | return _error.LineNumber.HasValue; 39 | case StandardTableKeyNames.ProjectName: 40 | content = _projectName; 41 | return true; 42 | case StandardTableKeyNames.Text: 43 | content = _error.Message; 44 | return true; 45 | default: 46 | content = null; 47 | return false; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/IntelliSense/XamlErrorTaggerProvider.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Composition; 2 | using AvaloniaVS.Services; 3 | using Microsoft.VisualStudio.Shell.TableManager; 4 | using Microsoft.VisualStudio.Text; 5 | using Microsoft.VisualStudio.Text.Operations; 6 | using Microsoft.VisualStudio.Text.Tagging; 7 | using Microsoft.VisualStudio.Utilities; 8 | 9 | namespace AvaloniaVS.IntelliSense 10 | { 11 | [Export(typeof(ITaggerProvider))] 12 | [ContentType("xml")] 13 | [TagType(typeof(IErrorTag))] 14 | internal class XamlErrorTaggerProvider : ITaggerProvider 15 | { 16 | private readonly ITextStructureNavigatorSelectorService _navigatorProvider; 17 | private readonly ITableManagerProvider _tableManagerProvider; 18 | 19 | [ImportingConstructor] 20 | public XamlErrorTaggerProvider( 21 | ITextStructureNavigatorSelectorService navigatorProvider, 22 | ITableManagerProvider tableManagerProvider) 23 | { 24 | _navigatorProvider = navigatorProvider; 25 | _tableManagerProvider = tableManagerProvider; 26 | } 27 | 28 | public ITagger CreateTagger(ITextBuffer buffer) where T : ITag 29 | { 30 | if (buffer.Properties.TryGetProperty( 31 | typeof(XamlErrorTagger), 32 | out var existing)) 33 | { 34 | return (ITagger)existing; 35 | } 36 | 37 | if (buffer.Properties.TryGetProperty( 38 | typeof(PreviewerProcess), 39 | out var process)) 40 | { 41 | var navigator = _navigatorProvider.GetTextStructureNavigator(buffer); 42 | var tagger = new XamlErrorTagger(_tableManagerProvider, buffer, navigator, process); 43 | buffer.Properties.AddProperty(typeof(XamlErrorTagger), tagger); 44 | tagger.Disposed += (s, e) => buffer.Properties.RemoveProperty(typeof(XamlErrorTagger)); 45 | return (ITagger)tagger; 46 | } 47 | 48 | return null; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/IntelliSense/XamlTextManipulatorRegistrar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using Avalonia.Ide.CompletionEngine; 6 | using AvaloniaVS.Models; 7 | using Microsoft.VisualStudio.Text; 8 | using Microsoft.VisualStudio.Text.Editor; 9 | using Serilog; 10 | 11 | namespace AvaloniaVS.IntelliSense 12 | { 13 | internal class XamlTextManipulatorRegistrar 14 | { 15 | private readonly IWpfTextView _textView; 16 | private readonly ITextBuffer _buffer; 17 | private bool _isChangingText = false; 18 | 19 | public XamlTextManipulatorRegistrar(IWpfTextView textView) 20 | { 21 | _textView = textView; 22 | _buffer = textView.TextBuffer; 23 | 24 | _textView.Closed += TextView_Closed; 25 | _buffer.Changed += TextBuffer_Changed; 26 | } 27 | 28 | private void TextBuffer_Changed(object sender, TextContentChangedEventArgs e) 29 | { 30 | if (_isChangingText) 31 | { 32 | return; 33 | } 34 | 35 | try 36 | { 37 | if (_buffer.Properties.TryGetProperty(typeof(XamlBufferMetadata), out var metadata) && 38 | metadata.CompletionMetadata != null) 39 | { 40 | var sw = Stopwatch.StartNew(); 41 | var pos = _textView.Caret.Position.BufferPosition; 42 | var text = _buffer.CurrentSnapshot.GetText(); 43 | 44 | 45 | foreach (Microsoft.VisualStudio.Text.ITextChange change in e.Changes.ToList()) 46 | { 47 | var textManipulator = new TextManipulator(text, change.NewPosition); 48 | var avaloniaChange = new TextChangeAdapter(change); 49 | var manipulations = textManipulator.ManipulateText(avaloniaChange); 50 | if (manipulations?.Count > 0) 51 | { 52 | _isChangingText = true; 53 | ApplyManipulations(manipulations); 54 | Log.Logger.Verbose("XAML manipulation took {Time}", sw.Elapsed); 55 | } 56 | } 57 | sw.Stop(); 58 | } 59 | } 60 | finally 61 | { 62 | _isChangingText = false; 63 | } 64 | } 65 | 66 | private void ApplyManipulations(IList manipulations) 67 | { 68 | var edit = _buffer.CreateEdit(); 69 | foreach (var manipulation in manipulations) 70 | { 71 | switch (manipulation.Type) 72 | { 73 | case ManipulationType.Insert: 74 | edit.Insert(manipulation.Start, manipulation.Text); 75 | break; 76 | case ManipulationType.Delete: 77 | edit.Delete(Span.FromBounds(manipulation.Start, manipulation.End)); 78 | break; 79 | } 80 | 81 | } 82 | edit.Apply(); 83 | } 84 | 85 | private void TextView_Closed(object sender, EventArgs e) 86 | { 87 | if (_textView != null) 88 | { 89 | _textView.Closed -= TextView_Closed; 90 | _buffer.Changed -= TextBuffer_Changed; 91 | } 92 | } 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/IntelliSense/XamlTextViewCreationListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.Composition; 3 | using AvaloniaVS.Models; 4 | using Microsoft.VisualStudio.Shell; 5 | using Microsoft.VisualStudio.Text.Editor; 6 | using Microsoft.VisualStudio.Utilities; 7 | 8 | namespace AvaloniaVS.IntelliSense 9 | { 10 | /// 11 | /// Registers a with newly-created text views. 12 | /// 13 | [Name("Avalonia XAML manupulator")] 14 | [ContentType("xml")] 15 | [Export(typeof(IWpfTextViewCreationListener))] 16 | [TextViewRole(PredefinedTextViewRoles.Editable)] 17 | [TextViewRole(PredefinedTextViewRoles.PrimaryDocument)] 18 | internal sealed class XamlTextViewCreationListener : IWpfTextViewCreationListener 19 | { 20 | private readonly IServiceProvider _serviceProvider; 21 | 22 | [ImportingConstructor] 23 | public XamlTextViewCreationListener( 24 | [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) 25 | { 26 | _serviceProvider = serviceProvider; 27 | } 28 | 29 | public void TextViewCreated(IWpfTextView textView) 30 | { 31 | // If the buffer contains Avalonia XAML, register a completion handler on it. 32 | if (textView.TextBuffer.Properties.ContainsProperty(typeof(XamlBufferMetadata))) 33 | { 34 | new XamlTextManipulatorRegistrar(textView); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AvaloniaUI/AvaloniaVS/17c10dc2ee8606c0e438a8e349fceda63ad8841b/AvaloniaVS.Shared/Key.snk -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Models/DesignerRunTarget.cs: -------------------------------------------------------------------------------- 1 | using AvaloniaVS.Views; 2 | using EnvDTE; 3 | 4 | namespace AvaloniaVS.Models 5 | { 6 | /// 7 | /// Represents an executable target for the previewer. 8 | /// 9 | internal class DesignerRunTarget 10 | { 11 | /// 12 | /// Gets or sets the target's name 13 | /// 14 | public string Name { get; set; } 15 | 16 | /// 17 | /// Gets or sets the full path to the executable assembly. 18 | /// 19 | public string ExecutableAssembly { get; set; } 20 | 21 | /// 22 | /// Gets or sets the full path to the assembly containing the XAML. 23 | /// 24 | public string XamlAssembly { get; set; } 25 | 26 | public Project Project { get; set; } 27 | 28 | /// 29 | /// Gets the full path to the Avalonia.Designer.HostApp.dll to use. 30 | /// 31 | public string HostApp { get; set; } 32 | 33 | public bool IsNetFramework { get; internal set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Models/ProjectInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using EnvDTE; 3 | 4 | namespace AvaloniaVS.Models 5 | { 6 | /// 7 | /// Holds information required by the designer about a project. 8 | /// 9 | internal class ProjectInfo 10 | { 11 | private IReadOnlyList _projectReferences; 12 | 13 | /// 14 | /// Gets or sets a value indicating whether the project is an executable. 15 | /// 16 | public bool IsExecutable { get; set; } 17 | 18 | /// 19 | /// Gets or sets a value indicating whether the project is a startup project. 20 | /// 21 | public bool IsStartupProject { get; set; } 22 | 23 | /// 24 | /// Gets or sets the project name. 25 | /// 26 | public string Name { get; set; } 27 | 28 | /// 29 | /// Gets or sets the underlying EnvDTE project. 30 | /// 31 | public Project Project { get; set; } 32 | 33 | /// 34 | /// Gets or sets the project's outputs. 35 | /// 36 | public IReadOnlyList Outputs { get; set; } 37 | 38 | public System.Lazy> LazyProjectReferences { get; set; } 39 | 40 | /// 41 | /// Gets or sets the project's project references. 42 | /// 43 | public IReadOnlyList ProjectReferences 44 | { 45 | get => _projectReferences ?? (_projectReferences = LazyProjectReferences?.Value); 46 | set => _projectReferences = value; 47 | } 48 | 49 | /// 50 | /// Gets or sets the project's assembly references. 51 | /// 52 | public IReadOnlyList References { get; set; } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Models/ProjectOutputInfo.cs: -------------------------------------------------------------------------------- 1 | using AvaloniaVS.Utils; 2 | 3 | namespace AvaloniaVS.Models 4 | { 5 | /// 6 | /// Holds information about a 's outputs. 7 | /// 8 | public class ProjectOutputInfo 9 | { 10 | /// 11 | /// Gets the full path to the target assembly for the output. 12 | /// 13 | public string TargetAssembly { get; } 14 | 15 | /// 16 | /// Gets the friendly name of framework for the output. 17 | /// 18 | public string TargetFramework { get; } 19 | 20 | /// 21 | /// Gets the long name of framework for the output. 22 | /// 23 | public string TargetFrameworkIdentifier { get; } 24 | 25 | /// 26 | /// Gets the RuntimeIdentifier of the project. 27 | /// 28 | public string RuntimeIdentifier { get; } 29 | 30 | public string TargetPlatformIdentifier { get; } 31 | 32 | /// 33 | /// Gets the full path to the Avalonia.Designer.HostApp.dll to use. 34 | /// 35 | public string HostApp { get; } 36 | 37 | /// 38 | /// Gets a value indicating whether the target framework is .NET Framework. 39 | /// 40 | public bool IsNetFramework => FrameworkInfoUtils.IsNetFramework(TargetFrameworkIdentifier); 41 | 42 | /// 43 | /// Gets a value indicating whether the target framework is .NET Core. 44 | /// 45 | public bool IsNetCore => FrameworkInfoUtils.IsNetCoreApp(TargetFrameworkIdentifier); 46 | 47 | /// 48 | /// Gets a value indicating whether the target framework is .NET Standard. 49 | /// 50 | public bool IsNetStandard => FrameworkInfoUtils.IsNetStandard(TargetFrameworkIdentifier); 51 | 52 | public ProjectOutputInfo( 53 | string targetAssembly, string targetFramework, string targetFrameworkIdentifier, string hostApp, string runtimeIdentifier, string targetPlatformIdentifier) 54 | { 55 | TargetAssembly = targetAssembly; 56 | TargetFramework = targetFramework; 57 | TargetFrameworkIdentifier = targetFrameworkIdentifier; 58 | HostApp = hostApp; 59 | RuntimeIdentifier = runtimeIdentifier; 60 | TargetPlatformIdentifier = targetPlatformIdentifier; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Models/XamlBufferMetadata.cs: -------------------------------------------------------------------------------- 1 | using CompletionMetadata = Avalonia.Ide.CompletionEngine.Metadata; 2 | 3 | namespace AvaloniaVS.Models 4 | { 5 | internal class XamlBufferMetadata 6 | { 7 | public CompletionMetadata CompletionMetadata { get; set; } 8 | 9 | public bool NeedInvalidation { get; set; } = true; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/ProjectSystem/AvaloniaProject.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.ProjectSystem; 2 | using Microsoft.VisualStudio.Shell; 3 | using System.ComponentModel.Composition; 4 | using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; 5 | using Microsoft.VisualStudio.Shell.Interop; 6 | using Task = System.Threading.Tasks.Task; 7 | 8 | namespace AvaloniaVS.ProjectSystem; 9 | 10 | [Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))] 11 | [AppliesTo(Constants.AvaloniaCapability)] 12 | internal class AvaloniaProject : IProjectDynamicLoadComponent 13 | { 14 | private IAsyncServiceProvider asyncServiceProvider; 15 | 16 | public async Task LoadAsync() 17 | { 18 | if (asyncServiceProvider is null) 19 | { 20 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 21 | if (ServiceProvider.GlobalProvider.GetService(typeof(IVsShell)) is IVsShell shell) 22 | { 23 | if (shell.IsPackageLoaded(Constants.PackageGuid, out var vsPackage) 24 | != Microsoft.VisualStudio.VSConstants.S_OK) 25 | { 26 | shell.LoadPackage(Constants.PackageGuid, out vsPackage); 27 | } 28 | asyncServiceProvider = (IAsyncServiceProvider)vsPackage; 29 | } 30 | } 31 | } 32 | 33 | public async Task UnloadAsync() 34 | { 35 | // Unload the feature 36 | await Task.CompletedTask; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Resources/AvaloniaPackage.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AvaloniaUI/AvaloniaVS/17c10dc2ee8606c0e438a8e349fceda63ad8841b/AvaloniaVS.Shared/Resources/AvaloniaPackage.ico -------------------------------------------------------------------------------- /AvaloniaVS.Shared/ServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft; 3 | using Microsoft.VisualStudio.ComponentModelHost; 4 | using Microsoft.VisualStudio.Shell; 5 | 6 | namespace AvaloniaVS 7 | { 8 | internal static class ServiceProviderExtensions 9 | { 10 | public static T GetService(this IServiceProvider sp) 11 | { 12 | ThreadHelper.ThrowIfNotOnUIThread(); 13 | var result = (T)sp.GetService(typeof(T)); 14 | Assumes.Present(result); 15 | return result; 16 | } 17 | 18 | public static TResult GetService(this IServiceProvider sp) 19 | { 20 | ThreadHelper.ThrowIfNotOnUIThread(); 21 | var result = sp.GetService(typeof(TService)); 22 | Assumes.Present(result); 23 | return (TResult)result; 24 | } 25 | 26 | public static T GetMefService(this IServiceProvider sp) where T : class 27 | { 28 | ThreadHelper.ThrowIfNotOnUIThread(); 29 | var componentModel = sp.GetService(); 30 | var result = componentModel.GetService(); 31 | Assumes.Present(result); 32 | return result; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Services/IAvaloniaVSSettings.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Windows.Controls; 3 | using AvaloniaVS.Views; 4 | using Serilog.Events; 5 | 6 | namespace AvaloniaVS.Services 7 | { 8 | public interface IAvaloniaVSSettings : INotifyPropertyChanged 9 | { 10 | Orientation DesignerSplitOrientation { get; set; } 11 | bool DesignerSplitSwapped { get; set; } 12 | AvaloniaDesignerView DesignerView { get; set; } 13 | LogEventLevel MinimumLogVerbosity { get; set; } 14 | string ZoomLevel { get; set; } 15 | void Save(); 16 | void Load(); 17 | bool UsageTracking { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Services/IVsFindTarget3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | // Do not change the namespace; otherwise, search box is broken in VS2022 5 | namespace Microsoft.VisualStudio.Editor.Internal 6 | { 7 | [ComImport] 8 | [TypeIdentifier] 9 | [Guid("A2F0D62B-D0DD-4C59-AAB8-79CD20785451")] 10 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 11 | public interface IVsFindTarget3 12 | { 13 | [PreserveSig] 14 | int get_IsNewUISupported(); 15 | 16 | [PreserveSig] 17 | int NotifyShowingNewUI(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Services/OutputPaneEventSink.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.VisualStudio; 4 | using Microsoft.VisualStudio.Shell; 5 | using Microsoft.VisualStudio.Shell.Interop; 6 | using Serilog.Core; 7 | using Serilog.Events; 8 | using Serilog.Formatting; 9 | using Serilog.Formatting.Display; 10 | 11 | namespace AvaloniaVS.Services 12 | { 13 | /// 14 | /// A serilog sink that outputs to the VS output window. 15 | /// 16 | internal class OutputPaneEventSink : ILogEventSink 17 | { 18 | private static readonly Guid paneGuid = new Guid("DC845612-459C-485C-8157-71BC39C9A044"); 19 | private readonly IVsOutputWindowPane _pane; 20 | private readonly ITextFormatter _formatter; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The VS output window. 26 | /// The serilog output template. 27 | public OutputPaneEventSink( 28 | IVsOutputWindow output, 29 | string outputTemplate) 30 | { 31 | ThreadHelper.ThrowIfNotOnUIThread(); 32 | 33 | _formatter = new MessageTemplateTextFormatter(outputTemplate, null); 34 | ErrorHandler.ThrowOnFailure(output.CreatePane(paneGuid, "Avalonia Diagnostics", 1, 1)); 35 | output.GetPane(paneGuid, out _pane); 36 | } 37 | 38 | #pragma warning disable VSTHRD010 39 | /// 40 | public void Emit(LogEvent logEvent) 41 | { 42 | var sw = new StringWriter(); 43 | _formatter.Format(logEvent, sw); 44 | var message = sw.ToString(); 45 | 46 | if (_pane is IVsOutputWindowPaneNoPump noPump) 47 | { 48 | noPump.OutputStringNoPump(message); 49 | } 50 | else 51 | { 52 | ErrorHandler.ThrowOnFailure(_pane.OutputStringThreadSafe(message)); 53 | } 54 | 55 | if (logEvent.Level == LogEventLevel.Error) 56 | { 57 | _pane.Activate(); 58 | } 59 | } 60 | #pragma warning restore VSTHRD010 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Services/Throttle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Threading; 3 | 4 | namespace AvaloniaVS.Services 5 | { 6 | public class Throttle : IDisposable 7 | { 8 | private readonly Action _execute; 9 | private readonly DispatcherTimer _timer; 10 | private T _value; 11 | 12 | public Throttle(TimeSpan interval, Action execute) 13 | { 14 | _execute = execute; 15 | 16 | _timer = new DispatcherTimer 17 | { 18 | Interval = interval, 19 | }; 20 | 21 | _timer.Tick += Tick; 22 | } 23 | 24 | public void Queue(T value) 25 | { 26 | if (!Equals(value, _value)) 27 | { 28 | _timer.Stop(); 29 | _value = value; 30 | _timer.Start(); 31 | } 32 | } 33 | 34 | public void Dispose() => _timer.Stop(); 35 | 36 | private void Tick(object sender, EventArgs e) 37 | { 38 | _execute(_value); 39 | _timer.Stop(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Services/VsProjectAssembliesProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using Avalonia.Ide.CompletionEngine.AssemblyMetadata; 5 | using Microsoft.VisualStudio.Shell; 6 | using Serilog; 7 | using VSLangProj; 8 | 9 | namespace AvaloniaVS.Shared.Services 10 | { 11 | // VS API requires this code to run on Main Thread, so we have to fetch that ahead. 12 | internal class VsProjectAssembliesProvider : IAssemblyProvider 13 | { 14 | private readonly List _references; 15 | 16 | private VsProjectAssembliesProvider(List references) 17 | { 18 | _references = references; 19 | } 20 | 21 | public static VsProjectAssembliesProvider TryCreate(EnvDTE.Project project, string xamlPrimaryAssemblyPath) 22 | { 23 | ThreadHelper.ThrowIfNotOnUIThread(); 24 | 25 | try 26 | { 27 | if (project.Object is VSProject vsProject) 28 | { 29 | var references = new List(200); 30 | references.Add(xamlPrimaryAssemblyPath); 31 | 32 | foreach (Reference reference in vsProject.References) 33 | { 34 | if (reference.Type == prjReferenceType.prjReferenceTypeAssembly 35 | && reference.Path is not null) 36 | { 37 | references.Add(reference.Path); 38 | } 39 | } 40 | 41 | // Not sure if it's possible, but never know what surprise VS has. 42 | if (references.Count == 1) 43 | return null; 44 | 45 | return new VsProjectAssembliesProvider(references); 46 | } 47 | } 48 | catch (Exception ex) 49 | { 50 | Log.Logger.Error(ex, "VsProjectAssembliesProvider.TryCreate failed with an exception."); 51 | } 52 | return null; 53 | } 54 | 55 | public IEnumerable GetAssemblies() => _references; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/Avalonia/Avalonia_AP00.snippet: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | Expansion 7 | 8 | 01 -- AttachedProperty 9 | attachedAvaloniaProperty 10 | Define AttachedProperty. 11 | Avalonia Team 12 |
13 | 14 | 15 | 16 | property 17 | AttachedProperty Name 18 | IsCapable 19 | 20 | 21 | type 22 | AttachedProperty Type 23 | bool 24 | 25 | 26 | ownerclass 27 | The owning class of this property (which is typically the class in which it is declared). 28 | ownerclass 29 | ClassName() 30 | 31 | 32 | hostclass 33 | The type of the class that the property is to be registered on. 34 | StyledElement 35 | 36 | 37 | defaultvalue 38 | The default value for this property. 39 | default 40 | 41 | 42 | desc 43 | A description for the property. 44 | ... 45 | 46 | 47 | 48 | 49 | 51 | /// $property$ AttachedProperty definition 52 | /// indicates $desc$. 53 | /// 54 | public static readonly AttachedProperty<$type$> $property$Property = 55 | AvaloniaProperty.RegisterAttached<$ownerclass$, $hostclass$ ,$type$>("$property$"); 56 | 57 | /// 58 | /// Accessor for Attached property . 59 | /// 60 | /// Target element 61 | /// The value to set . 62 | public static void Set$property$($hostclass$ element, $type$ value) => 63 | element.SetValue($property$Property, value); 64 | 65 | /// 66 | /// Accessor for Attached property . 67 | /// 68 | /// Target element 69 | public static $type$ Get$property$($hostclass$ element) => 70 | element.GetValue($property$Property); 71 | 72 | $end$]]> 73 | 74 | 75 | 76 | Avalonia 77 | 78 | 79 | 80 |
81 |
82 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/Avalonia/Avalonia_AP01.snippet: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | Expansion 7 | 8 | 02 -- AttachedProperty with default value 9 | attachedAvaloniaProperty 10 | Define AttachedProperty. 11 | Avalonia Team 12 |
13 | 14 | 15 | 16 | property 17 | AttachedProperty Name 18 | IsCapable 19 | 20 | 21 | type 22 | AttachedProperty Type 23 | bool 24 | 25 | 26 | ownerclass 27 | The owning class of this property (which is typically the class in which it is declared). 28 | ownerclass 29 | ClassName() 30 | 31 | 32 | hostclass 33 | The type of the class that the property is to be registered on. 34 | StyledElement 35 | 36 | 37 | defaultvalue 38 | The default value for this property. 39 | default 40 | 41 | 42 | desc 43 | A description for the property. 44 | ... 45 | 46 | 47 | 48 | 49 | 51 | /// $property$ AttachedProperty definition 52 | /// indicates $desc$. 53 | /// 54 | public static readonly AttachedProperty<$type$> $property$Property = 55 | AvaloniaProperty.RegisterAttached<$ownerclass$, $hostclass$ ,$type$>("$property$",$defaultvalue$); 56 | 57 | /// 58 | /// Accessor for Attached property . 59 | /// 60 | /// Target element 61 | /// The value to set . 62 | public static void Set$property$($hostclass$ element, $type$ value) => 63 | element.SetValue($property$Property, value); 64 | 65 | /// 66 | /// Accessor for Attached property . 67 | /// 68 | /// Target element 69 | public static $type$ Get$property$($hostclass$ element) => 70 | element.GetValue($property$Property); 71 | 72 | $end$]]> 73 | 74 | 75 | 76 | Avalonia 77 | 78 | 79 | 80 |
81 |
82 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/Avalonia/Avalonia_DP00.snippet: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | Expansion 7 | 8 | 01 -- DirectProperty 9 | directProperty 10 | Define DirectProperty. 11 | Avalonia Team 12 |
13 | 14 | 15 | 16 | property 17 | Property Name 18 | IsCapable 19 | 20 | 21 | type 22 | Property Type 23 | bool 24 | 25 | 26 | ownerclass 27 | The owning class of this property (which is typically the class in which it is declared). 28 | ownerclass 29 | ClassName() 30 | 31 | 32 | defaultvalue 33 | The default value for this property. 34 | default 35 | 36 | 37 | desc 38 | A description for the property. 39 | ... 40 | 41 | 42 | 43 | 44 | 46 | /// $property$ DirectProperty definition 47 | /// 48 | public static readonly DirectProperty<$ownerclass$, $type$> $property$Property = 49 | AvaloniaProperty.RegisterDirect<$ownerclass$, $type$>(nameof($property$), 50 | o => o.$property$, 51 | (o, v) => o.$property$ = v); 52 | 53 | private $type$ _$property$ = $defaultvalue$; 54 | /// 55 | /// Gets or sets the $property$ property. This DirectProperty 56 | /// indicates $desc$. 57 | /// 58 | public $type$ $property$ 59 | { 60 | get => _$property$; 61 | set => SetAndRaise($property$Property, ref _$property$, value); 62 | } 63 | 64 | $end$]]> 65 | 66 | 67 | 68 | Avalonia 69 | 70 | 71 | 72 |
73 |
74 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/Avalonia/Avalonia_DP01.snippet: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | Expansion 7 | 8 | 02 -- readonly DirectProperty 9 | directProperty 10 | Define read only DirectProperty. 11 | Avalonia Team 12 |
13 | 14 | 15 | 16 | property 17 | Property Name 18 | IsCapable 19 | 20 | 21 | type 22 | Property Type 23 | bool 24 | 25 | 26 | ownerclass 27 | The owning class of this property (which is typically the class in which it is declared). 28 | ownerclass 29 | ClassName() 30 | 31 | 32 | defaultvalue 33 | The default value for this property. 34 | default 35 | 36 | 37 | desc 38 | A description for the property. 39 | ... 40 | 41 | 42 | 43 | 44 | 46 | /// $property$ DirectProperty definition 47 | /// 48 | public static readonly DirectProperty<$ownerclass$, $type$> $property$Property = 49 | AvaloniaProperty.RegisterDirect<$ownerclass$, $type$>(nameof($property$), 50 | o => o.$property$); 51 | 52 | 53 | private $type$ _$property$ = $defaultvalue$; 54 | /// 55 | /// Gets or sets the $property$ property. This DirectProperty 56 | /// indicates $desc$. 57 | /// 58 | public $type$ $property$ 59 | { 60 | get => _$property$; 61 | private set => SetAndRaise($property$Property, ref _$property$, value); 62 | } 63 | 64 | $end$]]> 65 | 66 | 67 | 68 | Avalonia 69 | 70 | 71 | 72 |
73 |
74 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/Avalonia/Avalonia_RE00.snippet: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | Expansion 7 | 8 | 01 -- RoutedEvent 9 | routedEvent 10 | Define RoutedEvent. 11 | Avalonia Team 12 |
13 | 14 | 15 | 16 | event 17 | Name of RoutedEvent 18 | My 19 | 20 | 21 | ownerclass 22 | The owning class of this event (which is typically the class in which it is declared). 23 | ownerclass 24 | ClassName() 25 | 26 | 27 | eventArgs 28 | EventArgs Type 29 | RoutedEventArgs 30 | 31 | 32 | strategies 33 | Routed events use one of three routing strategies. 34 | Bubble 35 | 36 | 37 | desc 38 | A description for the RoutedEvent. 39 | ... 40 | 41 | 42 | 43 | 44 | 46 | /// $event$Event is raise when $desc$. 47 | /// 48 | public static readonly RoutedEvent<$eventArgs$> $event$Event = 49 | RoutedEvent.Register<$eventArgs$>( 50 | "$event$", RoutingStrategies.$strategies$, typeof($ownerclass$)); 51 | $end$]]> 52 | 53 | 54 | 55 | System 56 | 57 | 58 | Avalonia.Interactivity 59 | 60 | 61 | 62 |
63 |
64 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/Avalonia/Avalonia_RE01.snippet: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | Expansion 7 | 8 | 02 -- RoutedEvent with Handler 9 | routedEvent 10 | Define RoutedEvent. 11 | Avalonia Team 12 |
13 | 14 | 15 | 16 | event 17 | Name of RoutedEvent 18 | My 19 | 20 | 21 | ownerclass 22 | The owning class of this event (which is typically the class in which it is declared). 23 | ownerclass 24 | ClassName() 25 | 26 | 27 | eventArgs 28 | EventArgs Type 29 | RoutedEventArgs 30 | 31 | 32 | strategies 33 | Routed events use one of three routing strategies. 34 | Bubble 35 | 36 | 37 | desc 38 | A description for the RoutedEvent. 39 | ... 40 | 41 | 42 | 43 | 44 | 46 | /// $event$Event is raise when $desc$. 47 | /// 48 | public static readonly RoutedEvent<$eventArgs$> $event$Event = 49 | RoutedEvent.Register<$eventArgs$>( 50 | "$event$", RoutingStrategies.$strategies$, typeof($ownerclass$)); 51 | 52 | public static void Add$event$Handler(Interactive element, EventHandler<$eventArgs$> handler) => 53 | element.AddHandler($event$Event, handler); 54 | 55 | public static void Remove$event$Handler(Interactive element, EventHandler<$eventArgs$> handler) => 56 | element.RemoveHandler($event$Event, handler); 57 | 58 | $end$]]> 59 | 60 | 61 | 62 | System 63 | 64 | 65 | Avalonia.Interactivity 66 | 67 | 68 | 69 |
70 |
71 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/Avalonia/Avalonia_RE02.snippet: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | Expansion 7 | 8 | 03 -- RoutedEventArgs 9 | routedEvent 10 | Define Custom RoutedEventArgs. 11 | Avalonia Team 12 |
13 | 14 | 15 | 16 | event 17 | RoutedEvent 18 | Fire 19 | 20 | 21 | ownerclass 22 | The owning class of this event (which is typically the class in which it is declared). 23 | ownerclass 24 | FileName() 25 | 26 | 27 | desc 28 | A description for the RoutedEvent. 29 | ... 30 | 31 | 32 | 33 | 34 | 42 | 43 | 44 | 45 | System 46 | 47 | 48 | Avalonia.Interactivity 49 | 50 | 51 | 52 |
53 |
54 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/Avalonia/Avalonia_SP00.snippet: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | Expansion 7 | 8 | 01 -- StyledProperty 9 | styledProperty 10 | Define StyledProperty. 11 | Avalonia Team 12 |
13 | 14 | 15 | 16 | property 17 | Property Name 18 | IsCapable 19 | 20 | 21 | type 22 | Property Type 23 | bool 24 | 25 | 26 | ownerclass 27 | The owning class of this property (which is typically the class in which it is declared). 28 | ownerclass 29 | ClassName() 30 | 31 | 32 | defaultvalue 33 | The default value for this property. 34 | default 35 | 36 | 37 | desc 38 | A description for the property. 39 | ... 40 | 41 | 42 | 43 | 44 | 46 | /// $property$ StyledProperty definition 47 | /// 48 | public static readonly StyledProperty<$type$> $property$Property = 49 | AvaloniaProperty.Register<$ownerclass$, $type$>(nameof($property$)); 50 | 51 | /// 52 | /// Gets or sets the $property$ property. This StyledProperty 53 | /// indicates $desc$. 54 | /// 55 | public $type$ $property$ 56 | { 57 | get => this.GetValue($property$Property); 58 | set => SetValue($property$Property, value); 59 | } 60 | 61 | $end$]]> 62 | 63 | 64 | 65 | Avalonia 66 | 67 | 68 | 69 |
70 |
71 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/Avalonia/Avalonia_SP01.snippet: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | Expansion 7 | 8 | 02 -- StyledProperty with default value 9 | styledProperty 10 | Define StyledProperty. 11 | Avalonia Team 12 |
13 | 14 | 15 | 16 | property 17 | Property Name 18 | IsCapable 19 | 20 | 21 | type 22 | Property Type 23 | bool 24 | 25 | 26 | ownerclass 27 | The owning class of this property (which is typically the class in which it is declared). 28 | ownerclass 29 | ClassName() 30 | 31 | 32 | defaultvalue 33 | The default value for this property. 34 | default 35 | 36 | 37 | desc 38 | A description for the property. 39 | ... 40 | 41 | 42 | 43 | 44 | 46 | /// $property$ StyledProperty definition 47 | /// 48 | public static readonly StyledProperty<$type$> $property$Property = 49 | AvaloniaProperty.Register<$ownerclass$, $type$>(nameof($property$), $defaultvalue$); 50 | 51 | /// 52 | /// Gets or sets the $property$ property. This StyledProperty 53 | /// indicates $desc$. 54 | /// 55 | public $type$ $property$ 56 | { 57 | get => this.GetValue($property$Property); 58 | set => SetValue($property$Property, value); 59 | } 60 | 61 | $end$]]> 62 | 63 | 64 | 65 | Avalonia 66 | 67 | 68 | 69 |
70 |
71 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Snippets/snippets.pkgdef: -------------------------------------------------------------------------------- 1 | [$RootKey$\Languages\CodeExpansions\CSharp\Paths] 2 | "Avalonia Snippet Pack"="$PackageFolder$\Avalonia" 3 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/SuggestedActions/Actions/Base/BaseSuggestedAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.VisualStudio.Imaging.Interop; 6 | using Microsoft.VisualStudio.Language.Intellisense; 7 | 8 | namespace AvaloniaVS.Shared.SuggestedActions.Actions.Base 9 | { 10 | internal class BaseSuggestedAction 11 | { 12 | public bool HasActionSets { get; } 13 | 14 | public ImageMoniker IconMoniker { get; } 15 | 16 | public string IconAutomationText { get; } 17 | 18 | public string InputGestureText { get; } 19 | 20 | public bool HasPreview => true; 21 | 22 | public void Dispose() 23 | { 24 | } 25 | 26 | public bool TryGetTelemetryId(out Guid telemetryId) 27 | { 28 | telemetryId = Guid.Empty; 29 | return false; 30 | } 31 | 32 | public Task> GetActionSetsAsync(CancellationToken cancellationToken) 33 | { 34 | return Task.FromResult>(null); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/SuggestedActions/Actions/MissingAliasSuggestedAction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using AvaloniaVS.Shared.SuggestedActions.Actions.Base; 6 | using AvaloniaVS.Shared.SuggestedActions.Helpers; 7 | using Microsoft.VisualStudio.Language.Intellisense; 8 | using Microsoft.VisualStudio.Text; 9 | using Microsoft.VisualStudio.Text.Differencing; 10 | using Microsoft.VisualStudio.Text.Editor; 11 | 12 | namespace AvaloniaVS.Shared.SuggestedActions.Actions 13 | { 14 | internal class MissingAliasSuggestedAction : BaseSuggestedAction, ISuggestedAction 15 | { 16 | private readonly ITrackingSpan _span; 17 | private readonly ITextSnapshot _snapshot; 18 | private readonly string _targetClassName; 19 | private readonly string _namespaceAlias; 20 | private readonly IWpfDifferenceViewerFactoryService _diffFactory; 21 | private readonly IDifferenceBufferFactoryService _diffBufferFactory; 22 | private readonly ITextBufferFactoryService _bufferFactory; 23 | private readonly ITextViewRoleSet _previewRoleSet; 24 | 25 | public MissingAliasSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, IDifferenceBufferFactoryService diffBufferFactory, ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, IReadOnlyDictionary inverseNamespaces) 26 | { 27 | _span = span; 28 | _snapshot = _span.TextBuffer.CurrentSnapshot; 29 | _targetClassName = _span.GetText(_snapshot); 30 | var targetClassMetadata = inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _targetClassName); 31 | _namespaceAlias = targetClassMetadata.Value.Split(':').Last().Split('.').Last(); 32 | _diffFactory = diffFactory; 33 | _diffBufferFactory = diffBufferFactory; 34 | _bufferFactory = bufferFactory; 35 | _previewRoleSet = textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Analyzable); 36 | DisplayText = $"Use {_namespaceAlias.ToLower()} ({targetClassMetadata.Value})"; 37 | } 38 | 39 | public string DisplayText { get; } 40 | 41 | public Task GetPreviewAsync(CancellationToken cancellationToken) 42 | { 43 | return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplySuggestion)); 44 | } 45 | 46 | private void ApplySuggestion(ITextBuffer buffer) 47 | { 48 | buffer.Replace(_span.GetSpan(_snapshot), $"{_namespaceAlias.ToLower()}:{_targetClassName}"); 49 | } 50 | 51 | public void Invoke(CancellationToken cancellationToken) 52 | { 53 | ApplySuggestion(_span.TextBuffer); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceAndAliasSuggestedAction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using AvaloniaVS.Shared.SuggestedActions.Actions.Base; 6 | using AvaloniaVS.Shared.SuggestedActions.Helpers; 7 | using Microsoft.VisualStudio.Language.Intellisense; 8 | using Microsoft.VisualStudio.Text; 9 | using Microsoft.VisualStudio.Text.Differencing; 10 | using Microsoft.VisualStudio.Text.Editor; 11 | 12 | namespace AvaloniaVS.Shared.SuggestedActions.Actions 13 | { 14 | internal class MissingNamespaceAndAliasSuggestedAction : BaseSuggestedAction, ISuggestedAction 15 | { 16 | private readonly ITrackingSpan _span; 17 | private readonly ITextSnapshot _snapshot; 18 | private readonly string _namespaceAlias; 19 | private readonly string _targetClassName; 20 | private readonly KeyValuePair _targetClassMetadata; 21 | private readonly IWpfDifferenceViewerFactoryService _diffFactory; 22 | private readonly IDifferenceBufferFactoryService _diffBufferFactory; 23 | private readonly ITextBufferFactoryService _bufferFactory; 24 | private readonly Dictionary _aliases; 25 | private readonly ITextViewRoleSet _previewRoleSet; 26 | 27 | public MissingNamespaceAndAliasSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, 28 | IDifferenceBufferFactoryService diffBufferFactory, ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, 29 | IReadOnlyDictionary inverseNamespaces, Dictionary aliases) 30 | { 31 | _span = span; 32 | _snapshot = _span.TextBuffer.CurrentSnapshot; 33 | _targetClassName = _span.GetText(_snapshot); 34 | _targetClassMetadata = inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _targetClassName); 35 | 36 | // _targetClassMetadata.Value is the namespace of the control we are trying to add the namespace to. 37 | // It is usually in the format using:MyNamespace.Something. 38 | // So to get the prefix for the control we are splitting it by ':' 39 | // Then taking the MyNamespace.Something part and splitting it by '.' and getting Something. 40 | _namespaceAlias = _targetClassMetadata.Value.Split(':').Last().Split('.').Last(); 41 | DisplayText = $"Add xmlns {_namespaceAlias}"; 42 | _diffFactory = diffFactory; 43 | _diffBufferFactory = diffBufferFactory; 44 | _bufferFactory = bufferFactory; 45 | _aliases = aliases; 46 | _previewRoleSet = textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Analyzable); 47 | } 48 | 49 | public string DisplayText { get; } 50 | 51 | 52 | public Task GetPreviewAsync(CancellationToken cancellationToken) 53 | { 54 | return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplySuggestion)); 55 | } 56 | 57 | public void Invoke(CancellationToken cancellationToken) 58 | { 59 | if (cancellationToken.IsCancellationRequested) 60 | { 61 | return; 62 | } 63 | ApplySuggestion(_span.TextBuffer); 64 | } 65 | 66 | private void ApplySuggestion(ITextBuffer buffer) 67 | { 68 | var lastNs = _aliases.Last().Value; 69 | 70 | buffer.Replace(_span.GetSpan(_snapshot), $"{_namespaceAlias.ToLower()}:{_targetClassName}"); 71 | 72 | // We get the index of the last namespace in the list and add the last namespace length without quotes and add 2. 73 | // One for qutation mark and one to place the new namespace in an empty space. 74 | buffer.Insert(buffer.CurrentSnapshot.GetText().IndexOf(lastNs) + lastNs.Length + 2, $"xmlns:{_namespaceAlias.ToLower()}=\"{_targetClassMetadata.Value}\""); 75 | } 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/SuggestedActions/Actions/MissingNamespaceSuggestedAction.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using AvaloniaVS.Shared.SuggestedActions.Actions.Base; 6 | using AvaloniaVS.Shared.SuggestedActions.Helpers; 7 | using Microsoft.VisualStudio.Language.Intellisense; 8 | using Microsoft.VisualStudio.Text; 9 | using Microsoft.VisualStudio.Text.Differencing; 10 | using Microsoft.VisualStudio.Text.Editor; 11 | 12 | namespace AvaloniaVS.Shared.SuggestedActions.Actions 13 | { 14 | internal class MissingNamespaceSuggestedAction : BaseSuggestedAction, ISuggestedAction 15 | { 16 | private readonly ITrackingSpan _span; 17 | private readonly KeyValuePair _targetClassMetadata; 18 | private readonly IWpfDifferenceViewerFactoryService _diffFactory; 19 | private readonly IDifferenceBufferFactoryService _diffBufferFactory; 20 | private readonly ITextBufferFactoryService _bufferFactory; 21 | private readonly Dictionary _aliases; 22 | private readonly string _alias; 23 | private readonly ITextViewRoleSet _previewRoleSet; 24 | 25 | public MissingNamespaceSuggestedAction(ITrackingSpan span, IWpfDifferenceViewerFactoryService diffFactory, IDifferenceBufferFactoryService diffBufferFactory, 26 | ITextBufferFactoryService bufferFactory, ITextEditorFactoryService textEditorFactoryService, IReadOnlyDictionary inverseNamespaces, 27 | Dictionary aliases, string alias) 28 | { 29 | _span = span; 30 | _targetClassMetadata = inverseNamespaces.FirstOrDefault(x => x.Key.Split('.').Last() == _span.GetText(_span.TextBuffer.CurrentSnapshot)); 31 | DisplayText = $"Add xmlns {alias}"; 32 | _diffFactory = diffFactory; 33 | _diffBufferFactory = diffBufferFactory; 34 | _bufferFactory = bufferFactory; 35 | _aliases = aliases; 36 | _alias = alias; 37 | _previewRoleSet = textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Analyzable); 38 | } 39 | 40 | public string DisplayText { get; } 41 | 42 | public Task GetPreviewAsync(CancellationToken cancellationToken) 43 | { 44 | return Task.FromResult(PreviewProvider.GetPreview(_bufferFactory, _span, _diffBufferFactory, _diffFactory, _previewRoleSet, ApplySuggestion)); 45 | } 46 | 47 | public void Invoke(CancellationToken cancellationToken) 48 | { 49 | if (cancellationToken.IsCancellationRequested) 50 | { 51 | return; 52 | } 53 | ApplySuggestion(_span.TextBuffer); 54 | } 55 | 56 | private void ApplySuggestion(ITextBuffer buffer) 57 | { 58 | var lastNs = _aliases.Last().Value; 59 | 60 | buffer.Insert(buffer.CurrentSnapshot.GetText().IndexOf(lastNs) + lastNs.Length + 2, $"xmlns:{_alias}=\"{_targetClassMetadata.Value}\""); 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/SuggestedActions/Helpers/PreviewProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using Microsoft.VisualStudio.Text; 4 | using Microsoft.VisualStudio.Text.Differencing; 5 | using Microsoft.VisualStudio.Text.Editor; 6 | 7 | namespace AvaloniaVS.Shared.SuggestedActions.Helpers 8 | { 9 | internal static class PreviewProvider 10 | { 11 | public static FrameworkElement GetPreview(ITextBufferFactoryService bufferFactory, ITrackingSpan span, IDifferenceBufferFactoryService diffBufferFactory, IWpfDifferenceViewerFactoryService diffFactory, ITextViewRoleSet previewRoleSet, Action applyNamespaceSuggestionAction) 12 | { 13 | var snapshot = span.TextBuffer.CurrentSnapshot; 14 | 15 | var leftBuffer = bufferFactory.CreateTextBuffer(snapshot.GetText(), span.TextBuffer.ContentType); 16 | 17 | var rightBuffer = bufferFactory.CreateTextBuffer(snapshot.GetText(), span.TextBuffer.ContentType); 18 | 19 | applyNamespaceSuggestionAction(rightBuffer); 20 | 21 | var diffBuffer = diffBufferFactory.CreateDifferenceBuffer(leftBuffer, rightBuffer); 22 | var diffView = diffFactory.CreateDifferenceView(diffBuffer, previewRoleSet); 23 | diffView.ViewMode = DifferenceViewMode.Inline; 24 | diffView.InlineView.VisualElement.Focusable = false; 25 | 26 | // DiffView size to content 27 | diffView.DifferenceBuffer.SnapshotDifferenceChanged += (sender, args) => 28 | { 29 | diffView.InlineView.DisplayTextLineContainingBufferPosition( 30 | new SnapshotPoint(diffView.DifferenceBuffer.CurrentInlineBufferSnapshot, 0), 31 | 0.0, ViewRelativePosition.Top, double.MaxValue, double.MaxValue 32 | ); 33 | 34 | var width = Math.Max(diffView.InlineView.MaxTextRightCoordinate * (diffView.InlineView.ZoomLevel / 100), 400); // Width of the widest line. 35 | var height = diffView.InlineView.LineHeight * (diffView.InlineView.ZoomLevel / 100) * // Height of each line. 36 | diffView.DifferenceBuffer.CurrentInlineBufferSnapshot.LineCount; 37 | 38 | diffView.VisualElement.Width = width; 39 | diffView.VisualElement.Height = height; 40 | }; 41 | return diffView.VisualElement; 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/SuggestedActions/SuggestedActionsSourceProvider.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.Composition; 2 | using Microsoft.VisualStudio.Language.Intellisense; 3 | using Microsoft.VisualStudio.Text; 4 | using Microsoft.VisualStudio.Text.Differencing; 5 | using Microsoft.VisualStudio.Text.Editor; 6 | using Microsoft.VisualStudio.Text.Operations; 7 | using Microsoft.VisualStudio.Utilities; 8 | 9 | namespace AvaloniaVS.Shared.SuggestedActions 10 | { 11 | [Export(typeof(ISuggestedActionsSourceProvider))] 12 | [Name("SuggestedActionsSourceProvider")] 13 | [ContentType("xml")] 14 | internal class SuggestedActionsSourceProvider : ISuggestedActionsSourceProvider 15 | { 16 | private readonly IWpfDifferenceViewerFactoryService _diffFactory; 17 | private readonly IDifferenceBufferFactoryService _diffBufferFactory; 18 | private readonly ITextBufferFactoryService _bufferFactory; 19 | private readonly ITextEditorFactoryService _textEditorFactoryService; 20 | 21 | [ImportingConstructor] 22 | public SuggestedActionsSourceProvider([Import] IWpfDifferenceViewerFactoryService diffFactory, [Import] IDifferenceBufferFactoryService diffBufferFactory, 23 | [Import] ITextBufferFactoryService bufferFactory, [Import] ITextEditorFactoryService textEditorFactoryService) 24 | { 25 | _diffFactory = diffFactory; 26 | _diffBufferFactory = diffBufferFactory; 27 | _bufferFactory = bufferFactory; 28 | _textEditorFactoryService = textEditorFactoryService; 29 | } 30 | 31 | [Import(typeof(ITextStructureNavigatorSelectorService))] 32 | internal ITextStructureNavigatorSelectorService NavigatorService { get; set; } 33 | 34 | public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) 35 | { 36 | if (textBuffer == null && textView == null) 37 | { 38 | return null; 39 | } 40 | return new SuggestedActionsSource(this, textView, textBuffer, _diffFactory, _diffBufferFactory, 41 | _bufferFactory, _textEditorFactoryService); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Serilog; 3 | 4 | namespace AvaloniaVS 5 | { 6 | internal static class TaskExtensions 7 | { 8 | public static void FireAndForget(this Task task) 9 | { 10 | _ = task.ContinueWith(t => 11 | { 12 | if (t.IsFaulted) 13 | { 14 | Log.Error(t.Exception, "Exception caught by FireAndForget"); 15 | } 16 | }, TaskScheduler.Default); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/TextViewExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.VisualStudio.Text; 3 | using Microsoft.VisualStudio.Text.Editor; 4 | 5 | namespace AvaloniaVS; 6 | 7 | internal static class TextViewExtensions 8 | { 9 | public static NormalizedSnapshotSpanCollection GetSpanInView(this ITextView textView, SnapshotSpan span) 10 | => textView.BufferGraph.MapUpToSnapshot(span, SpanTrackingMode.EdgeInclusive, textView.TextSnapshot); 11 | 12 | public static void SetSelection( 13 | this ITextView textView, VirtualSnapshotPoint anchorPoint, VirtualSnapshotPoint activePoint) 14 | { 15 | var isReversed = activePoint < anchorPoint; 16 | var start = isReversed ? activePoint : anchorPoint; 17 | var end = isReversed ? anchorPoint : activePoint; 18 | SetSelection(textView, new SnapshotSpan(start.Position, end.Position), isReversed); 19 | } 20 | 21 | public static void SetSelection( 22 | this ITextView textView, SnapshotSpan span, bool isReversed = false) 23 | { 24 | var spanInView = textView.GetSpanInView(span).Single(); 25 | textView.Selection.Select(spanInView, isReversed); 26 | textView.Caret.MoveTo(isReversed ? spanInView.Start : spanInView.End); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Utils/FrameworkInfoUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AvaloniaVS.Utils 4 | { 5 | internal static class FrameworkInfoUtils 6 | { 7 | public static bool IsNetFramework(string targetFrameworkIdentifier) => 8 | string.Equals(targetFrameworkIdentifier, ".NETFramework", StringComparison.Ordinal); 9 | 10 | public static bool IsNetCoreApp(string targetFrameworkIdentifier) => 11 | string.Equals(targetFrameworkIdentifier, ".NETCoreApp", StringComparison.Ordinal); 12 | 13 | public static bool IsNetStandard(string targetFrameworkIdentifier) => 14 | string.Equals(targetFrameworkIdentifier, ".NETStandard", StringComparison.Ordinal); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AvaloniaVS.Shared/Views/AvaloniaPreviewer.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | 36 | 37 | 42 | 43 | 46 | 47 | 48 | 52 | 59 | 60 | 61 | 65 | 66 | 67 | 72 |