提交 9902b306 编写于 作者: C CyrusNajmabadi

Move to a model with an explicit global and workspace level separation for options.

上级 565d5477
......@@ -29,7 +29,8 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
// give out new option service per workspace
return new OptionServiceFactory.OptionService(
workspaceServices, _providers, SpecializedCollections.EmptyEnumerable<Lazy<IOptionSerializer, OptionSerializerMetadata>>());
new GlobalOptionService(_providers, SpecializedCollections.EmptyEnumerable<Lazy<IOptionSerializer, OptionSerializerMetadata>>()),
workspaceServices);
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using Microsoft.CodeAnalysis.Options.Providers;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Options
{
[Export(typeof(IGlobalOptionService)), Shared]
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 object _gate = new object();
private ImmutableDictionary<OptionKey, object> _currentValues;
[ImportingConstructor]
public GlobalOptionService(
IEnumerable<Lazy<IOptionProvider>> optionProviders,
IEnumerable<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers)
{
_options = new Lazy<HashSet<IOption>>(() =>
{
var options = new HashSet<IOption>();
foreach (var provider in optionProviders)
{
options.AddRange(provider.Value.GetOptions());
}
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));
}
}
_currentValues = ImmutableDictionary.Create<OptionKey, object>();
}
private object LoadOptionFromSerializerOrGetDefault(OptionKey optionKey)
{
lock (_gate)
{
ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers;
if (_featureNameToOptionSerializers.TryGetValue(optionKey.Option.Feature, out optionSerializers))
{
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;
}
}
}
// 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()
{
return _options.Value;
}
public T GetOption<T>(Option<T> option)
{
return (T)GetOption(new OptionKey(option, language: null));
}
public T GetOption<T>(PerLanguageOption<T> option, string language)
{
return (T)GetOption(new OptionKey(option, language));
}
public object GetOption(OptionKey optionKey)
{
lock (_gate)
{
object value;
if (_currentValues.TryGetValue(optionKey, out value))
{
return value;
}
value = LoadOptionFromSerializerOrGetDefault(optionKey);
_currentValues = _currentValues.Add(optionKey, value);
return value;
}
}
public void SetOptions(OptionSet optionSet)
{
if (optionSet == null)
{
throw new ArgumentNullException(nameof(optionSet));
}
var workspaceOptionSet = optionSet as WorkspaceOptionSet;
if (workspaceOptionSet == null)
{
throw new ArgumentException(WorkspacesResources.OptionsDidNotComeFromWorkspace, paramName: nameof(optionSet));
}
var changedOptions = new List<OptionChangedEventArgs>();
lock (_gate)
{
foreach (var optionKey in workspaceOptionSet.GetAccessedOptions())
{
var setValue = optionSet.GetOption(optionKey);
object currentValue = this.GetOption(optionKey);
if (object.Equals(currentValue, setValue))
{
// Identical, so nothing is changing
continue;
}
// The value is actually changing, so update
changedOptions.Add(new OptionChangedEventArgs(optionKey, setValue));
_currentValues = _currentValues.SetItem(optionKey, setValue);
ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers;
if (_featureNameToOptionSerializers.TryGetValue(optionKey.Option.Feature, out optionSerializers))
{
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;
}
if (serializer.Value.TryPersist(optionKey, setValue))
{
break;
}
}
}
}
}
// Outside of the lock, raise the events on our task queue.
RaiseEvents(changedOptions);
}
private void RaiseEvents(List<OptionChangedEventArgs> changedOptions)
{
var optionChanged = OptionChanged;
if (optionChanged != null)
{
foreach (var changedOption in changedOptions)
{
optionChanged(this, changedOption);
}
}
}
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
// 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;
namespace Microsoft.CodeAnalysis.Options
{
/// <summary>
/// Provides services for reading and writing options.
/// This will provide support for options at the global level (i.e. shared among
/// all workspaces/services).
/// </summary>
internal interface IGlobalOptionService
{
/// <summary>
/// Gets the current value of the specific option.
/// </summary>
T GetOption<T>(Option<T> option);
/// <summary>
/// Gets the current value of the specific option.
/// </summary>
T GetOption<T>(PerLanguageOption<T> option, string languageName);
/// <summary>
/// Gets the current value of the specific option.
/// </summary>
object GetOption(OptionKey optionKey);
/// <summary>
/// Applies a set of options.
/// </summary>
void SetOptions(OptionSet optionSet);
/// <summary>
/// Returns the set of all registered options.
/// </summary>
IEnumerable<IOption> GetRegisteredOptions();
event EventHandler<OptionChangedEventArgs> OptionChanged;
}
}
\ No newline at end of file
......@@ -7,7 +7,10 @@
namespace Microsoft.CodeAnalysis.Options
{
/// <summary>
/// Provides services for reading and writing options.
/// Provides services for reading and writing options. This will provide support for
/// customizations workspaces need to perform around options. Note that global options
/// will normally still be offered through implementations of this. However, implementations
/// may customize things differently depending on their needs.
/// </summary>
internal interface IOptionService : IWorkspaceService
{
......@@ -43,4 +46,4 @@ internal interface IOptionService : IWorkspaceService
event EventHandler<OptionChangedEventArgs> OptionChanged;
}
}
}
\ 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
{
/// <summary>
/// Interface used for exposing functionality from the option service that we don't want to
/// ever be public.
/// </summary>
internal interface IWorkspaceOptionService : IOptionService
{
void OnWorkspaceDisposed(Workspace workspace);
}
}
\ No newline at end of file
......@@ -6,234 +6,116 @@
using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options.Providers;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Options
{
[ExportWorkspaceServiceFactory(typeof(IOptionService)), Shared]
internal class OptionServiceFactory : IWorkspaceServiceFactory
{
private readonly IEnumerable<Lazy<IOptionProvider>> _optionProviders;
private readonly IEnumerable<Lazy<IOptionSerializer, OptionSerializerMetadata>> _optionSerializers;
private readonly IGlobalOptionService _globalOptionService;
[ImportingConstructor]
public OptionServiceFactory(
[ImportMany] IEnumerable<Lazy<IOptionProvider>> optionProviders,
[ImportMany] IEnumerable<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers)
public OptionServiceFactory(IGlobalOptionService globalOptionService)
{
_optionProviders = optionProviders;
_optionSerializers = optionSerializers;
_globalOptionService = globalOptionService;
}
public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
{
return new OptionService(workspaceServices, _optionProviders, _optionSerializers);
return new OptionService(_globalOptionService, workspaceServices);
}
// Internal for testing purposes only.
internal class OptionService : IOptionService
/// <summary>
/// Wraps an underlying <see cref="IGlobalOptionService"/> and exposes its data to workspace
/// clients. Also takes the <see cref="IGlobalOptionService.OptionChanged"/> notifications
/// and forwards them along using the same <see cref="IWorkspaceTaskScheduler"/> used by the
/// <see cref="Workspace"/> this is connected to. i.e. instead of synchronously just passing
/// along the underlying events, these will be enqueued onto the workspaces eventing queue.
/// </summary>
// Internal for testing purposes.
internal class OptionService : IWorkspaceOptionService
{
private readonly Lazy<HashSet<IOption>> _options;
private readonly ImmutableDictionary<string, ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>>> _featureNameToOptionSerializers =
ImmutableDictionary.Create<string, ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>>>();
private readonly IGlobalOptionService _globalOptionService;
private readonly object _gate = new object();
/// <summary>
/// Can be null during tests.
/// </summary>
// Can be null during testing.
private readonly IWorkspaceTaskScheduler _taskQueue;
private ImmutableDictionary<OptionKey, object> _currentValues;
private readonly object _gate = new object();
private ImmutableArray<EventHandler<OptionChangedEventArgs>> _eventHandlers =
ImmutableArray<EventHandler<OptionChangedEventArgs>>.Empty;
public OptionService(
HostWorkspaceServices workspaceServices,
IEnumerable<Lazy<IOptionProvider>> optionProviders,
IEnumerable<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers)
IGlobalOptionService globalOptionService,
HostWorkspaceServices workspaceServices)
{
_globalOptionService = globalOptionService;
var workspaceTaskSchedulerFactory = workspaceServices?.GetRequiredService<IWorkspaceTaskSchedulerFactory>();
_taskQueue = workspaceTaskSchedulerFactory?.CreateTaskQueue();
_options = new Lazy<HashSet<IOption>>(() =>
{
var options = new HashSet<IOption>();
foreach (var provider in optionProviders)
{
options.AddRange(provider.Value.GetOptions());
}
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));
}
}
_globalOptionService.OptionChanged += OnGlobalOptionServiceOptionChanged;
}
_currentValues = ImmutableDictionary.Create<OptionKey, object>();
public void OnWorkspaceDisposed(Workspace workspace)
{
// Disconnect us from the underlying global service. That way it doesn't
// keep us around (and all the event handlers we're holding onto) forever.
_globalOptionService.OptionChanged -= OnGlobalOptionServiceOptionChanged;
}
private object LoadOptionFromSerializerOrGetDefault(OptionKey optionKey)
private void OnGlobalOptionServiceOptionChanged(object sender, OptionChangedEventArgs e)
{
lock (_gate)
var eventHandlers = GetEventHandlers();
if (eventHandlers.Length > 0)
{
ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers;
if (_featureNameToOptionSerializers.TryGetValue(optionKey.Option.Feature, out optionSerializers))
_taskQueue?.ScheduleTask(() =>
{
foreach (var serializer in optionSerializers)
foreach (var handler in eventHandlers)
{
// 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;
}
handler(this, e);
}
}
// Just use the default. We will still cache this so we aren't trying to deserialize
// over and over.
return optionKey.Option.DefaultValue;
}, "OptionsService.SetOptions");
}
}
public IEnumerable<IOption> GetRegisteredOptions()
{
return _options.Value;
}
public OptionSet GetOptions()
{
return new WorkspaceOptionSet(this);
}
public T GetOption<T>(Option<T> option)
{
return (T)GetOption(new OptionKey(option, language: null));
}
public T GetOption<T>(PerLanguageOption<T> option, string language)
{
return (T)GetOption(new OptionKey(option, language));
}
public object GetOption(OptionKey optionKey)
private ImmutableArray<EventHandler<OptionChangedEventArgs>> GetEventHandlers()
{
lock (_gate)
{
object value;
if (_currentValues.TryGetValue(optionKey, out value))
{
return value;
}
value = LoadOptionFromSerializerOrGetDefault(optionKey);
_currentValues = _currentValues.Add(optionKey, value);
return value;
return _eventHandlers;
}
}
public void SetOptions(OptionSet optionSet)
public event EventHandler<OptionChangedEventArgs> OptionChanged
{
if (optionSet == null)
{
throw new ArgumentNullException(nameof(optionSet));
}
var workspaceOptionSet = optionSet as WorkspaceOptionSet;
if (workspaceOptionSet == null)
{
throw new ArgumentException(WorkspacesResources.OptionsDidNotComeFromWorkspace, paramName: nameof(optionSet));
}
var changedOptions = new List<OptionChangedEventArgs>();
lock (_gate)
add
{
foreach (var optionKey in workspaceOptionSet.GetAccessedOptions())
lock (_gate)
{
var setValue = optionSet.GetOption(optionKey);
object currentValue = this.GetOption(optionKey);
if (object.Equals(currentValue, setValue))
{
// Identical, so nothing is changing
continue;
}
// The value is actually changing, so update
changedOptions.Add(new OptionChangedEventArgs(optionKey, setValue));
_currentValues = _currentValues.SetItem(optionKey, setValue);
ImmutableArray<Lazy<IOptionSerializer, OptionSerializerMetadata>> optionSerializers;
if (_featureNameToOptionSerializers.TryGetValue(optionKey.Option.Feature, out optionSerializers))
{
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;
}
if (serializer.Value.TryPersist(optionKey, setValue))
{
break;
}
}
}
_eventHandlers = _eventHandlers.Add(value);
}
}
// Outside of the lock, raise the events on our task queue.
_taskQueue?.ScheduleTask(() =>
{
RaiseEvents(changedOptions);
}, "OptionsService.SetOptions");
}
private void RaiseEvents(List<OptionChangedEventArgs> changedOptions)
{
var optionChanged = OptionChanged;
if (optionChanged != null)
remove
{
foreach (var changedOption in changedOptions)
lock(_gate)
{
optionChanged(this, changedOption);
_eventHandlers = _eventHandlers.Remove(value);
}
}
}
private static bool SupportedSerializer(OptionKey optionKey, OptionSerializerMetadata metadata)
public OptionSet GetOptions()
{
return optionKey.Language == null || optionKey.Language == metadata.Language;
return new WorkspaceOptionSet(this);
}
public event EventHandler<OptionChangedEventArgs> OptionChanged;
// Simple forwarding functions.
public object GetOption(OptionKey optionKey) => _globalOptionService.GetOption(optionKey);
public T GetOption<T>(Option<T> option) => _globalOptionService.GetOption(option);
public T GetOption<T>(PerLanguageOption<T> option, string languageName) => _globalOptionService.GetOption(option, languageName);
public IEnumerable<IOption> GetRegisteredOptions() => _globalOptionService.GetRegisteredOptions();
public void SetOptions(OptionSet optionSet) => _globalOptionService.SetOptions(optionSet);
}
}
}
\ No newline at end of file
......@@ -290,6 +290,8 @@ protected virtual void Dispose(bool finalize)
{
this.ClearSolutionData();
}
((IWorkspaceOptionService)this.Services.GetService<IOptionService>()).OnWorkspaceDisposed(this);
}
#region Host API
......
......@@ -361,6 +361,9 @@
<Compile Include="Diagnostics\InternalRuntimeDiagnosticOptions.cs" />
<Compile Include="FindSymbols\SyntaxTree\IDeclarationInfo.cs" />
<Compile Include="LanguageServices\SyntaxFactsService\AbstractSyntaxFactsService.cs" />
<Compile Include="Options\GlobalOptionService.cs" />
<Compile Include="Options\IGlobalOptionService.cs" />
<Compile Include="Options\IWorkspaceOptionService.cs" />
<Compile Include="SolutionCrawler\ExportIncrementalAnalyzerProviderAttribute.cs" />
<Compile Include="SolutionCrawler\IIncrementalAnalyzer.cs" />
<Compile Include="SolutionCrawler\IIncrementalAnalyzerProvider.cs" />
......
......@@ -13,7 +13,7 @@ public static OptionServiceFactory.OptionService GetService()
{
var features = new Dictionary<string, object>();
features.Add("Features", new List<string>(new[] { "Test Features" }));
return new OptionServiceFactory.OptionService(null, new[]
return new OptionServiceFactory.OptionService(new GlobalOptionService(new[]
{
new Lazy<IOptionProvider>(() => new TestOptionsProvider())
},
......@@ -25,7 +25,7 @@ public static OptionServiceFactory.OptionService GetService()
return new TestOptionSerializer();
},
new OptionSerializerMetadata(features))
});
}), null);
}
internal class TestOptionsProvider : IOptionProvider
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册