Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mhutch committed Jan 23, 2025
1 parent 58797f7 commit 2dbb2d4
Show file tree
Hide file tree
Showing 11 changed files with 368 additions and 27 deletions.
62 changes: 62 additions & 0 deletions Core/Options/EditorConfigCharSetSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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.

using System;
using System.Text;

namespace MonoDevelop.Xml.Options;

class EditorConfigCharSetSerializer : EditorConfigSerializerImpl<Encoding>
{
public override string Serialize (Encoding value)
{
if (TrySerialize (value, out var name) && name is not null) {
return name;
}
throw new InvalidOperationException ($"Encoding '{value}' cannot be serialized to editorconfig charset");
}

public override bool TryParse (string value, out Encoding? encoding)
{
switch (value) {
case "latin1": encoding = Latin1; return true;
case "utf-8": encoding = Utf8NoBom; return true;
case "utf-8-bom": encoding = Utf8WithBom; return true;
case "utf-16be": encoding = Utf16BigEndian; return true;
case "utf-16le": encoding = Utf16LittleEndian; return true;
default: encoding = null; return false;
}
}

static bool TrySerialize (Encoding encoding, out string? name)
{
switch(encoding.CodePage) {
case 28591:
name = "latin1";
return true;
case 65001:
name = encoding.GetPreamble().Length > 0? "utf-8-bom" : "utf-8";
return true;
case 1200:
name = "utf-16le";
return true;
case 1201:
name = "utf-16be";
return true;
default:
name = null;
return false;
}
}

readonly static Lazy<Encoding> latin1 = new(() => Encoding.GetEncoding (28591));
readonly static Lazy<Encoding> utf8NoBom = new(() => new UTF8Encoding (false));

public static Encoding Latin1 => latin1.Value;
public static Encoding Utf8NoBom => utf8NoBom.Value;
public static Encoding Utf8WithBom => Encoding.UTF8;

public static Encoding Utf16BigEndian => Encoding.BigEndianUnicode;
public static Encoding Utf16LittleEndian => Encoding.Unicode;
}
87 changes: 87 additions & 0 deletions Core/Options/EditorConfigSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Diagnostics.CodeAnalysis;

namespace MonoDevelop.Xml.Options;

static class EditorConfigSerializer
{
// TODO: use a TryParse func instead of deserialize (Roslyn implements this with a Func<string,Optional<T>>)
public static IEditorConfigSerializer<T> Create<T> (Func<string, T> deserialize, Func<T, string> serialize) => new EditorConfigFuncSerializer<T> (deserialize, serialize);

public static IEditorConfigSerializer<T> Default<T> () => typeof(T) switch {
Type t when t == typeof(string) => (IEditorConfigSerializer<T>)(object)stringSerializer,
Type t when t == typeof(int) => (IEditorConfigSerializer<T>)(object)intSerializer,
Type t when t == typeof(bool) => (IEditorConfigSerializer<T>)(object)boolSerializer,
Type t when t == typeof(bool?) => (IEditorConfigSerializer<T>)(object)nullableBoolSerializer,
_ => throw new NotSupportedException ($"No default serializer for type {typeof(T)}")
};

public static IEditorConfigSerializer<T> CreateForEnum<T> () where T : struct, Enum => new EditorConfigEnumSerializer<T> ();

static EditorConfigFuncSerializer<string> stringSerializer = new (s => s, s => s);
static EditorConfigIntSerializer intSerializer = new EditorConfigIntSerializer ();
static EditorConfigBoolSerializer boolSerializer = new ();
static EditorConfigNullableBoolSerializer nullableBoolSerializer = new ();

sealed class EditorConfigEnumSerializer<TEnum> : EditorConfigSerializerImpl<TEnum> where TEnum : struct, Enum
{
public override bool TryParse (string value, [MaybeNullWhen (false)] out TEnum parsedValue) => Enum.TryParse (value, ignoreCase: true, out parsedValue);
public override string Serialize (TEnum value) => value.ToString ();
}

sealed class EditorConfigIntSerializer : EditorConfigSerializerImpl<int>
{
public override bool TryParse (string value, [MaybeNullWhen (false)] out int parsedValue) => int.TryParse (value, out parsedValue);
public override string Serialize (int value) => value.ToString ();
}

sealed class EditorConfigBoolSerializer : EditorConfigSerializerImpl<bool>
{
public override bool TryParse (string value, [MaybeNullWhen (false)] out bool parsedValue) => bool.TryParse (value, out parsedValue);
public override string Serialize (bool value) => value? "true" : "false";
}

sealed class EditorConfigNullableBoolSerializer : EditorConfigSerializerImpl<bool?>
{
public override bool TryParse (string value, out bool? parsedValue)
{
if (string.Equals(value, "null", StringComparison.OrdinalIgnoreCase)) {
parsedValue = null;
return true;
}

if (bool.TryParse (value, out var parsedBool)) {
parsedValue = parsedBool;
return true;
}

parsedValue = null;
return false;
}

public override string Serialize (bool? value) => value switch {
true => "true",
false => "false",
_ => "null"
};
}

sealed class EditorConfigFuncSerializer<T> (Func<string, T> deserialize, Func<T, string> serialize) : EditorConfigSerializerImpl<T>
{
public override string Serialize (T value) => serialize (value);

public override bool TryParse (string value, [MaybeNullWhen (false)] out T? parsedValue)
{
try {
parsedValue = deserialize (value);
return true;
} catch {
parsedValue = default;
return false;
}
}
}
}
25 changes: 25 additions & 0 deletions Core/Options/EditorConfigSerializerImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;

namespace MonoDevelop.Xml.Options;

abstract class EditorConfigSerializerImpl<T> : IEditorConfigSerializer<T>
{
public abstract bool TryParse (string value, [MaybeNullWhen(false)] out T? parsedValue);
public abstract string Serialize (T value);

bool IEditorConfigSerializer.TryParse (string value, out object? parsedValue)
{
if (TryParse (value, out var parsedValueTyped)) {
parsedValue = parsedValueTyped;
return true;
}

parsedValue = null;
return false;
}

string IEditorConfigSerializer.Serialize (object value) => Serialize ((T)value);
}
10 changes: 10 additions & 0 deletions Core/Options/IEditorConfigSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace MonoDevelop.Xml.Options;

public interface IEditorConfigSerializer
{
bool TryParse (string value, out object? parsedValue);
string Serialize (object value);
}
31 changes: 31 additions & 0 deletions Core/Options/IEditorConfigSerializer`1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;

namespace MonoDevelop.Xml.Options;

public interface IEditorConfigSerializer<T> : IEditorConfigSerializer
{
bool TryParse (string value, [MaybeNullWhen(false)] out T? parsedValue);
string Serialize (T value);
}

abstract class EditorConfigSerializer<T> : IEditorConfigSerializer<T>
{
public abstract bool TryParse (string value, [MaybeNullWhen(false)] out T? parsedValue);
public abstract string Serialize (T value);

bool IEditorConfigSerializer.TryParse (string value, out object? parsedValue)
{
if (TryParse (value, out var parsedValueTyped)) {
parsedValue = parsedValueTyped;
return true;
}

parsedValue = null;
return false;
}

string IEditorConfigSerializer.Serialize (object value) => Serialize ((T)value);
}
72 changes: 72 additions & 0 deletions Core/Options/IGlobalOptionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// based on https://raw.githubusercontent.com/dotnet/roslyn/f20e4d3e49b4e2d0b615e57c64fac5c356b25079/src/Workspaces/Core/Portable/Options/IGlobalOptionService.cs
//
// 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.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace MonoDevelop.Xml.Options;

/// <summary>
/// Provides services for reading and writing global client (in-proc) options
/// shared across all workspaces.
/// </summary>
public interface IGlobalOptionService : IOptionsReader
{
/// <summary>
/// Gets the current value of the specific option.
/// </summary>
T GetOption<T>(Option<T> option);

/// <summary>
/// Gets the current values of specified options.
/// All options are read atomically.
/// </summary>
ImmutableArray<object?> GetOptions(ImmutableArray<IOption> optionKeys);

void SetGlobalOption<T>(Option<T> option, T value);

/// <summary>
/// Sets and persists the value of a global option.
/// Sets the value of a global option.
/// Invokes registered option persisters.
/// Triggers option changed event for handlers registered with <see cref="AddOptionChangedHandler"/>.
/// </summary>
void SetGlobalOption(IOption optionKey, object? value);

/// <summary>
/// Atomically sets the values of specified global options. The option values are persisted.
/// Triggers option changed event for handlers registered with <see cref="AddOptionChangedHandler"/>.
/// </summary>
/// <remarks>
/// Returns true if any option changed its value stored in the global options.
/// </remarks>
bool SetGlobalOptions(ImmutableArray<KeyValuePair<IOption, object?>> options);

/// <summary>
/// Refreshes the stored value of an option. This should only be called from persisters.
/// Does not persist the new option value.
/// </summary>
/// <remarks>
/// Returns true if the option changed its value stored in the global options.
/// </remarks>
bool RefreshOption(IOption optionKey, object? newValue);

void AddOptionChangedHandler(object target, WeakEventHandler<OptionChangedEventArgs> handler);

void RemoveOptionChangedHandler(object target, WeakEventHandler<OptionChangedEventArgs> handler);
}

public delegate void WeakEventHandler<TEventArgs>(object sender, object target, TEventArgs args);

public sealed class OptionChangedEventArgs(ImmutableArray<(IOption key, object? newValue)> changedOptions) : EventArgs
{
public ImmutableArray<(IOption key, object? newValue)> ChangedOptions => changedOptions;

public bool HasOption(Func<IOption, bool> predicate)
=> changedOptions.Any(option => predicate(option.key));
}
30 changes: 30 additions & 0 deletions Core/Options/IOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;

namespace MonoDevelop.Xml.Options;

public interface IOption
{
/// <summary>
/// A unique name for the option. If this is an editorconfig option, this will be used as the name
/// in .editorconfig.
/// </summary>
string Name { get; }

/// <summary>
/// Whether this option will be read from .editorconfig.
/// </summary>
bool IsEditorConfigOption { get; }

/// <summary>
/// Deserialize the option value to/from an editorconfig string
/// </summary>
IEditorConfigSerializer Serializer {get; }

/// <summary>
/// The type of the option value
/// </summary>
Type Type { get; }
}
32 changes: 13 additions & 19 deletions Core/Options/Option`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Diagnostics.CodeAnalysis;

namespace MonoDevelop.Xml.Options;

Expand All @@ -10,24 +11,17 @@ namespace MonoDevelop.Xml.Options;
/// Some of these are read from .editorconfig, and others may be mapped to equivalent settings
/// of the host IDE.
/// </summary>
public class Option<T>
public class Option<T> : IOption
{
public Option(string name, T defaultValue, bool isEditorConfigOption)
public Option(string name, T value, bool isEditorConfigOption, IEditorConfigSerializer<T>? serializer = null)
{
Name = name;
DefaultValue = defaultValue;
DefaultValue = value;
IsEditorConfigOption = isEditorConfigOption;
Serializer = serializer ?? EditorConfigSerializer.Default<T>();
}

public Option(string name, T value, EditorConfigSerializer<T>? serializer = null) : this(name, value, true)
{
Serializer = serializer;
}

/// <summary>
/// A unique name for the option. If this is an editorconfig option, this will be used as the name
/// in .editorconfig.
/// </summary>
/// <<inheritdoc/>
public string Name { get; }

/// <summary>
Expand All @@ -36,15 +30,15 @@ public Option(string name, T value, EditorConfigSerializer<T>? serializer = null
/// </summary>
public T DefaultValue { get; }

/// <summary>
/// Whether this option will be read from .editorconfig.
/// </summary>
/// <inheritdoc/>
public bool IsEditorConfigOption { get; }

/// <summary>
/// Optionally override the EditorConfig serialization behavior
/// Deserialize the option value to/from an editorconfig string
/// </summary>
public EditorConfigSerializer<T>? Serializer { get; }
}
public IEditorConfigSerializer<T> Serializer { get; }

public record EditorConfigSerializer<T> (Func<string, T> Deserialize, Func<T, string> Serialize);
public Type Type => typeof(T);

IEditorConfigSerializer IOption.Serializer => Serializer;
}
Loading

0 comments on commit 2dbb2d4

Please sign in to comment.