|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Preview;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Projection;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options;
internal abstract class AbstractOptionPreviewViewModel : AbstractNotifyPropertyChanged, IDisposable
{
private readonly IComponentModel _componentModel;
private IWpfTextViewHost _textViewHost;
private readonly IContentType _contentType;
private readonly IEditorOptionsFactoryService _editorOptions;
private readonly ITextEditorFactoryService _textEditorFactoryService;
private readonly ITextBufferCloneService _textBufferCloneService;
private readonly IProjectionBufferFactoryService _projectionBufferFactory;
private readonly IContentTypeRegistryService _contentTypeRegistryService;
public List<object> Items { get; set; }
public ObservableCollection<AbstractCodeStyleOptionViewModel> CodeStyleItems { get; set; }
public OptionStore OptionStore { get; set; }
protected AbstractOptionPreviewViewModel(OptionStore optionStore, IServiceProvider serviceProvider, string language)
{
this.OptionStore = optionStore;
this.Items = [];
this.CodeStyleItems = [];
_componentModel = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel));
_contentTypeRegistryService = _componentModel.GetService<IContentTypeRegistryService>();
_textBufferCloneService = _componentModel.GetService<ITextBufferCloneService>();
_textEditorFactoryService = _componentModel.GetService<ITextEditorFactoryService>();
_projectionBufferFactory = _componentModel.GetService<IProjectionBufferFactoryService>();
_editorOptions = _componentModel.GetService<IEditorOptionsFactoryService>();
this.Language = language;
_contentType = _contentTypeRegistryService.GetContentType(ContentTypeNames.CSharpContentType);
}
public void SetOptionAndUpdatePreview<T>(T value, IOption2 option, string preview)
{
object actualValue;
if (option.DefaultValue is ICodeStyleOption2 codeStyleOption)
{
// The value provided is either an ICodeStyleOption2 OR the underlying ICodeStyleOption2.Value
if (value is ICodeStyleOption2 newCodeStyleOption)
{
actualValue = codeStyleOption.WithValue(newCodeStyleOption.Value).WithNotification(newCodeStyleOption.Notification);
}
else
{
actualValue = codeStyleOption.WithValue(value);
}
}
else
{
actualValue = value;
}
OptionStore.SetOption(option, option.IsPerLanguage ? Language : null, actualValue);
UpdateDocument(preview);
}
public IWpfTextViewHost TextViewHost
{
get
{
return _textViewHost;
}
private set
{
// make sure we close previous view.
_textViewHost?.Close();
SetProperty(ref _textViewHost, value);
}
}
public string Language { get; }
public void UpdatePreview(string text)
{
var service = VisualStudioMefHostServices.Create(_componentModel.GetService<ExportProvider>());
var workspace = new PreviewWorkspace(service);
var fileName = "project." + (Language == "C#" ? "csproj" : "vbproj");
var project = workspace.CurrentSolution.AddProject(fileName, "assembly.dll", Language);
// use the mscorlib, system, and system.core that are loaded in the current process.
string[] references =
[
"mscorlib",
"System",
"System.Core"
];
var metadataService = workspace.Services.GetService<IMetadataService>();
var referenceAssemblies = Thread.GetDomain().GetAssemblies()
.Where(x => references.Contains(x.GetName(true).Name, StringComparer.OrdinalIgnoreCase))
.Select(a => metadataService.GetReference(a.Location, MetadataReferenceProperties.Assembly));
project = project.WithMetadataReferences(referenceAssemblies);
var document = project.AddDocument("document", SourceText.From(text, Encoding.UTF8));
var fallbackFormattingOptions = OptionStore.GlobalOptions.GetSyntaxFormattingOptions(document.Project.Services);
var formattingService = document.GetRequiredLanguageService<ISyntaxFormattingService>();
var formattingOptions = formattingService.GetFormattingOptions(OptionStore);
var root = document.GetSyntaxRootSynchronously(CancellationToken.None);
var formatted = Formatter.Format(root, document.Project.Solution.Services, formattingOptions, CancellationToken.None);
var textBuffer = _textBufferCloneService.Clone(SourceText.From(formatted.ToFullString(), Encoding.UTF8), _contentType);
var container = textBuffer.AsTextContainer();
var projection = _projectionBufferFactory.CreateProjectionBufferWithoutIndentation(_contentTypeRegistryService,
_editorOptions.CreateOptions(),
textBuffer.CurrentSnapshot,
separator: "",
exposedLineSpans: [.. GetExposedLineSpans(textBuffer.CurrentSnapshot)]);
var textView = _textEditorFactoryService.CreateTextView(projection,
_textEditorFactoryService.CreateTextViewRoleSet(PredefinedTextViewRoles.Interactive));
this.TextViewHost = _textEditorFactoryService.CreateTextViewHost(textView, setFocus: false);
workspace.TryApplyChanges(document.Project.Solution);
workspace.OpenDocument(document.Id, container);
this.TextViewHost.Closed += (s, a) =>
{
workspace.Dispose();
workspace = null;
};
}
private static List<LineSpan> GetExposedLineSpans(ITextSnapshot textSnapshot)
{
const string start = "//[";
const string end = "//]";
var bufferText = textSnapshot.GetText().ToString();
var lineSpans = new List<LineSpan>();
var lastEndIndex = 0;
while (true)
{
var startIndex = bufferText.IndexOf(start, lastEndIndex, StringComparison.Ordinal);
if (startIndex == -1)
{
break;
}
var endIndex = bufferText.IndexOf(end, lastEndIndex, StringComparison.Ordinal);
var startLine = textSnapshot.GetLineNumberFromPosition(startIndex) + 1;
var endLine = textSnapshot.GetLineNumberFromPosition(endIndex);
lineSpans.Add(LineSpan.FromBounds(startLine, endLine));
lastEndIndex = endIndex + end.Length;
}
return lineSpans;
}
public void Dispose()
{
if (_textViewHost != null)
{
_textViewHost.Close();
_textViewHost = null;
}
}
private void UpdateDocument(string text)
=> UpdatePreview(text);
protected void AddParenthesesOption(
OptionStore optionStore,
PerLanguageOption2<CodeStyleOption2<ParenthesesPreference>> languageOption,
string title, string[] examples, bool defaultAddForClarity)
{
var preferences = new List<ParenthesesPreference>();
var codeStylePreferences = new List<CodeStylePreference>();
preferences.Add(ParenthesesPreference.AlwaysForClarity);
codeStylePreferences.Add(new CodeStylePreference(ServicesVSResources.Always_for_clarity, isChecked: defaultAddForClarity));
preferences.Add(ParenthesesPreference.NeverIfUnnecessary);
codeStylePreferences.Add(new CodeStylePreference(
ServicesVSResources.Never_if_unnecessary,
isChecked: !defaultAddForClarity));
CodeStyleItems.Add(new EnumCodeStyleOptionViewModel<ParenthesesPreference>(
languageOption, title, [.. preferences],
examples, this, optionStore, ServicesVSResources.Parentheses_preferences_colon,
codeStylePreferences));
}
protected void AddUnusedParameterOption(OptionStore optionStore, string title, string[] examples)
{
var unusedParameterPreferences = new List<CodeStylePreference>
{
new CodeStylePreference(ServicesVSResources.Non_public_methods, isChecked: false),
new CodeStylePreference(ServicesVSResources.All_methods, isChecked: true),
};
var enumValues = new[]
{
UnusedParametersPreference.NonPublicMethods,
UnusedParametersPreference.AllMethods
};
CodeStyleItems.Add(new EnumCodeStyleOptionViewModel<UnusedParametersPreference>(
CodeStyleOptions2.UnusedParameters,
ServicesVSResources.Avoid_unused_parameters, enumValues,
examples, this, optionStore, title,
unusedParameterPreferences));
}
}
|