Skip to content

Commit

Permalink
Add sample for Settings API (#412)
Browse files Browse the repository at this point in the history
* Add sample for Settings API

---------

Co-authored-by: Charles Willis <[email protected]>
  • Loading branch information
trippwill and Charles Willis authored Sep 9, 2024
1 parent 2331763 commit 8b4ca15
Show file tree
Hide file tree
Showing 10 changed files with 459 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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"
}
47 changes: 47 additions & 0 deletions New_Extensibility_Model/Samples/SettingsSample/MyToolWindow.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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)
{
}
}
Original file line number Diff line number Diff line change
@@ -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>
139 changes: 139 additions & 0 deletions New_Extensibility_Model/Samples/SettingsSample/MyToolWindowData.cs
Original file line number Diff line number Diff line change
@@ -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.
}
83 changes: 83 additions & 0 deletions New_Extensibility_Model/Samples/SettingsSample/README.md
Original file line number Diff line number Diff line change
@@ -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.
Loading

0 comments on commit 8b4ca15

Please sign in to comment.