提交 7894cc81 编写于 作者: J Jason Malinowski

Reimplement our option serializers to consume the metadata on options

Now that our existing options have been tagged with the appropriate
metadata, we can use that metadata to drive serialization. This
updates IOptionSerializers to make them no longer have any language-
specific metadata, and are simply driven from the metadata included
on the option.

This also renames IOptionSerializer to IOptionPersister, which is
a better name.
上级 f8cbdc6f
......@@ -8,6 +8,7 @@ internal static class TodoCommentOptions
{
public const string OptionName = "TaskList/Tokens";
// This is serialized by the CommentTaskTokenSerializer in the Visual Studio layer, since it's backed by another component
[ExportOption]
public static readonly Option<string> TokenList = new Option<string>(OptionName, "Token List", defaultValue: "HACK:1|TODO:1|UNDONE:1|UnresolvedMergeConflict:0");
}
......
......@@ -29,7 +29,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
// give out new option service per workspace
return new OptionServiceFactory.OptionService(
new GlobalOptionService(_providers, SpecializedCollections.EmptyEnumerable<Lazy<IOptionSerializer, OptionSerializerMetadata>>()),
new GlobalOptionService(_providers, SpecializedCollections.EmptyEnumerable<Lazy<IOptionPersister>>()),
workspaceServices);
}
}
......
......@@ -167,8 +167,6 @@
<DependentUpon>AdvancedOptionPageControl.xaml</DependentUpon>
</Compile>
<Compile Include="Options\AutomationObject.cs" />
<Compile Include="Options\CSharpLanguageSettingsSerializer.cs" />
<Compile Include="Options\CSharpSettingsManagerOptionSerializer.cs" />
<Compile Include="Options\Formatting\IndentationViewModel.cs" />
<Compile Include="Options\Formatting\FormattingIndentationOptionPage.cs" />
<Compile Include="Options\Formatting\NewLinesViewModel.cs" />
......@@ -267,4 +265,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Editor.Options;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.VisualStudio.LanguageServices.Implementation.Options;
using Microsoft.VisualStudio.Shell;
namespace Microsoft.VisualStudio.LanguageServices.CSharp.Options
{
[ExportLanguageSpecificOptionSerializer(
LanguageNames.CSharp,
FormattingOptions.TabFeatureName,
BraceCompletionOptions.FeatureName,
CompletionOptions.FeatureName,
SignatureHelpOptions.FeatureName,
NavigationBarOptions.FeatureName), Shared]
internal class CSharpLanguageSettingsSerializer : AbstractLanguageSettingsSerializer
{
[ImportingConstructor]
public CSharpLanguageSettingsSerializer(SVsServiceProvider serviceProvider) :
base(Guids.CSharpLanguageServiceId, LanguageNames.CSharp, serviceProvider)
{
}
}
}
......@@ -2,11 +2,10 @@
using System;
using System.Collections.ObjectModel;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options.Providers;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Snippets;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Text;
......@@ -64,13 +63,10 @@ public static void CreateServicesOnUIThread(IComponentModel componentModel, stri
}
}
var serializers = componentModel.DefaultExportProvider.GetExports<IOptionSerializer, OptionSerializerMetadata>();
var serializers = componentModel.DefaultExportProvider.GetExports<IOptionPersister>();
foreach (var serializer in serializers)
{
if (serializer.Metadata.Language == null || serializer.Metadata.Language == languageName)
{
var unused = serializer.Value;
}
var unused = serializer.Value;
}
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Editor.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Options.Providers;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.TextManager.Interop;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options
{
internal abstract class AbstractLanguageSettingsSerializer : ForegroundThreadAffinitizedObject, IVsTextManagerEvents4, IOptionSerializer
{
private readonly IServiceProvider _serviceProvider;
private readonly string _languageName;
private readonly IVsTextManager4 _textManager;
private readonly IComEventSink _textManagerEvents2Sink;
private LANGPREFERENCES3 _languageSetting;
// We make sure this code is from the UI by asking for the optionservice in the initialize() in AbstractPackage`2
public AbstractLanguageSettingsSerializer(Guid languageServiceguid, string languageName, IServiceProvider serviceProvider)
: base(assertIsForeground: true)
{
_serviceProvider = serviceProvider;
_languageName = languageName;
_textManager = (IVsTextManager4)serviceProvider.GetService(typeof(SVsTextManager));
var langPrefs = new LANGPREFERENCES3[1];
langPrefs[0].guidLang = languageServiceguid;
Marshal.ThrowExceptionForHR(_textManager.GetUserPreferences4(pViewPrefs: null, pLangPrefs: langPrefs, pColorPrefs: null));
_languageSetting = langPrefs[0];
_textManagerEvents2Sink = ComEventSink.Advise<IVsTextManagerEvents4>(_textManager, this);
}
int IVsTextManagerEvents4.OnUserPreferencesChanged4(
VIEWPREFERENCES3[] viewPrefs,
LANGPREFERENCES3[] langPrefs,
FONTCOLORPREFERENCES2[] colorPrefs)
{
this.AssertIsForeground();
if (langPrefs != null && langPrefs[0].guidLang == _languageSetting.guidLang)
{
_languageSetting = langPrefs[0];
// We need to go and refresh each option we know about, since the option service caches them.
// TODO: should a serializer have an event to say the backing store changed?
RefreshOption(FormattingOptions.UseTabs);
RefreshOption(FormattingOptions.TabSize);
RefreshOption(FormattingOptions.SmartIndent);
RefreshOption(FormattingOptions.IndentationSize);
RefreshOption(CompletionOptions.HideAdvancedMembers);
RefreshOption(CompletionOptions.TriggerOnTyping);
RefreshOption(SignatureHelpOptions.ShowSignatureHelp);
RefreshOption(NavigationBarOptions.ShowNavigationBar);
RefreshOption(BraceCompletionOptions.EnableBraceCompletion);
}
return VSConstants.S_OK;
}
public virtual bool TryFetch(OptionKey optionKey, out object value)
{
if (optionKey.Language != _languageName)
{
value = null;
return false;
}
if (optionKey.Option == FormattingOptions.UseTabs)
{
value = _languageSetting.fInsertTabs != 0;
}
else if (optionKey.Option == FormattingOptions.TabSize)
{
value = Convert.ToInt32(_languageSetting.uTabSize);
}
else if (optionKey.Option == FormattingOptions.IndentationSize)
{
value = Convert.ToInt32(_languageSetting.uIndentSize);
}
else if (optionKey.Option == FormattingOptions.SmartIndent)
{
switch (_languageSetting.IndentStyle)
{
case vsIndentStyle.vsIndentStyleNone:
value = FormattingOptions.IndentStyle.None;
break;
case vsIndentStyle.vsIndentStyleDefault:
value = FormattingOptions.IndentStyle.Block;
break;
default:
value = FormattingOptions.IndentStyle.Smart;
break;
}
}
else if (optionKey.Option == CompletionOptions.HideAdvancedMembers)
{
value = _languageSetting.fHideAdvancedAutoListMembers != 0;
}
else if (optionKey.Option == CompletionOptions.TriggerOnTyping)
{
value = _languageSetting.fAutoListMembers != 0;
}
else if (optionKey.Option == SignatureHelpOptions.ShowSignatureHelp)
{
value = _languageSetting.fAutoListParams != 0;
}
else if (optionKey.Option == NavigationBarOptions.ShowNavigationBar)
{
value = _languageSetting.fDropdownBar != 0;
}
else if (optionKey.Option == BraceCompletionOptions.EnableBraceCompletion)
{
value = _languageSetting.fBraceCompletion != 0;
}
else
{
// Unrecognized option
value = null;
return false;
}
return true;
}
public bool TryPersist(OptionKey optionKey, object value)
{
if (optionKey.Language != _languageName)
{
return false;
}
var newSetting = _languageSetting;
if (optionKey.Option == FormattingOptions.UseTabs)
{
newSetting.fInsertTabs = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (optionKey.Option == FormattingOptions.TabSize)
{
newSetting.uTabSize = Convert.ToUInt32(value);
}
else if (optionKey.Option == FormattingOptions.IndentationSize)
{
newSetting.uIndentSize = Convert.ToUInt32(value);
}
else if (optionKey.Option == FormattingOptions.SmartIndent)
{
switch ((FormattingOptions.IndentStyle)value)
{
case FormattingOptions.IndentStyle.None:
newSetting.IndentStyle = vsIndentStyle.vsIndentStyleNone;
break;
case FormattingOptions.IndentStyle.Block:
newSetting.IndentStyle = vsIndentStyle.vsIndentStyleDefault;
break;
default:
newSetting.IndentStyle = vsIndentStyle.vsIndentStyleSmart;
break;
}
}
else if (optionKey.Option == CompletionOptions.HideAdvancedMembers)
{
newSetting.fHideAdvancedAutoListMembers = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (optionKey.Option == CompletionOptions.TriggerOnTyping)
{
newSetting.fAutoListMembers = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (optionKey.Option == SignatureHelpOptions.ShowSignatureHelp)
{
newSetting.fAutoListParams = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (optionKey.Option == NavigationBarOptions.ShowNavigationBar)
{
newSetting.fDropdownBar = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (optionKey.Option == BraceCompletionOptions.EnableBraceCompletion)
{
newSetting.fBraceCompletion = Convert.ToUInt32((bool)value ? 1 : 0);
}
else
{
// Unrecognized option
return false;
}
if (!newSetting.Equals(_languageSetting))
{
_languageSetting = newSetting;
// Something actually changed, so let's call down.
SetUserPreferences();
}
// Even if we didn't call back, say we completed the persist
return true;
}
private void SetUserPreferences()
{
var langPrefs = new LANGPREFERENCES3[1];
langPrefs[0] = _languageSetting;
if (IsForeground())
{
Marshal.ThrowExceptionForHR(_textManager.SetUserPreferences4(pViewPrefs: null, pLangPrefs: langPrefs, pColorPrefs: null));
}
else
{
Task.Factory.StartNew(this.SetUserPreferences, CancellationToken.None, TaskCreationOptions.None, ForegroundThreadAffinitizedObject.CurrentForegroundThreadData.TaskScheduler);
}
}
private void RefreshOption<T>(PerLanguageOption<T> option)
{
object value;
OptionKey optionKey = new OptionKey(option, _languageName);
if (TryFetch(optionKey, out value))
{
var componentModel = (IComponentModel)_serviceProvider.GetService(typeof(SComponentModel));
var visualStudioWorkspace = componentModel.GetService<VisualStudioWorkspace>();
visualStudioWorkspace.Options = visualStudioWorkspace.Options.WithChangedOption(optionKey, value);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Composition;
using Microsoft.CodeAnalysis.Options.Providers;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options
{
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
internal class ExportLanguageSpecificOptionSerializerAttribute : ExportAttribute
{
public ExportLanguageSpecificOptionSerializerAttribute(string language, params string[] features) : base(typeof(IOptionSerializer))
{
this.Language = language;
this.Features = features;
}
public string Language { get; set; }
public IEnumerable<string> Features { get; set; }
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Composition;
using Microsoft.CodeAnalysis.Options.Providers;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options
{
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
internal class ExportOptionSerializerAttribute : ExportAttribute
{
public ExportOptionSerializerAttribute(params string[] features) : base(typeof(IOptionSerializer))
{
this.Features = features;
}
public IEnumerable<string> Features { get; set; }
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Editor.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Options.Providers;
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.TextManager.Interop;
using Roslyn.Utilities;
using SVsServiceProvider = Microsoft.VisualStudio.Shell.SVsServiceProvider;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options
{
/// <summary>
/// An <see cref="IOptionPersister"/> that syncs core language settings against the settings that exist for all languages
/// in Visual Studio and whose backing store is provided by the shell. This includes things like default tab size, tabs vs. spaces, etc.
/// </summary>
[Export(typeof(IOptionPersister))]
internal sealed class LanguageSettingsPersister : ForegroundThreadAffinitizedObject, IVsTextManagerEvents4, IOptionPersister
{
private readonly IVsTextManager4 _textManager;
private readonly IGlobalOptionService _optionService;
private readonly IComEventSink _textManagerEvents2Sink;
private readonly IBidirectionalMap<string, Guid> _languageMap;
/// <remarks>
/// We make sure this code is from the UI by asking for all serializers on the UI thread in <see cref="HACK_AbstractCreateServicesOnUiThread"/>.
/// </remarks>
[ImportingConstructor]
public LanguageSettingsPersister(
[Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider,
IGlobalOptionService optionService)
: base(assertIsForeground: true)
{
_textManager = (IVsTextManager4)serviceProvider.GetService(typeof(SVsTextManager));
_optionService = optionService;
// TODO: make this configurable
_languageMap = BidirectionalMap<string, Guid>.Empty.Add(LanguageNames.CSharp, Guids.CSharpLanguageServiceId)
.Add(LanguageNames.VisualBasic, Guids.VisualBasicLanguageServiceId);
foreach (var languageGuid in _languageMap.Values)
{
var languagePreferences = new LANGPREFERENCES3[1];
languagePreferences[0].guidLang = languageGuid;
Marshal.ThrowExceptionForHR(_textManager.GetUserPreferences4(pViewPrefs: null, pLangPrefs: languagePreferences, pColorPrefs: null));
RefreshLanguageSettings(languagePreferences);
}
_textManagerEvents2Sink = ComEventSink.Advise<IVsTextManagerEvents4>(_textManager, this);
}
private readonly IOption[] _supportedOptions = new IOption[]
{
FormattingOptions.UseTabs,
FormattingOptions.TabSize,
FormattingOptions.SmartIndent,
FormattingOptions.IndentationSize,
CompletionOptions.HideAdvancedMembers,
CompletionOptions.TriggerOnTyping,
SignatureHelpOptions.ShowSignatureHelp,
NavigationBarOptions.ShowNavigationBar,
BraceCompletionOptions.EnableBraceCompletion,
};
int IVsTextManagerEvents4.OnUserPreferencesChanged4(
VIEWPREFERENCES3[] viewPrefs,
LANGPREFERENCES3[] langPrefs,
FONTCOLORPREFERENCES2[] colorPrefs)
{
if (langPrefs != null)
{
RefreshLanguageSettings(langPrefs);
}
return VSConstants.S_OK;
}
private void RefreshLanguageSettings(LANGPREFERENCES3[] langPrefs)
{
this.AssertIsForeground();
string languageName;
if (_languageMap.TryGetKey(langPrefs[0].guidLang, out languageName))
{
foreach (var option in _supportedOptions)
{
var keyWithLanguage = new OptionKey(option, languageName);
object newValue = GetValueForOption(option, langPrefs[0]);
_optionService.RefreshOption(keyWithLanguage, newValue);
}
}
}
private static object GetValueForOption(IOption option, LANGPREFERENCES3 languagePreference)
{
if (option == FormattingOptions.UseTabs)
{
return languagePreference.fInsertTabs != 0;
}
else if (option == FormattingOptions.TabSize)
{
return Convert.ToInt32(languagePreference.uTabSize);
}
else if (option == FormattingOptions.IndentationSize)
{
return Convert.ToInt32(languagePreference.uIndentSize);
}
else if (option == FormattingOptions.SmartIndent)
{
switch (languagePreference.IndentStyle)
{
case vsIndentStyle.vsIndentStyleNone:
return FormattingOptions.IndentStyle.None;
case vsIndentStyle.vsIndentStyleDefault:
return FormattingOptions.IndentStyle.Block;
default:
return FormattingOptions.IndentStyle.Smart;
}
}
else if (option == CompletionOptions.HideAdvancedMembers)
{
return languagePreference.fHideAdvancedAutoListMembers != 0;
}
else if (option == CompletionOptions.TriggerOnTyping)
{
return languagePreference.fAutoListMembers != 0;
}
else if (option == SignatureHelpOptions.ShowSignatureHelp)
{
return languagePreference.fAutoListParams != 0;
}
else if (option == NavigationBarOptions.ShowNavigationBar)
{
return languagePreference.fDropdownBar != 0;
}
else if (option == BraceCompletionOptions.EnableBraceCompletion)
{
return languagePreference.fBraceCompletion != 0;
}
else
{
throw new ArgumentException("Unexpected option.", nameof(option));
}
}
private static void SetValueForOption(IOption option, ref LANGPREFERENCES3 languagePreference, object value)
{
if (option == FormattingOptions.UseTabs)
{
languagePreference.fInsertTabs = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (option == FormattingOptions.TabSize)
{
languagePreference.uTabSize = Convert.ToUInt32(value);
}
else if (option == FormattingOptions.IndentationSize)
{
languagePreference.uIndentSize = Convert.ToUInt32(value);
}
else if (option == FormattingOptions.SmartIndent)
{
switch ((FormattingOptions.IndentStyle)value)
{
case FormattingOptions.IndentStyle.None:
languagePreference.IndentStyle = vsIndentStyle.vsIndentStyleNone;
break;
case FormattingOptions.IndentStyle.Block:
languagePreference.IndentStyle = vsIndentStyle.vsIndentStyleDefault;
break;
default:
languagePreference.IndentStyle = vsIndentStyle.vsIndentStyleSmart;
break;
}
}
else if (option == CompletionOptions.HideAdvancedMembers)
{
languagePreference.fHideAdvancedAutoListMembers = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (option == CompletionOptions.TriggerOnTyping)
{
languagePreference.fAutoListMembers = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (option == SignatureHelpOptions.ShowSignatureHelp)
{
languagePreference.fAutoListParams = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (option == NavigationBarOptions.ShowNavigationBar)
{
languagePreference.fDropdownBar = Convert.ToUInt32((bool)value ? 1 : 0);
}
else if (option == BraceCompletionOptions.EnableBraceCompletion)
{
languagePreference.fBraceCompletion = Convert.ToUInt32((bool)value ? 1 : 0);
}
else
{
throw new ArgumentException("Unexpected option.", nameof(option));
}
}
public bool TryFetch(OptionKey optionKey, out object value)
{
// This particular serializer is a bit strange, since we have to initially read things out on the UI thread.
// Therefore, we refresh the values in the constructor, meaning that this should never get called for our values.
Contract.ThrowIfTrue(_supportedOptions.Contains(optionKey.Option));
value = null;
return false;
}
public bool TryPersist(OptionKey optionKey, object value)
{
if (!_supportedOptions.Contains(optionKey.Option))
{
value = null;
return false;
}
Guid languageServiceGuid;
if (!_languageMap.TryGetValue(optionKey.Language, out languageServiceGuid))
{
value = null;
return false;
}
var languagePreferences = new LANGPREFERENCES3[1];
languagePreferences[0].guidLang = languageServiceGuid;
Marshal.ThrowExceptionForHR(_textManager.GetUserPreferences4(null, languagePreferences, null));
SetValueForOption(optionKey.Option, ref languagePreferences[0], value);
SetUserPreferencesMaybeAsync(languagePreferences);
// Even if we didn't call back, say we completed the persist
return true;
}
private void SetUserPreferencesMaybeAsync(LANGPREFERENCES3[] languagePreferences)
{
if (IsForeground())
{
Marshal.ThrowExceptionForHR(_textManager.SetUserPreferences4(pViewPrefs: null, pLangPrefs: languagePreferences, pColorPrefs: null));
}
else
{
Task.Factory.StartNew(() => this.SetUserPreferencesMaybeAsync(languagePreferences), CancellationToken.None, TaskCreationOptions.None, ForegroundThreadAffinitizedObject.CurrentForegroundThreadData.TaskScheduler);
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Options.Providers;
......@@ -10,7 +13,11 @@
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options
{
internal abstract class AbstractLocalUserRegistryOptionSerializer : ForegroundThreadAffinitizedObject, IOptionSerializer
/// <summary>
/// Serializes options marked with <see cref="LocalUserProfilePersistence"/> to the local hive-specific registry.
/// </summary>
[Export(typeof(IOptionPersister))]
internal sealed class LocalUserRegistryOptionPersister : ForegroundThreadAffinitizedObject, IOptionPersister
{
/// <summary>
/// An object to gate access to <see cref="_registryKey"/>.
......@@ -18,18 +25,38 @@ internal abstract class AbstractLocalUserRegistryOptionSerializer : ForegroundTh
private readonly object _gate = new object();
private readonly RegistryKey _registryKey;
protected abstract string GetCollectionPathForOption(OptionKey key);
public AbstractLocalUserRegistryOptionSerializer(IServiceProvider serviceProvider)
[ImportingConstructor]
public LocalUserRegistryOptionPersister([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
: base(assertIsForeground: true) // The VSRegistry.RegistryRoot call requires being on the UI thread or else it will marshal and risk deadlock
{
this._registryKey = VSRegistry.RegistryRoot(serviceProvider, __VsLocalRegistryType.RegType_UserSettings, writable: true);
}
bool IOptionSerializer.TryFetch(OptionKey optionKey, out object value)
private static bool TryGetKeyPathAndName(IOption option, out string path, out string key)
{
var collectionPath = GetCollectionPathForOption(optionKey);
if (collectionPath == null)
var serialization = option.Persistences.OfType<LocalUserProfilePersistence>().SingleOrDefault();
if (serialization == null)
{
path = null;
key = null;
return false;
}
else
{
// We'll just use the filesystem APIs to decompose this
path = Path.GetDirectoryName(serialization.KeyName);
key = Path.GetFileName(serialization.KeyName);
return true;
}
}
bool IOptionPersister.TryFetch(OptionKey optionKey, out object value)
{
string path;
string key;
if (!TryGetKeyPathAndName(optionKey.Option, out path, out key))
{
value = null;
return false;
......@@ -37,7 +64,7 @@ bool IOptionSerializer.TryFetch(OptionKey optionKey, out object value)
lock (_gate)
{
using (var subKey = this._registryKey.OpenSubKey(collectionPath))
using (var subKey = this._registryKey.OpenSubKey(path))
{
if (subKey == null)
{
......@@ -48,45 +75,48 @@ bool IOptionSerializer.TryFetch(OptionKey optionKey, out object value)
// Options that are of type bool have to be serialized as integers
if (optionKey.Option.Type == typeof(bool))
{
value = subKey.GetValue(optionKey.Option.Name, defaultValue: (bool)optionKey.Option.DefaultValue ? 1 : 0).Equals(1);
value = subKey.GetValue(key, defaultValue: (bool)optionKey.Option.DefaultValue ? 1 : 0).Equals(1);
return true;
}
else
{
// Otherwise we can just store normally
value = subKey.GetValue(optionKey.Option.Name, defaultValue: optionKey.Option.DefaultValue);
value = subKey.GetValue(key, defaultValue: optionKey.Option.DefaultValue);
return true;
}
}
}
}
bool IOptionSerializer.TryPersist(OptionKey optionKey, object value)
bool IOptionPersister.TryPersist(OptionKey optionKey, object value)
{
if (this._registryKey == null)
{
throw new InvalidOperationException();
}
var collectionPath = GetCollectionPathForOption(optionKey);
if (collectionPath == null)
string path;
string key;
if (!TryGetKeyPathAndName(optionKey.Option, out path, out key))
{
value = null;
return false;
}
lock (_gate)
{
using (var subKey = this._registryKey.CreateSubKey(collectionPath))
using (var subKey = this._registryKey.CreateSubKey(path))
{
// Options that are of type bool have to be serialized as integers
if (optionKey.Option.Type == typeof(bool))
{
subKey.SetValue(optionKey.Option.Name, (bool)value ? 1 : 0, RegistryValueKind.DWord);
subKey.SetValue(key, (bool)value ? 1 : 0, RegistryValueKind.DWord);
return true;
}
else
{
subKey.SetValue(optionKey.Option.Name, value);
subKey.SetValue(key, value);
return true;
}
}
......
......@@ -2,150 +2,108 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Options.Providers;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
using Microsoft.VisualStudio.Settings;
using Microsoft.VisualStudio.Shell;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options
{
internal abstract class AbstractSettingsManagerOptionSerializer : ForegroundThreadAffinitizedObject, IOptionSerializer
/// <summary>
/// Serializes settings marked with <see cref="RoamingProfilePersistence"/> to and from the user's roaming profile.
/// </summary>
[Export(typeof(IOptionPersister))]
internal sealed class RoamingVisualStudioProfileOptionPersister : ForegroundThreadAffinitizedObject, IOptionPersister
{
// NOTE: This service is not public or intended for use by teams/individuals outside of Microsoft. Any data stored is subject to deletion without warning.
[Guid("9B164E40-C3A2-4363-9BC5-EB4039DEF653")]
private class SVsSettingsPersistenceManager { };
protected readonly ISettingsManager Manager;
private readonly IOptionService _optionService;
private readonly ISettingsManager _settingManager;
private readonly IGlobalOptionService _globalOptionService;
public AbstractSettingsManagerOptionSerializer(VisualStudioWorkspaceImpl workspace)
/// <summary>
/// The list of options that have been been fetched from <see cref="_settingManager"/>, by key. We track this so
/// if a later change happens, we know to refresh that value. This is synchronized with monitor locks on
/// <see cref="_optionsToMonitorForChangesGate" />.
/// </summary>
private readonly Dictionary<string, List<OptionKey>> _optionsToMonitorForChanges = new Dictionary<string, List<OptionKey>>();
private readonly object _optionsToMonitorForChangesGate = new object();
/// <remarks>We make sure this code is from the UI by asking for all serializers on the UI thread in <see cref="HACK_AbstractCreateServicesOnUiThread"/>.</remarks>
[ImportingConstructor]
public RoamingVisualStudioProfileOptionPersister(IGlobalOptionService globalOptionService, [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider)
: base(assertIsForeground: true) // The GetService call requires being on the UI thread or else it will marshal and risk deadlock
{
Contract.ThrowIfNull(workspace);
_storageKeyToOptionMap = new Lazy<ImmutableDictionary<string, IOption>>(CreateStorageKeyToOptionMap, isThreadSafe: true);
Contract.ThrowIfNull(globalOptionService);
this.Manager = workspace.GetVsService<SVsSettingsPersistenceManager, ISettingsManager>();
_optionService = workspace.Services.GetService<IOptionService>();
this._settingManager = (ISettingsManager)serviceProvider.GetService(typeof(SVsSettingsPersistenceManager));
_globalOptionService = globalOptionService;
// While the settings persistence service should be available in all SKUs it is possible an ISO shell author has undefined the
// contributing package. In that case persistence of settings won't work (we don't bother with a backup solution for persistence
// as the scenario seems exceedingly unlikely), but we shouldn't crash the IDE.
if (this.Manager != null)
if (this._settingManager != null)
{
ISettingsSubset settingsSubset = this.Manager.GetSubset(SettingStorageRoot + "*");
ISettingsSubset settingsSubset = this._settingManager.GetSubset("*");
settingsSubset.SettingChangedAsync += OnSettingChangedAsync;
}
}
protected abstract string SettingStorageRoot { get; }
protected abstract bool SupportsOption(IOption option, string languageName);
protected virtual string GetStorageKeyForOption(IOption option)
{
return SettingStorageRoot + option.Name;
}
protected static IEnumerable<KeyValuePair<string, IOption>> GetOptionInfoFromTypeFields(IEnumerable<Type> types, BindingFlags flags, Func<FieldInfo, KeyValuePair<string, IOption>> transform)
private System.Threading.Tasks.Task OnSettingChangedAsync(object sender, PropertyChangedEventArgs args)
{
return GetOptionInfoFromTypeFields(types, flags, transform, filter: null);
}
private Task OnSettingChangedAsync(object sender, PropertyChangedEventArgs args)
{
IOption option;
if (this.StorageKeyToOptionMap.TryGetValue(args.PropertyName, out option))
lock (_optionsToMonitorForChangesGate)
{
this.SetChangedOption(_optionService, option, LanguageName);
}
return SpecializedTasks.Default<object>();
}
protected abstract string LanguageName { get; }
private void SetChangedOption(IOptionService optionService, IOption option, string languageName)
{
OptionKey key = new OptionKey(option, option.IsPerLanguage ? languageName : null);
object currentValue;
if (this.TryFetch(key, out currentValue))
{
OptionSet optionSet = optionService.GetOptions();
optionSet = optionSet.WithChangedOption(key, currentValue);
optionService.SetOptions(optionSet);
}
}
private Lazy<ImmutableDictionary<string, IOption>> _storageKeyToOptionMap;
protected abstract ImmutableDictionary<string, IOption> CreateStorageKeyToOptionMap();
protected ImmutableDictionary<string, IOption> StorageKeyToOptionMap
{
get
{
return _storageKeyToOptionMap.Value;
}
}
protected KeyValuePair<string, IOption> GetOptionInfo(FieldInfo fieldInfo)
{
var value = (IOption)fieldInfo.GetValue(null);
return new KeyValuePair<string, IOption>(GetStorageKeyForOption(value), value);
}
protected static IEnumerable<KeyValuePair<string, IOption>> GetOptionInfoFromTypeFields(IEnumerable<Type> types, BindingFlags flags, Func<FieldInfo, KeyValuePair<string, IOption>> transform, Predicate<FieldInfo> filter)
{
var values = new List<KeyValuePair<string, IOption>>();
foreach (Type type in types)
{
FieldInfo[] fields = type.GetFields(flags) ?? Array.Empty<FieldInfo>();
Func<FieldInfo, bool> localFilter = (fi) =>
List<OptionKey> optionsToRefresh;
if (_optionsToMonitorForChanges.TryGetValue(args.PropertyName, out optionsToRefresh))
{
foreach (var optionToRefresh in optionsToRefresh)
{
if (!(fi.GetValue(null) is IOption))
object optionValue;
if (TryFetch(optionToRefresh, out optionValue))
{
return false;
_globalOptionService.RefreshOption(optionToRefresh, optionValue);
}
return (filter != null) ? filter(fi) : true;
};
values.AddRange(fields.Where(localFilter).Select(transform));
}
}
}
return values;
return SpecializedTasks.EmptyTask;
}
public virtual bool TryFetch(OptionKey optionKey, out object value)
public bool TryFetch(OptionKey optionKey, out object value)
{
value = null;
if (this.Manager == null)
if (this._settingManager == null)
{
Debug.Fail("Manager field is unexpectedly null.");
value = null;
return false;
}
if (!SupportsOption(optionKey.Option, optionKey.Language))
// Do we roam this at all?
var roamingSerialization = optionKey.Option.Persistences.OfType<RoamingProfilePersistence>().SingleOrDefault();
if (roamingSerialization == null)
{
value = null;
return false;
}
var storageKey = GetStorageKeyForOption(optionKey.Option);
value = this.Manager.GetValueOrDefault(storageKey, optionKey.Option.DefaultValue);
var storageKey = roamingSerialization.GetKeyNameForLanguage(optionKey.Language);
RecordObservedValueToWatchForChanges(optionKey, storageKey);
value = this._settingManager.GetValueOrDefault(storageKey, optionKey.Option.DefaultValue);
// VS's ISettingsManager has some quirks around storing enums. Specifically,
// it *can* persist and retrieve enums, but only if you properly call
......@@ -156,29 +114,78 @@ public virtual bool TryFetch(OptionKey optionKey, out object value)
//
// Because of that, manually convert the integer to an enum here so we don't
// crash later trying to cast a boxed integer to an enum value.
if (value != null && optionKey.Option.Type.IsEnum)
if (optionKey.Option.Type.IsEnum)
{
if (value != null)
{
value = Enum.ToObject(optionKey.Option.Type, value);
}
}
else if (optionKey.Option.Type == typeof(CodeStyleOption<bool>))
{
value = Enum.ToObject(optionKey.Option.Type, value);
// We store these as strings, so deserialize
var serializedValue = value as string;
if (serializedValue != null)
{
value = CodeStyleOption<bool>.FromXElement(XElement.Parse(serializedValue));
}
else
{
value = optionKey.Option.DefaultValue;
}
}
return true;
}
public virtual bool TryPersist(OptionKey optionKey, object value)
private void RecordObservedValueToWatchForChanges(OptionKey optionKey, string storageKey)
{
if (this.Manager == null)
// We're about to fetch the value, so make sure that if it changes we'll know about it
lock (_optionsToMonitorForChangesGate)
{
var optionKeysToMonitor = _optionsToMonitorForChanges.GetOrAdd(storageKey, _ => new List<OptionKey>());
if (!optionKeysToMonitor.Contains(optionKey))
{
optionKeysToMonitor.Add(optionKey);
}
}
}
public bool TryPersist(OptionKey optionKey, object value)
{
if (this._settingManager == null)
{
Debug.Fail("Manager field is unexpectedly null.");
return false;
}
if (!SupportsOption(optionKey.Option, optionKey.Language))
// Do we roam this at all?
var roamingSerialization = optionKey.Option.Persistences.OfType<RoamingProfilePersistence>().SingleOrDefault();
if (roamingSerialization == null)
{
value = null;
return false;
}
var storageKey = GetStorageKeyForOption(optionKey.Option);
this.Manager.SetValueAsync(storageKey, value, isMachineLocal: false);
var storageKey = roamingSerialization.GetKeyNameForLanguage(optionKey.Language);
RecordObservedValueToWatchForChanges(optionKey, storageKey);
if (optionKey.Option.Type == typeof(CodeStyleOption<bool>))
{
// We store these as strings, so serialize
var valueToSerialize = value as CodeStyleOption<bool>;
if (value != null)
{
value = valueToSerialize.ToXElement().ToString();
}
}
this._settingManager.SetValueAsync(storageKey, value, isMachineLocal: false);
return true;
}
}
......
......@@ -2,19 +2,18 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Composition;
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Editor.Implementation.TodoComments;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Options.Providers;
using Microsoft.VisualStudio.LanguageServices.Implementation.Options;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList
{
[ExportOptionSerializer(TodoCommentOptions.OptionName), Shared]
internal class CommentTaskTokenSerializer : IOptionSerializer
[Export(typeof(IOptionPersister))]
internal class CommentTaskTokenSerializer : IOptionPersister
{
private readonly ITaskList _taskList;
private readonly IOptionService _optionService;
......
......@@ -83,6 +83,8 @@
<Compile Include="Implementation\Library\VsNavInfo\NavInfoNode.cs" />
<Compile Include="Implementation\Log\VisualStudioErrorLogger.cs" />
<Compile Include="Implementation\AnalyzerDependency\MissingAnalyzerDependency.cs" />
<Compile Include="Implementation\Options\LocalUserRegistryOptionPersister.cs" />
<Compile Include="Implementation\Options\RoamingVisualStudioProfileOptionPersister.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.MetadataReferenceChange.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.AnalyzerReferenceChange.cs" />
<Compile Include="Implementation\Preview\ReferenceChange.ProjectReferenceChange.cs" />
......@@ -104,9 +106,7 @@
<Compile Include="Implementation\ProjectSystem\RuleSets\RuleSetEventHandler.cs" />
<Compile Include="Implementation\ProjectSystem\VisualStudioProjectTracker_IVsSolutionWorkingFoldersEvents.cs" />
<Compile Include="Implementation\Extensions\VisualStudioWorkspaceImplExtensions.cs" />
<Compile Include="Implementation\Options\AbstractLanguageSettingsSerializer.cs" />
<Compile Include="Implementation\Options\ExportLanguageSpecificOptionSerializerAttribute.cs" />
<Compile Include="Implementation\Options\ExportOptionSerializerAttribute.cs" />
<Compile Include="Implementation\Options\LanguageSettingsPersister.cs" />
<Compile Include="Implementation\ProjectSystem\Interop\IVsUndoState.cs" />
<Compile Include="Implementation\Remote\IRemoteHostClientFactory.cs" />
<Compile Include="Implementation\Remote\IRemoteHostClientService.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Composition;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.VisualStudio.Shell;
using Microsoft.Win32;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options
{
[ExportOptionSerializer(
EditorComponentOnOffOptions.OptionName,
InternalFeatureOnOffOptions.OptionName,
PerformanceFunctionIdOptionsProvider.Name,
LoggerOptions.FeatureName,
CacheOptions.FeatureName,
InternalDiagnosticsOptions.OptionName,
InternalSolutionCrawlerOptions.OptionName), Shared]
internal class InternalOptionSerializer : AbstractLocalUserRegistryOptionSerializer
{
[ImportingConstructor]
public InternalOptionSerializer(SVsServiceProvider serviceProvider)
: base(serviceProvider)
{
}
protected override string GetCollectionPathForOption(OptionKey key)
{
if (key.Option.Feature == EditorComponentOnOffOptions.OptionName)
{
return @"Roslyn\Internal\OnOff\Components";
}
else if (key.Option.Feature == InternalFeatureOnOffOptions.OptionName)
{
return @"Roslyn\Internal\OnOff\Features";
}
else if (key.Option.Feature == PerformanceFunctionIdOptionsProvider.Name)
{
return @"Roslyn\Internal\Performance\FunctionId";
}
else if (key.Option.Feature == LoggerOptions.FeatureName)
{
return @"Roslyn\Internal\Performance\Logger";
}
else if (key.Option.Feature == InternalDiagnosticsOptions.OptionName)
{
return @"Roslyn\Internal\Diagnostics";
}
else if (key.Option.Feature == InternalSolutionCrawlerOptions.OptionName)
{
return @"Roslyn\Internal\SolutionCrawler";
}
else if (key.Option.Feature == CacheOptions.FeatureName)
{
return @"Roslyn\Internal\Performance\Cache";
}
throw ExceptionUtilities.Unreachable;
}
}
}
......@@ -236,12 +236,9 @@
<Compile Include="Options\AbstractOptionPage.cs" />
<Compile Include="Options\AbstractOptionPageControl.cs" />
<Compile Include="Options\AbstractRadioButtonViewModel.cs" />
<Compile Include="Options\AbstractSettingsManagerOptionSerializer.cs" />
<Compile Include="Options\AbstractLocalUserRegistryOptionSerializer.cs" />
<Compile Include="Options\CheckBoxViewModel.cs" />
<Compile Include="Options\HeaderItemViewModel.cs" />
<Compile Include="Options\OptionBinding.cs" />
<Compile Include="Options\InternalOptionSerializer.cs" />
<Compile Include="Options\PerLanguageOptionBinding.cs" />
<Compile Include="Options\RadioButtonViewModel.cs" />
<Compile Include="Options\Style\NamingPreferences\EnforcementLevel.cs" />
......
......@@ -130,8 +130,6 @@
<Compile Include="Options\AutomationObject.vb" />
<Compile Include="Options\Formatting\CodeStylePage.vb" />
<Compile Include="Options\NamingStylesOptionPage.vb" />
<Compile Include="Options\VisualBasicLanguageSettingsSerializer.vb" />
<Compile Include="Options\VisualBasicSettingsManagerOptionSerializer.vb" />
<Compile Include="Options\StyleOptionPage.vb" />
<Compile Include="Options\AdvancedOptionPage.vb" />
<Compile Include="Options\AdvancedOptionPageControl.xaml.vb">
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Editor.Options
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.VisualStudio.Shell
Imports Microsoft.VisualStudio.LanguageServices.Implementation.Options
Imports Microsoft.CodeAnalysis.Options
Imports System.Composition
Imports Microsoft.CodeAnalysis.Shared.Options
Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options
<ExportLanguageSpecificOptionSerializer(
LanguageNames.VisualBasic,
FormattingOptions.TabFeatureName,
BraceCompletionOptions.FeatureName,
CompletionOptions.FeatureName,
SignatureHelpOptions.FeatureName,
NavigationBarOptions.FeatureName), [Shared]>
Friend NotInheritable Class VisualBasicLanguageSettingsSerializer
Inherits AbstractLanguageSettingsSerializer
<ImportingConstructor>
Public Sub New(serviceProvider As SVsServiceProvider)
MyBase.New(Guids.VisualBasicLanguageServiceId, LanguageNames.VisualBasic, serviceProvider)
End Sub
Public Overrides Function TryFetch(optionKey As OptionKey, ByRef value As Object) As Boolean
If optionKey.Option Is CompletionOptions.HideAdvancedMembers Then
Return False
End If
Return MyBase.TryFetch(optionKey, value)
End Function
End Class
End Namespace
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Collections.Immutable
Imports System.Composition
Imports System.Reflection
Imports System.Xml.Linq
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeStyle
Imports Microsoft.CodeAnalysis.Completion
Imports Microsoft.CodeAnalysis.Editor.Shared.Options
Imports Microsoft.CodeAnalysis.ExtractMethod
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.Shared.Options
Imports Microsoft.CodeAnalysis.Simplification
Imports Microsoft.VisualStudio.LanguageServices.Implementation
Imports Microsoft.VisualStudio.LanguageServices.Implementation.Options
Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.Options
<ExportLanguageSpecificOptionSerializer(
LanguageNames.VisualBasic,
AddImportOptions.FeatureName,
CodeStyleOptions.PerLanguageCodeStyleOption,
CompletionOptions.FeatureName,
ExtractMethodOptions.FeatureName,
FeatureOnOffOptions.OptionName,
FormattingOptions.InternalTabFeatureName,
ServiceFeatureOnOffOptions.OptionName,
SimplificationOptions.PerLanguageFeatureName,
VisualStudioNavigationOptions.FeatureName), [Shared]>
Friend NotInheritable Class VisualBasicSettingsManagerOptionSerializer
Inherits AbstractSettingsManagerOptionSerializer
<ImportingConstructor>
Public Sub New(workspace As VisualStudioWorkspaceImpl)
MyBase.New(workspace)
End Sub
Private Const Style_QualifyFieldAccess As String = NameOf(AutomationObject.Style_QualifyFieldAccess)
Private Const Style_QualifyPropertyAccess As String = NameOf(AutomationObject.Style_QualifyPropertyAccess)
Private Const Style_QualifyMethodAccess As String = NameOf(AutomationObject.Style_QualifyMethodAccess)
Private Const Style_QualifyEventAccess As String = NameOf(AutomationObject.Style_QualifyEventAccess)
Private Const Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration = NameOf(AutomationObject.Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration)
Private Const Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess = NameOf(AutomationObject.Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess)
Protected Overrides Function CreateStorageKeyToOptionMap() As ImmutableDictionary(Of String, IOption)
Dim Result As ImmutableDictionary(Of String, IOption).Builder = ImmutableDictionary.Create(Of String, IOption)(StringComparer.OrdinalIgnoreCase).ToBuilder()
Result.AddRange(New KeyValuePair(Of String, IOption)() {
New KeyValuePair(Of String, IOption)(SettingStorageRoot + "PrettyListing", FeatureOnOffOptions.PrettyListing),
New KeyValuePair(Of String, IOption)(SettingStorageRoot + "DisplayLineSeparators", FeatureOnOffOptions.LineSeparator),
New KeyValuePair(Of String, IOption)(SettingStorageRoot + "Outlining", FeatureOnOffOptions.Outlining),
New KeyValuePair(Of String, IOption)(SettingStorageRoot + "EnableHighlightReferences", FeatureOnOffOptions.ReferenceHighlighting),
New KeyValuePair(Of String, IOption)(SettingStorageRoot + "EnableHighlightRelatedKeywords", FeatureOnOffOptions.KeywordHighlighting),
New KeyValuePair(Of String, IOption)(SettingStorageRoot + "RenameTrackingPreview", FeatureOnOffOptions.RenameTrackingPreview),
New KeyValuePair(Of String, IOption)(SettingStorageRoot + "AutoEndInsert", FeatureOnOffOptions.EndConstruct),
New KeyValuePair(Of String, IOption)(SettingStorageRoot + "AutoComment", FeatureOnOffOptions.AutoXmlDocCommentGeneration),
New KeyValuePair(Of String, IOption)(SettingStorageRoot + "AutoRequiredMemberInsert", FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers)})
Dim Types As Type() = {
GetType(AddImportOptions),
GetType(CompletionOptions),
GetType(FormattingOptions),
GetType(ExtractMethodOptions),
GetType(SimplificationOptions),
GetType(ServiceFeatureOnOffOptions),
GetType(VisualStudioNavigationOptions),
GetType(CodeStyleOptions)}
Dim Flags As BindingFlags = BindingFlags.Public Or BindingFlags.Static
Result.AddRange(AbstractSettingsManagerOptionSerializer.GetOptionInfoFromTypeFields(Types, Flags, AddressOf GetOptionInfo))
Return Result.ToImmutable()
End Function
Protected Overrides ReadOnly Property LanguageName As String
Get
Return LanguageNames.VisualBasic
End Get
End Property
Protected Overrides ReadOnly Property SettingStorageRoot As String
Get
Return "TextEditor.VisualBasic.Specific."
End Get
End Property
Protected Overrides Function SupportsOption([option] As IOption, languageName As String) As Boolean
If [option].Name = CompletionOptions.EnterKeyBehavior.Name OrElse
[option].Name = CompletionOptions.TriggerOnTypingLetters.Name OrElse
[option].Name = CompletionOptions.TriggerOnDeletion.Name Then
Return True
ElseIf [option].Name = CompletionOptions.SnippetsBehavior.Name Then
Return True
ElseIf languageName = LanguageNames.VisualBasic Then
If [option].Feature = FeatureOnOffOptions.OptionName Then
Return [option].Name = FeatureOnOffOptions.PrettyListing.Name OrElse
[option].Name = FeatureOnOffOptions.LineSeparator.Name OrElse
[option].Name = FeatureOnOffOptions.Outlining.Name OrElse
[option].Name = FeatureOnOffOptions.ReferenceHighlighting.Name OrElse
[option].Name = FeatureOnOffOptions.KeywordHighlighting.Name OrElse
[option].Name = FeatureOnOffOptions.RenameTrackingPreview.Name OrElse
[option].Name = FeatureOnOffOptions.EndConstruct.Name OrElse
[option].Name = FeatureOnOffOptions.AutoXmlDocCommentGeneration.Name OrElse
[option].Name = FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers.Name
End If
Return [option].Feature = FormattingOptions.InternalTabFeatureName OrElse
[option].Feature = AddImportOptions.FeatureName OrElse
[option].Feature = CodeStyleOptions.PerLanguageCodeStyleOption OrElse
[option].Feature = CompletionOptions.FeatureName OrElse
[option].Feature = ExtractMethodOptions.FeatureName OrElse
[option].Feature = SimplificationOptions.PerLanguageFeatureName OrElse
[option].Feature = ServiceFeatureOnOffOptions.OptionName OrElse
[option].Feature = VisualStudioNavigationOptions.FeatureName
End If
Return False
End Function
Protected Overrides Function GetStorageKeyForOption(key As IOption) As String
If key.Feature = FeatureOnOffOptions.OptionName Then
Select Case key.Name
Case FeatureOnOffOptions.PrettyListing.Name
Return SettingStorageRoot + "PrettyListing"
Case FeatureOnOffOptions.LineSeparator.Name
Return SettingStorageRoot + "DisplayLineSeparators"
Case FeatureOnOffOptions.Outlining.Name
Return SettingStorageRoot + "Outlining"
Case FeatureOnOffOptions.ReferenceHighlighting.Name
Return SettingStorageRoot + "EnableHighlightReferences"
Case FeatureOnOffOptions.KeywordHighlighting.Name
Return SettingStorageRoot + "EnableHighlightRelatedKeywords"
Case FeatureOnOffOptions.RenameTrackingPreview.Name
Return SettingStorageRoot + "RenameTrackingPreview"
Case FeatureOnOffOptions.EndConstruct.Name
Return SettingStorageRoot + "AutoEndInsert"
Case FeatureOnOffOptions.AutoXmlDocCommentGeneration.Name
Return SettingStorageRoot + "AutoComment"
Case FeatureOnOffOptions.AutomaticInsertionOfAbstractOrInterfaceMembers.Name
Return SettingStorageRoot + "AutoRequiredMemberInsert"
Case FeatureOnOffOptions.FormatOnPaste.Name
Return Nothing
End Select
End If
Return MyBase.GetStorageKeyForOption(key)
End Function
Public Overrides Function TryFetch(optionKey As OptionKey, ByRef value As Object) As Boolean
If Me.Manager Is Nothing Then
Debug.Fail("Manager is unexpectedly Nothing")
Return False
End If
' code style use Me.
If optionKey.Option Is CodeStyleOptions.QualifyFieldAccess Then
Return FetchStyleBool(Style_QualifyFieldAccess, value)
ElseIf optionKey.Option Is CodeStyleOptions.QualifyPropertyAccess Then
Return FetchStyleBool(Style_QualifyPropertyAccess, value)
ElseIf optionKey.Option Is CodeStyleOptions.QualifyMethodAccess Then
Return FetchStyleBool(Style_QualifyMethodAccess, value)
ElseIf optionKey.Option Is CodeStyleOptions.QualifyEventAccess Then
Return FetchStyleBool(Style_QualifyEventAccess, value)
End If
' code style use predefined/framework type
If optionKey.Option Is CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration Then
Return FetchStyleBool(Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration, value)
ElseIf optionKey.Option Is CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess Then
Return FetchStyleBool(Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess, value)
End If
If optionKey.Option Is CompletionOptions.EnterKeyBehavior Then
Return FetchEnterKeyBehavior(optionKey, value)
End If
If optionKey.Option Is CompletionOptions.SnippetsBehavior Then
Return FetchSnippetsBehavior(optionKey, value)
End If
If optionKey.Option Is CompletionOptions.TriggerOnDeletion Then
Return FetchTriggerOnDeletion(optionKey, value)
End If
Return MyBase.TryFetch(optionKey, value)
End Function
Private Function FetchTriggerOnDeletion(optionKey As OptionKey, ByRef value As Object) As Boolean
If MyBase.TryFetch(optionKey, value) Then
If value Is Nothing Then
' The default behavior for VB is to trigger completion on deletion.
value = CType(True, Boolean?)
End If
Return True
End If
Return False
End Function
Private Function FetchEnterKeyBehavior(optionKey As OptionKey, ByRef value As Object) As Boolean
If MyBase.TryFetch(optionKey, value) Then
If value.Equals(EnterKeyRule.Default) Then
value = EnterKeyRule.Always
End If
Return True
End If
Return False
End Function
Private Function FetchSnippetsBehavior(optionKey As OptionKey, ByRef value As Object) As Boolean
If MyBase.TryFetch(optionKey, value) Then
If value.Equals(SnippetsRule.Default) Then
value = SnippetsRule.IncludeAfterTypingIdentifierQuestionTab
End If
Return True
End If
Return False
End Function
Public Overrides Function TryPersist(optionKey As OptionKey, value As Object) As Boolean
If Me.Manager Is Nothing Then
Debug.Fail("Manager is unexpectedly Nothing")
Return False
End If
' code style use Me.
If optionKey.Option Is CodeStyleOptions.QualifyFieldAccess Then
Return PersistStyleOption(Of Boolean)(Style_QualifyFieldAccess, value)
ElseIf optionKey.Option Is CodeStyleOptions.QualifyPropertyAccess Then
Return PersistStyleOption(Of Boolean)(Style_QualifyPropertyAccess, value)
ElseIf optionKey.Option Is CodeStyleOptions.QualifyMethodAccess Then
Return PersistStyleOption(Of Boolean)(Style_QualifyMethodAccess, value)
ElseIf optionKey.Option Is CodeStyleOptions.QualifyEventAccess Then
Return PersistStyleOption(Of Boolean)(Style_QualifyEventAccess, value)
End If
' code style use predefined/framework type
If optionKey.Option Is CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration Then
Return PersistStyleOption(Of Boolean)(Style_PreferIntrinsicPredefinedTypeKeywordInDeclaration, value)
ElseIf optionKey.Option Is CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess Then
Return PersistStyleOption(Of Boolean)(Style_PreferIntrinsicPredefinedTypeKeywordInMemberAccess, value)
End If
Return MyBase.TryPersist(optionKey, value)
End Function
Private Function FetchStyleBool(settingName As String, ByRef value As Object) As Boolean
Dim typeStyleValue = Manager.GetValueOrDefault(Of String)(settingName)
Return FetchStyleOption(Of Boolean)(typeStyleValue, value)
End Function
Private Shared Function FetchStyleOption(Of T)(typeStyleOptionValue As String, ByRef value As Object) As Boolean
If String.IsNullOrEmpty(typeStyleOptionValue) Then
value = CodeStyleOption(Of T).Default
Else
value = CodeStyleOption(Of T).FromXElement(XElement.Parse(typeStyleOptionValue))
End If
Return True
End Function
Private Function PersistStyleOption(Of T)([option] As String, value As Object) As Boolean
Dim serializedValue = CType(value, CodeStyleOption(Of T)).ToXElement().ToString()
Me.Manager.SetValueAsync([option], value:=serializedValue, isMachineLocal:=False)
Return True
End Function
End Class
End Namespace
......@@ -13,8 +13,7 @@ namespace Microsoft.CodeAnalysis.Options
internal class GlobalOptionService : IGlobalOptionService
{
private readonly Lazy<HashSet<IOption>> _options;
private readonly ImmutableDictionary<string, ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>>> _featureNameToOptionSerializers =
ImmutableDictionary.Create<string, ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>>>();
private readonly ImmutableArray<Lazy<IOptionPersister>> _optionSerializers;
private readonly object _gate = new object();
......@@ -23,7 +22,7 @@ internal class GlobalOptionService : IGlobalOptionService
[ImportingConstructor]
public GlobalOptionService(
[ImportMany] IEnumerable<Lazy<IOptionProvider>> optionProviders,
[ImportMany] IEnumerable<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers)
[ImportMany] IEnumerable<Lazy<IOptionPersister>> optionSerializers)
{
_options = new Lazy<HashSet<IOption>>(() =>
{
......@@ -37,52 +36,25 @@ internal class GlobalOptionService : IGlobalOptionService
return options;
});
foreach (var optionSerializerAndMetadata in optionSerializers)
{
foreach (var featureName in optionSerializerAndMetadata.Metadata.Features)
{
ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>> existingSerializers;
if (!_featureNameToOptionSerializers.TryGetValue(featureName, out existingSerializers))
{
existingSerializers = ImmutableArray.Create<Lazy<IOptionSerializer, OptionSerializerMetadata>>();
}
_featureNameToOptionSerializers = _featureNameToOptionSerializers.SetItem(featureName, existingSerializers.Add(optionSerializerAndMetadata));
}
}
_optionSerializers = optionSerializers.ToImmutableArray();
_currentValues = ImmutableDictionary.Create<OptionKey, object>();
}
private object LoadOptionFromSerializerOrGetDefault(OptionKey optionKey)
{
lock (_gate)
foreach (var serializer in _optionSerializers)
{
ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers;
if (_featureNameToOptionSerializers.TryGetValue(optionKey.Option.Feature, out optionSerializers))
// We have a deserializer, so deserialize and use that value.
object deserializedValue;
if (serializer.Value.TryFetch(optionKey, out deserializedValue))
{
foreach (var serializer in optionSerializers)
{
// There can be options (ex, formatting) that only exist in only one specific language. In those cases,
// feature's serializer should exist in only that language.
if (!SupportedSerializer(optionKey, serializer.Metadata))
{
continue;
}
// We have a deserializer, so deserialize and use that value.
object deserializedValue;
if (serializer.Value.TryFetch(optionKey, out deserializedValue))
{
return deserializedValue;
}
}
return deserializedValue;
}
// Just use the default. We will still cache this so we aren't trying to deserialize
// over and over.
return optionKey.Option.DefaultValue;
}
// Just use the default. We will still cache this so we aren't trying to deserialize
// over and over.
return optionKey.Option.DefaultValue;
}
public IEnumerable<IOption> GetRegisteredOptions()
......@@ -153,22 +125,11 @@ public void SetOptions(OptionSet optionSet)
_currentValues = _currentValues.SetItem(optionKey, setValue);
ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers;
if (_featureNameToOptionSerializers.TryGetValue(optionKey.Option.Feature, out optionSerializers))
foreach (var serializer in _optionSerializers)
{
foreach (var serializer in optionSerializers)
if (serializer.Value.TryPersist(optionKey, setValue))
{
// There can be options (ex, formatting) that only exist in only one specific language. In those cases,
// feature's serializer should exist in only that language.
if (!SupportedSerializer(optionKey, serializer.Metadata))
{
continue;
}
if (serializer.Value.TryPersist(optionKey, setValue))
{
break;
}
break;
}
}
}
......@@ -178,6 +139,16 @@ public void SetOptions(OptionSet optionSet)
RaiseEvents(changedOptions);
}
public void RefreshOption(OptionKey optionKey, object newValue)
{
lock (_gate)
{
_currentValues = _currentValues.SetItem(optionKey, newValue);
}
RaiseEvents(new List<OptionChangedEventArgs> { new OptionChangedEventArgs(optionKey, newValue) });
}
private void RaiseEvents(List<OptionChangedEventArgs> changedOptions)
{
var optionChanged = OptionChanged;
......@@ -190,11 +161,6 @@ private void RaiseEvents(List<OptionChangedEventArgs> changedOptions)
}
}
private static bool SupportedSerializer(OptionKey optionKey, OptionSerializerMetadata metadata)
{
return optionKey.Language == null || optionKey.Language == metadata.Language;
}
public event EventHandler<OptionChangedEventArgs> OptionChanged;
}
}
\ No newline at end of file
......@@ -31,7 +31,7 @@ internal interface IGlobalOptionService
object GetOption(OptionKey optionKey);
/// <summary>
/// Applies a set of options.
/// Applies a set of options, invoking serializers if needed.
/// </summary>
void SetOptions(OptionSet optionSet);
......@@ -41,5 +41,10 @@ internal interface IGlobalOptionService
IEnumerable<IOption> GetRegisteredOptions();
event EventHandler<OptionChangedEventArgs> OptionChanged;
/// <summary>
/// Refreshes the stored value of a serialized option. This should only be called from serializers.
/// </summary>
void RefreshOption(OptionKey optionKey, object newValue);
}
}
\ No newline at end of file
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.Options.Providers
namespace Microsoft.CodeAnalysis.Options
{
/// <summary>
/// Exportable by a host to specify the save and restore behavior for a particular set of
/// values.
/// </summary>
internal interface IOptionSerializer
internal interface IOptionPersister
{
bool TryFetch(OptionKey optionKey, out object value);
bool TryPersist(OptionKey optionKey, object value);
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Host.Mef;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Options.Providers
{
internal class OptionSerializerMetadata : LanguageMetadata
{
public IEnumerable<string> Features { get; }
public OptionSerializerMetadata(IDictionary<string, object> data) : base(data)
{
this.Features = (IEnumerable<string>)data.GetValueOrDefault("Features");
}
}
}
......@@ -390,7 +390,7 @@
<Compile Include="FindSymbols\SymbolTree\SymbolTreeInfo.FirstEntityHandleProvider.cs" />
<Compile Include="FindSymbols\SyntaxTree\IDeclarationInfo.cs" />
<Compile Include="LanguageServices\SyntaxFactsService\AbstractSyntaxFactsService.cs" />
<Compile Include="Options\OptionService.cs" />
<Compile Include="Options\GlobalOptionService.cs" />
<Compile Include="Options\IGlobalOptionService.cs" />
<Compile Include="Options\IWorkspaceOptionService.cs" />
<Compile Include="Shared\RuntimeOptions.cs" />
......@@ -758,8 +758,7 @@
<Compile Include="Options\PerLanguageOption.cs" />
<Compile Include="Options\Providers\ExportOptionProviderAttribute.cs" />
<Compile Include="Options\Providers\IOptionProvider.cs" />
<Compile Include="Options\Providers\IOptionSerializer.cs" />
<Compile Include="Options\Providers\OptionSerializerMetadata.cs" />
<Compile Include="Options\IOptionPersister.cs" />
<Compile Include="OrderableMetadata.cs" />
<Compile Include="Recommendations\AbstractRecommendationService.cs" />
<Compile Include="Recommendations\IRecommendationService.cs" />
......@@ -1064,4 +1063,4 @@
</ItemGroup>
<Import Project="..\..\..\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems" Label="Shared" />
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</Project>
\ No newline at end of file
</Project>
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Options.Providers;
......@@ -17,15 +18,7 @@ public static OptionServiceFactory.OptionService GetService()
{
new Lazy<IOptionProvider>(() => new TestOptionsProvider())
},
new[]
{
new Lazy<IOptionSerializer, OptionSerializerMetadata>(
() =>
{
return new TestOptionSerializer();
},
new OptionSerializerMetadata(features))
}), workspaceServices: null);
Enumerable.Empty<Lazy<IOptionPersister>>()), workspaceServices: null);
}
internal class TestOptionsProvider : IOptionProvider
......@@ -35,19 +28,5 @@ public IEnumerable<IOption> GetOptions()
yield return new Option<bool>("Test Feature", "Test Name", false);
}
}
internal class TestOptionSerializer : IOptionSerializer
{
public bool TryFetch(OptionKey optionKey, out object value)
{
value = null;
return false;
}
public bool TryPersist(OptionKey optionKey, object value)
{
return false;
}
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册