diff --git a/New_Extensibility_Model/Samples/SettingsSample/.vsextension/string-resources.json b/New_Extensibility_Model/Samples/SettingsSample/.vsextension/string-resources.json new file mode 100644 index 00000000..46593c69 --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/.vsextension/string-resources.json @@ -0,0 +1,14 @@ +{ + "SettingsSample.MyToolWindowCommand.DisplayName": "Sample Text Tool Window", + "SettingsSample.Settings.Category.DisplayName": "Settings Sample", + "SettingsSample.Settings.Category.Description": "Settings for the LoremIpsum sample generator.", + "SettingsSample.Settings.AutoUpdate.DisplayName": "Auto Update", + "SettingsSample.Settings.AutoUpdate.Description": "Whether to update the sample text when a setting changes.", + "SettingsSample.Settings.TextLength.DisplayName": "Text Length", + "SettingsSample.Settings.TextLength.Description": "Number of characters to include in the generated text.", + "SettingsSample.Settings.QuoteStyle.DisplayName": "Quote Style", + "SettingsSample.Settings.QuoteStyle.Description": "Style of quotes to enclose the generated text.", + "SettingsSample.Settings.QuoteStyle.None": "None", + "SettingsSample.Settings.QuoteStyle.Single": "Single", + "SettingsSample.Settings.QuoteStyle.Double": "Double" +} \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/SettingsSample/MyToolWindow.cs b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindow.cs new file mode 100644 index 00000000..725958ff --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindow.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace SettingsSample; + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.ToolWindows; +using Microsoft.VisualStudio.RpcContracts.RemoteUI; + +/// <summary> +/// A sample tool window. +/// </summary> +[VisualStudioContribution] +public class MyToolWindow : ToolWindow +{ + private MyToolWindowData? dataContext; + + /// <summary> + /// Initializes a new instance of the <see cref="MyToolWindow" /> class. + /// </summary> + public MyToolWindow() + { + this.Title = "Settings Sample Tool Window"; + } + + /// <inheritdoc /> + public override ToolWindowConfiguration ToolWindowConfiguration => new() + { + Placement = ToolWindowPlacement.DocumentWell, + AllowAutoCreation = false, + }; + + /// <inheritdoc /> + public override Task InitializeAsync(CancellationToken cancellationToken) + { + this.dataContext = new MyToolWindowData(this.Extensibility); + return this.dataContext.InitializeAsync(cancellationToken); + } + + /// <inheritdoc /> + public override Task<IRemoteUserControl> GetContentAsync(CancellationToken cancellationToken) + { + return Task.FromResult<IRemoteUserControl>(new MyToolWindowControl(this.dataContext)); + } +} diff --git a/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowCommand.cs b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowCommand.cs new file mode 100644 index 00000000..2ca4cf54 --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowCommand.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace SettingsSample; + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.Commands; + +/// <summary> +/// A sample command for showing a tool window. +/// </summary> +[VisualStudioContribution] +public class MyToolWindowCommand : Command +{ + /// <inheritdoc /> + public override CommandConfiguration CommandConfiguration => new("%SettingsSample.MyToolWindowCommand.DisplayName%") + { + Placements = [CommandPlacement.KnownPlacements.ToolsMenu], + Icon = new(ImageMoniker.KnownValues.ToolWindow, IconSettings.IconAndText), + }; + + /// <inheritdoc /> + public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken) + { + await this.Extensibility.Shell().ShowToolWindowAsync<MyToolWindow>(activate: true, cancellationToken); + } +} diff --git a/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowControl.cs b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowControl.cs new file mode 100644 index 00000000..1e95b6bc --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowControl.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace SettingsSample; + +using System.Threading; +using Microsoft.VisualStudio.Extensibility.UI; + +/// <summary> +/// A sample remote user control to use as tool window UI content. +/// </summary> +internal class MyToolWindowControl : RemoteUserControl +{ + /// <summary> + /// Initializes a new instance of the <see cref="MyToolWindowControl" /> class. + /// </summary> + /// <param name="dataContext"> + /// Data context of the remote control which can be referenced from xaml through data binding. + /// </param> + public MyToolWindowControl(object? dataContext) + : base(dataContext) + { + } +} diff --git a/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowControl.xaml b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowControl.xaml new file mode 100644 index 00000000..fc320a67 --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowControl.xaml @@ -0,0 +1,31 @@ +<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:vs="http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml" + xmlns:styles="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0" + xmlns:colors="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"> + <Border Background="{DynamicResource {x:Static colors:EnvironmentColors.ToolWindowBackgroundBrushKey}}" + TextElement.Foreground="{DynamicResource {x:Static colors:EnvironmentColors.ToolWindowTextBrushKey}}" + Padding="5"> + <StackPanel Orientation="Vertical" + HorizontalAlignment="Center" + VerticalAlignment="Center"> + <TextBlock Margin="0,0,5,0" + Text="Sample Text:" + VerticalAlignment="Center" /> + <TextBlock x:Name="SampleTextBlock" + HorizontalAlignment="Center" + VerticalAlignment="Center" + Text="{Binding SampleText, Mode=OneWay}" + Style="{StaticResource {x:Static styles:VsResourceKeys.TextBlockEnvironment155PercentFontSizeStyleKey}}"> + </TextBlock> + <Button x:Name="UpdateButton" + Content="Update Sample Text From Settings" + IsEnabled="{Binding ManualUpdate}" + Margin="0,10,0,0" + MinHeight="25" + MinWidth="60" + Style="{StaticResource {x:Static styles:VsResourceKeys.ButtonStyleKey}}" + Command="{Binding UpdateCommand}" /> + </StackPanel> + </Border> +</DataTemplate> diff --git a/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowData.cs b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowData.cs new file mode 100644 index 00000000..9fa775bd --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/MyToolWindowData.cs @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace SettingsSample; + +using System; +using System.Runtime.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.Settings; +using Microsoft.VisualStudio.Extensibility.UI; + +/// <summary> +/// A sample data context object to use with tool window UI content. +/// </summary> +[DataContract] +internal class MyToolWindowData : NotifyPropertyChangedObject +{ + internal static readonly string LoremIpsumText = """ + Lorem ipsum dolor sit amet consectetur adipiscing elit dignissim lacinia donec, + praesent diam ac mattis morbi nisi dictum dapibus. Convallis natoque interdum + curabitur malesuada tellus aliquam dignissim hendrerit tempor, primis sem nulla + neque cubilia rutrum mollis nisl, eleifend imperdiet lacinia fames gravida sed mus + magnis. Pretium aliquet consequat curabitur eros eleifend praesent, nostra malesuada + hendrerit ornare volutpat, cubilia aptent at mollis convallis. + """; + + private readonly VisualStudioExtensibility extensibility; + private string sampleText = LoremIpsumText; + private bool manualUpdate = false; + + /// <summary> + /// Initializes a new instance of the <see cref="MyToolWindowData" /> class. + /// </summary> + /// <param name="extensibility"> + /// Extensibility object instance. + /// </param> + public MyToolWindowData(VisualStudioExtensibility extensibility) + { + this.extensibility = Requires.NotNull(extensibility, nameof(extensibility)); + + this.UpdateCommand = new AsyncCommand(this.UpdateAsync); + } + + /// <summary> + /// Gets the async command used to show a message prompt. + /// </summary> + [DataMember] + public IAsyncCommand UpdateCommand + { + get; + } + + /// <summary> + /// Gets or sets a value indicating whether the user must + /// click the "Update" button to refresh the sample text. + /// </summary> + [DataMember] + public bool ManualUpdate + { + get => this.manualUpdate; + set => this.SetProperty(ref this.manualUpdate, value); + } + + /// <summary> + /// Gets or sets the message to display in the message prompt. + /// </summary> + [DataMember] + public string SampleText + { + get => this.sampleText; + set => this.SetProperty(ref this.sampleText, value); + } + + /// <summary> + /// Initializes the current instance of <see cref="MyToolWindowData"/>. + /// </summary> + /// <param name="cancellationToken">Cancellation token to monitor.</param> + /// <returns>Task indicating completion of initialization.</returns> + public Task InitializeAsync(CancellationToken cancellationToken) + { + return this.InitializeSettingsAsync(cancellationToken); + } + +#pragma warning disable VSEXTPREVIEW_SETTINGS // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + + private async Task InitializeSettingsAsync(CancellationToken cancellationToken) + { + await this.extensibility.Settings().SubscribeAsync( + [SettingDefinitions.SettingsSampleCategory], + cancellationToken, + values => + { + if (values.TryGetValue(SettingDefinitions.AutoUpdateSetting.FullId, out ISettingValue? autoUpdateValue)) + { + this.ManualUpdate = !autoUpdateValue.Value<bool>(); + } + + if (!this.ManualUpdate) + { + this.UpdateSampleTextFromSettings(values); + } + }); + } + + private async Task UpdateAsync(object? commandParameter, CancellationToken cancellationToken) + { + SettingValues values = await this.extensibility.Settings().ReadEffectiveValuesAsync( + [SettingDefinitions.SettingsSampleCategory], + cancellationToken); + + this.UpdateSampleTextFromSettings(values); + } + + private void UpdateSampleTextFromSettings(SettingValues values) + { + string text = LoremIpsumText; + + if (values.TryGetValue(SettingDefinitions.TextLengthSetting.FullId, out ISettingValue? textLengthValue)) + { + int length = textLengthValue.Value<int>(); + text = LoremIpsumText[..Math.Min(length, LoremIpsumText.Length)]; + } + + if (values.TryGetValue(SettingDefinitions.QuoteStyleSetting.FullId, out ISettingValue? quoteStyleValue)) + { + this.SampleText = quoteStyleValue.Value<string>() switch + { + "single" => $"'{text}'", + "double" => $"\"{text}\"", + _ => text, + }; + } + } + +#pragma warning restore VSEXTPREVIEW_SETTINGS // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. +} diff --git a/New_Extensibility_Model/Samples/SettingsSample/README.md b/New_Extensibility_Model/Samples/SettingsSample/README.md new file mode 100644 index 00000000..47eb6703 --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/README.md @@ -0,0 +1,83 @@ +--- +title: Extension Settings Sample reference +description: A reference sample for extensions settings +date: 2024-08-20 +--- + +# Walkthrough: Extension Settings Sample + +This is a simple extension that shows how settings can be added to Visual Studio and read to configure the behavior of a tool window. + +## Tool window definition + +See the [ToolWindowSample](../ToolWindowSample/README.md) for more information on defining the tool window. + +## Setting definitions + +The extension contains a [code file](./SettingDefinitions.cs) that defines three settings and a parent category to contain them. Each setting and the category starts with the `VisualStudioContribution` class attribute which makes it available to Visual Studio: + +```csharp +[VisualStudioContribution] +internal static SettingCategory SettingsSampleCategory { get; } = new("settingsSample", "%SettingsSample.Settings.Category.DisplayName%") +{ + Description = "%SettingsSample.Settings.Category.Description%", +}; +``` + +```csharp +[VisualStudioContribution] +internal static Setting.Boolean AutoUpdateSetting { get; } = new("autoUpdate", "%SettingsSample.Settings.AutoUpdate.DisplayName%", SettingsSampleCategory, defaultValue: true) +{ + Description = "%SettingsSample.Settings.AutoUpdate.Description%", +}; + +The `SettingCategory` and `Setting.Boolean` properties define information about the settings that is available to Visual Studio even before the extension is loaded. + +In `MyToolWindowData`, a subscription is created to read and monitor value changes for all the settings in the `SettingSampleCategory`: + +```csharp +public MyToolWindowData(VisualStudioExtensibility extensibility) +{ + ... + this.InitializeSettingsAsync(extensibility).Forget(); +} +private async Task InitializeSettingsAsync(VisualStudioExtensibility extensibility) +{ + await extensibility.Settings().SubscribeAsync( + [SettingDefinitions.SettingsSampleCategory], + CancellationToken.None, + values => {...}); +} +``` + +## Usage + +Once deployed, the "Sample Text Tool Window" command can be used to show the "Settings Sample Tool Window" in the document well. + +### Changing the TextLengthSetting + +Setting values are stored in json files in well-known locations. After deploying the extension: + +* Open the "Sample Text Tool Window": Tools -> Sample Text Tool Window +* Open the extension settings json file: Extensions -> Extension Settings (experimental) -> User Scope (current install) + +The `extensibility.settings.json` file will open in an editor. To change the textLength setting, add a value to the file to override +the default: + +```json +/* Visual Studio Settings File */ +{ + "settingsSample.textLength": 150 +} +``` + +The string key is the `FullId` property of the `TextLengthSetting` property defined in [SettingDefinitions](./SettingDefinitions.cs). It is formed by the id of the category and the id of the setting. + +Each time you change the value and save the file, the sample text in the tool window will update. + +## Current Limitations + +The settings API is currently experimental, and has several limitations: + +* An extension can only read or write settings from itself or other extensions. Core Visual Studio settings are not available. +* There is no UI for extension settings. They can only be changed by using the json files available in the Extensions -> Extension Settings (experimental) menu. \ No newline at end of file diff --git a/New_Extensibility_Model/Samples/SettingsSample/SettingDefinitions.cs b/New_Extensibility_Model/Samples/SettingsSample/SettingDefinitions.cs new file mode 100644 index 00000000..7fb3de4e --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/SettingDefinitions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace SettingsSample; + +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.Settings; + +#pragma warning disable VSEXTPREVIEW_SETTINGS // The settings API is currently in preview and marked as experimental + +internal static class SettingDefinitions +{ + [VisualStudioContribution] + internal static SettingCategory SettingsSampleCategory { get; } = new("settingsSample", "%SettingsSample.Settings.Category.DisplayName%") + { + Description = "%SettingsSample.Settings.Category.Description%", + }; + + [VisualStudioContribution] + internal static Setting.Boolean AutoUpdateSetting { get; } = new("autoUpdate", "%SettingsSample.Settings.AutoUpdate.DisplayName%", SettingsSampleCategory, defaultValue: true) + { + Description = "%SettingsSample.Settings.AutoUpdate.Description%", + }; + + [VisualStudioContribution] + internal static Setting.Integer TextLengthSetting { get; } = new("textLength", "%SettingsSample.Settings.TextLength.DisplayName%", SettingsSampleCategory, defaultValue: 10) + { + Description = "%SettingsSample.Settings.TextLength.Description%", + Minimum = 1, + Maximum = MyToolWindowData.LoremIpsumText.Length, + }; + + [VisualStudioContribution] + internal static Setting.Enum QuoteStyleSetting { get; } = new( + "quoteStyle", + "%SettingsSample.Settings.QuoteStyle.DisplayName%", + SettingsSampleCategory, + [new EnumSettingEntry("none", "%SettingsSample.Settings.QuoteStyle.None%"), new EnumSettingEntry("single", "%SettingsSample.Settings.QuoteStyle.Single%"), new EnumSettingEntry("double", "%SettingsSample.Settings.QuoteStyle.Double%")], + defaultValue: "double") + { + Description = "%SettingsSample.Settings.QuoteStyle.Description%", + }; +} diff --git a/New_Extensibility_Model/Samples/SettingsSample/SettingsSample.csproj b/New_Extensibility_Model/Samples/SettingsSample/SettingsSample.csproj new file mode 100644 index 00000000..e8329e55 --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/SettingsSample.csproj @@ -0,0 +1,18 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>net8.0-windows8.0</TargetFramework> + <Nullable>enable</Nullable> + <LangVersion>12</LangVersion> + <NeutralLanguage>en-US</NeutralLanguage> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.11.40261" /> + <PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.11.40261" /> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Include="MyToolWindowControl.xaml" /> + <Page Remove="MyToolWindowControl.xaml" /> + </ItemGroup> +</Project> diff --git a/New_Extensibility_Model/Samples/SettingsSample/SettingsSampleExtension.cs b/New_Extensibility_Model/Samples/SettingsSample/SettingsSampleExtension.cs new file mode 100644 index 00000000..82c04694 --- /dev/null +++ b/New_Extensibility_Model/Samples/SettingsSample/SettingsSampleExtension.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace SettingsSample; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.VisualStudio.Extensibility; + +/// <summary> +/// Extension entry point for the SettingsSample. +/// </summary> +[VisualStudioContribution] +public class SettingsSampleExtension : Extension +{ + /// <inheritdoc/> + public override ExtensionConfiguration ExtensionConfiguration => new() + { + Metadata = new( + id: "SettingsSampleExtension.4ca466cb-dc8d-4216-8323-b5c45f8e0da5", + version: this.ExtensionAssemblyVersion, + publisherName: "Microsoft", + displayName: "Settings Sample Extension", + description: "Sample extension demonstrating settings"), + }; + + /// <inheritdoc/> + protected override void InitializeServices(IServiceCollection serviceCollection) + { + base.InitializeServices(serviceCollection); + } +}