diff --git a/.gitmodules b/.gitmodules
index 7c5c005..4e48b39 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,3 +7,7 @@
path = fantomas-stable
url = https://github.com/fsprojects/fantomas.git
branch = master
+
+[submodule "fsharp-highlighting"]
+ path = config/highlighting
+ url = https://github.com/deviousasti/fsharp-highlighting.git
\ No newline at end of file
diff --git a/config/editor/App.config b/config/editor/App.config
new file mode 100644
index 0000000..4bfa005
--- /dev/null
+++ b/config/editor/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/config/editor/App.xaml b/config/editor/App.xaml
new file mode 100644
index 0000000..09b6459
--- /dev/null
+++ b/config/editor/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/config/editor/App.xaml.cs b/config/editor/App.xaml.cs
new file mode 100644
index 0000000..cfa0fda
--- /dev/null
+++ b/config/editor/App.xaml.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace editor
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ }
+}
diff --git a/config/editor/Editor.csproj b/config/editor/Editor.csproj
new file mode 100644
index 0000000..3759267
--- /dev/null
+++ b/config/editor/Editor.csproj
@@ -0,0 +1,29 @@
+
+
+ WinExe
+ FantomasVs.Editor
+ net48
+ true
+ editor
+ true
+ 9.0
+
+
+
+
+
+
+
+
+
+ FantomasLatest
+
+
+ FantomasStable
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/editor/Fakes.cs b/config/editor/Fakes.cs
new file mode 100644
index 0000000..2fd1e9b
--- /dev/null
+++ b/config/editor/Fakes.cs
@@ -0,0 +1,11 @@
+namespace Microsoft.VisualStudio.Shell
+{
+ public class DialogPage
+ {
+ }
+}
+
+namespace System.Runtime.CompilerServices
+{
+ internal static class IsExternalInit { }
+}
diff --git a/config/editor/FantomasOptionsEditor.xaml b/config/editor/FantomasOptionsEditor.xaml
new file mode 100644
index 0000000..549afd0
--- /dev/null
+++ b/config/editor/FantomasOptionsEditor.xaml
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/editor/FantomasOptionsEditor.xaml.cs b/config/editor/FantomasOptionsEditor.xaml.cs
new file mode 100644
index 0000000..403f48f
--- /dev/null
+++ b/config/editor/FantomasOptionsEditor.xaml.cs
@@ -0,0 +1,85 @@
+extern alias FantomasLatest;
+extern alias FantomasStable;
+
+using FSharp.Compiler.SourceCodeServices;
+using System;
+using System.Threading;
+using System.Windows.Controls;
+
+
+using StableCodeFormatter = FantomasStable::Fantomas.CodeFormatter;
+using LatestCodeFormatter = FantomasLatest::Fantomas.CodeFormatter;
+
+namespace FantomasVs.Editor
+{
+ ///
+ /// Interaction logic for FantomasOptionsPage2.xaml
+ ///
+ public partial class FantomasOptionsEditor : UserControl
+ {
+ public FantomasOptionsPage FantomasOptions { get; }
+
+ public EditorPage Editor { get; }
+
+ public FSharpChecker FSharpCheckerInstance { get; }
+
+ public string SampleText { get; set; } =
+ @"
+ let rec distribute e = function
+ | [] -> [[e]]
+ | x::xs as list ->
+ [e::list] @ [for xs in distribute e xs -> x::xs]
+ ";
+
+ #region Checker
+
+ private readonly Lazy _checker = new(() =>
+ FSharpChecker.Create(null, null, null, null, null, null, null, null, null)
+ );
+
+ protected FSharpChecker CheckerInstance => _checker.Value;
+
+ #endregion
+
+ public FantomasOptionsEditor()
+ {
+ InitializeComponent();
+ FantomasOptions = new FantomasOptionsPage();
+ Editor = new EditorPage(FantomasOptions);
+ Editor.PropertyEdited += OnPropertyEdited;
+ PropertyView.ItemsSource = Editor.View;
+ DataContext = Editor;
+ OnPropertyEdited(nameof(FantomasOptionsPage.CommitChanges));
+ }
+
+ CancellationTokenSource tokenSource;
+
+ private void OnPropertyEdited(string name)
+ {
+ var config = FantomasOptions.ToOptions(null);
+ var defaults = FSharpParsingOptions.Default;
+ var opts = new FSharpParsingOptions(
+ sourceFiles: new string[] { "sample.fsx" },
+ conditionalCompilationDefines: defaults.ConditionalCompilationDefines,
+ errorSeverityOptions: defaults.ErrorSeverityOptions,
+ isInteractive: defaults.IsInteractive,
+ lightSyntax: defaults.LightSyntax,
+ compilingFsLib: defaults.CompilingFsLib,
+ isExe: true // let's have this on for now
+ );
+
+
+ //var fsasync = LatestCodeFormatter.FormatDocumentAsync("sample.fsx", Fantomas.SourceOrigin.SourceOrigin.NewSourceString(SampleText), config, opts, FSharpCheckerInstance);
+ var fsasync = StableCodeFormatter.FormatDocumentAsync("sample.fsx", Fantomas.SourceOrigin.SourceOrigin.NewSourceString(SampleText), config, opts, FSharpCheckerInstance);
+
+ tokenSource?.Cancel();
+ tokenSource?.Dispose();
+
+ tokenSource = new();
+
+ var token = tokenSource.Token;
+ var task = Microsoft.FSharp.Control.FSharpAsync.StartAsTask(fsasync, null, token);
+ task.ContinueWith(t => Dispatcher.Invoke(() => SourcePreview.SourceText = t.Result), token);
+ }
+ }
+}
diff --git a/config/editor/FantomasOptionsPage.cs b/config/editor/FantomasOptionsPage.cs
new file mode 100644
index 0000000..5237b28
--- /dev/null
+++ b/config/editor/FantomasOptionsPage.cs
@@ -0,0 +1,319 @@
+using System;
+using System.ComponentModel;
+using Microsoft.VisualStudio.Shell;
+using System.Runtime.InteropServices;
+using Fantomas;
+using FantomasConfig = Fantomas.FormatConfig.FormatConfig;
+using System.Xml.Serialization;
+using EditorConfig.Core;
+
+namespace FantomasVs
+{
+ [Guid(GuidString)]
+ public class FantomasOptionsPage : DialogPage
+ {
+ public const string GuidString = "74927147-72e8-4b47-a70d-5568807d6878";
+
+ public static FormatConfig.FormatConfig Defaults => FormatConfig.FormatConfig.Default;
+
+ #region Indent
+
+ [Category("Indentation")]
+ [DisplayName("Indent Try-With")]
+ public bool IndentOnTryWith { get; set; } = Defaults.IndentOnTryWith;
+
+ [Category("Indentation")]
+ [DisplayName("Indent Number of Spaces")]
+ [Description("This will normally be set by the editor, this is the value to fallback to.")]
+ public int IndentSize { get; set; } = Defaults.IndentSize;
+
+ [Category("Indentation")]
+ [DisplayName("Keep Indent in Branch")]
+ public bool KeepIndentInBranch { get; set; } = Defaults.KeepIndentInBranch;
+
+ #endregion
+
+ #region Elmish
+
+ [Category("Elmish")]
+ [DisplayName("Disable for Elmish Syntax")]
+ public bool DisableElmishSyntax { get; set; } = Defaults.DisableElmishSyntax;
+
+ #endregion
+
+ #region Boundaries
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Array or List Width")]
+ public int MaxArrayOrListWidth { get; set; } = Defaults.MaxArrayOrListWidth;
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Width for Elmish Views")]
+ public int MaxElmishWidth { get; set; } = Defaults.MaxElmishWidth;
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Function Binding Width")]
+ public int MaxFunctionBindingWidth { get; set; } = Defaults.MaxFunctionBindingWidth;
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum If-Then-Else Width")]
+ public int MaxIfThenElseShortWidth { get; set; } = Defaults.MaxIfThenElseShortWidth;
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Infix Operator Expression")]
+ public int MaxInfixOperatorExpression { get; set; } = Defaults.MaxInfixOperatorExpression;
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Line Length")]
+ public int MaxLineLength { get; set; } = Defaults.MaxLineLength;
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Record Width")]
+ public int MaxRecordWidth { get; set; } = Defaults.MaxRecordWidth;
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Record Items")]
+ public int MaxRecordNumberOfItems { get; set; } = Defaults.MaxRecordNumberOfItems;
+
+ public enum MultilineFormatterType
+ {
+ CharacterWidth = 0,
+ NumberOfItems = 1
+ }
+
+ public static FormatConfig.MultilineFormatterType ConvertFormatterType(MultilineFormatterType type) =>
+ type switch
+ {
+ MultilineFormatterType.CharacterWidth => FormatConfig.MultilineFormatterType.CharacterWidth,
+ MultilineFormatterType.NumberOfItems => FormatConfig.MultilineFormatterType.NumberOfItems,
+ _ => FormatConfig.MultilineFormatterType.CharacterWidth
+ };
+
+ [Category("Boundaries")]
+ [DisplayName("Record Multi-line formatter")]
+ public MultilineFormatterType RecordMultilineFormatterConfig { get; set; } = MultilineFormatterType.CharacterWidth;
+
+ [Category("Boundaries")]
+ [DisplayName("Array/List Multi-line formatter")]
+ public MultilineFormatterType ArrayOrListMultilineFormatterConfig { get; set; } = MultilineFormatterType.CharacterWidth;
+
+ internal FormatConfig.MultilineFormatterType RecordMultilineFormatter => ConvertFormatterType(RecordMultilineFormatterConfig);
+ internal FormatConfig.MultilineFormatterType ArrayOrListMultilineFormatter => ConvertFormatterType(ArrayOrListMultilineFormatterConfig);
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Value Binding Width")]
+ public int MaxValueBindingWidth { get; set; } = Defaults.MaxValueBindingWidth;
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Array/List Number Of Items")]
+ public int MaxArrayOrListNumberOfItems { get; set; } = Defaults.MaxArrayOrListNumberOfItems;
+
+ [Category("Boundaries")]
+ [DisplayName("MultiLine Lambda Closing Newline")]
+ public bool MultiLineLambdaClosingNewline { get; set; } = Defaults.MultiLineLambdaClosingNewline;
+
+ [Category("Boundaries")]
+ [DisplayName("Blank Lines Around Nested Multiline Expressions")]
+ public bool BlankLinesAroundNestedMultilineExpressions { get; set; } = Defaults.BlankLinesAroundNestedMultilineExpressions;
+
+ [Category("Boundaries")]
+ [DisplayName("Multiline Block Brackets On Same Column")]
+ public bool MultilineBlockBracketsOnSameColumn { get; set; } = Defaults.MultilineBlockBracketsOnSameColumn;
+
+ public enum LineEndingStyle
+ {
+ CR = FormatConfig.EndOfLineStyle.Tags.CR,
+ LF = FormatConfig.EndOfLineStyle.Tags.LF,
+ CRLF = FormatConfig.EndOfLineStyle.Tags.CRLF,
+ }
+
+ [Category("Boundaries")]
+ [DisplayName("End Of Line Style")]
+ public LineEndingStyle EndOfLineStyle { get; set; } = LineEndingStyle.CRLF;
+
+ public FormatConfig.EndOfLineStyle EndOfLine
+ => this.EndOfLineStyle switch
+ {
+ LineEndingStyle.CR => FormatConfig.EndOfLineStyle.CR,
+ LineEndingStyle.CRLF => FormatConfig.EndOfLineStyle.CRLF,
+ LineEndingStyle.LF => FormatConfig.EndOfLineStyle.LF,
+ _ => FormatConfig.EndOfLineStyle.FromEnvironment
+ };
+
+ [Category("Boundaries")]
+ [DisplayName("Maximum Dot Get Expression Width")]
+ public int MaxDotGetExpressionWidth { get; set; } = Defaults.MaxDotGetExpressionWidth;
+
+ #endregion
+
+ #region Convention
+
+ [Category("Convention")]
+ [DisplayName("Semicolon at End of Line")]
+ public bool SemicolonAtEndOfLine { get; set; } = Defaults.SemicolonAtEndOfLine;
+
+ [Category("Convention")]
+ [DisplayName("Strict Mode")]
+ [Description("Pretty printing the ASTs only (strips trivia like comments)")]
+ public bool StrictMode { get; set; } = Defaults.StrictMode;
+
+ [Category("Convention")]
+ [DisplayName("Keep Newline After")]
+ public bool KeepIfThenInSameLine { get; set; } = Defaults.KeepIfThenInSameLine;
+
+
+ [Category("Convention")]
+ [DisplayName("Newline Between Type Definition And Members")]
+ public bool NewlineBetweenTypeDefinitionAndMembers { get; set; } = Defaults.NewlineBetweenTypeDefinitionAndMembers;
+
+
+ [Category("Convention")]
+ [DisplayName("Single-Argument Web Mode")]
+ public bool SingleArgumentWebMode { get; set; } = Defaults.SingleArgumentWebMode;
+
+ [Category("Convention")]
+ [DisplayName("Align Function Signature To Indentation")]
+ public bool AlignFunctionSignatureToIndentation { get; set; } = Defaults.AlignFunctionSignatureToIndentation;
+
+ [Category("Convention")]
+ [DisplayName("Alternative Long Member Definitions")]
+ public bool AlternativeLongMemberDefinitions { get; set; } = Defaults.AlternativeLongMemberDefinitions;
+
+ #endregion
+
+ #region Spacing
+
+ [Category("Spacing")]
+ [DisplayName("Before Parameter")]
+ public bool SpaceBeforeParameter { get; set; } = Defaults.SpaceBeforeParameter;
+
+ [Category("Spacing")]
+ [DisplayName("Before Colon")]
+ public bool SpaceBeforeColon { get; set; } = Defaults.SpaceBeforeColon;
+
+ [Category("Spacing")]
+ [DisplayName("After Comma")]
+ public bool SpaceAfterComma { get; set; } = Defaults.SpaceAfterComma;
+
+ [Category("Spacing")]
+ [DisplayName("After Semicolon")]
+ public bool SpaceAfterSemicolon { get; set; } = Defaults.SpaceAfterSemicolon;
+
+ [Category("Spacing")]
+ [DisplayName("Before Semicolon")]
+ public bool SpaceBeforeSemicolon { get; set; } = Defaults.SpaceBeforeSemicolon;
+
+ [Category("Spacing")]
+ [DisplayName("Around Delimiter")]
+ public bool SpaceAroundDelimiter { get; set; } = Defaults.SpaceAroundDelimiter;
+
+
+ [Category("Spacing")]
+ [DisplayName("Before Class Constructor")]
+ public bool SpaceBeforeClassConstructor { get; set; } = Defaults.SpaceBeforeClassConstructor;
+
+ [Category("Spacing")]
+ [DisplayName("Before Lowercase Invocation")]
+ public bool SpaceBeforeLowercaseInvocation { get; set; } = Defaults.SpaceBeforeLowercaseInvocation;
+
+
+ [Category("Spacing")]
+ [DisplayName("Before Uppercase Invocation")]
+ public bool SpaceBeforeUppercaseInvocation { get; set; } = Defaults.SpaceBeforeUppercaseInvocation;
+
+ [Category("Spacing")]
+ [DisplayName("Before Member")]
+ public bool SpaceBeforeMember { get; set; } = Defaults.SpaceBeforeMember;
+
+ #endregion
+
+ #region Ordering
+
+
+ #endregion
+
+ #region Performance
+
+ [Category("Performance")]
+ [DisplayName("Apply As Diff")]
+ [Description("Applies the formatting as changes, which shows which lines were changed. Turn off if computing the diff is too slow. ")]
+ public bool ApplyDiff { get; set; } = true;
+
+ [Category("Performance")]
+ [DisplayName("Warmup On Start")]
+ [Description("Runs through formatting code on startup to warm up the Jit. Reduces delay when first using it.")]
+ public bool WarmUpOnStartup { get; set; } = false;
+
+ [Category("Performance")]
+ [DisplayName("Enable SpaceBar Heating")]
+ [Description("xkcd/1172")]
+ public bool EnableSpaceBarHeating { get; set; } = false;
+
+ #endregion
+
+ #region On Save
+
+ [Category("On Save")]
+ [DisplayName("Format On Save")]
+ [Description("This triggers a formatting whenever you hit save")]
+ public bool FormatOnSave { get; set; } = false;
+
+ [Category("On Save")]
+ [DisplayName("Commit Changes")]
+ [Description("Set this to false if you don't want to commit formatting changes to the file unless you hit save once again")]
+ public bool CommitChanges { get; set; } = true;
+
+ #endregion
+
+ public FantomasConfig ToOptions(int? spaces)
+ {
+ var config = new FantomasConfig(
+ indentSize: spaces ?? this.IndentSize,
+ indentOnTryWith: this.IndentOnTryWith,
+
+ disableElmishSyntax: this.DisableElmishSyntax,
+ maxArrayOrListWidth: this.MaxArrayOrListWidth,
+ maxElmishWidth: this.MaxElmishWidth,
+ maxFunctionBindingWidth: this.MaxFunctionBindingWidth,
+ maxValueBindingWidth: this.MaxValueBindingWidth,
+ maxIfThenElseShortWidth: this.MaxIfThenElseShortWidth,
+ maxInfixOperatorExpression: this.MaxInfixOperatorExpression,
+ maxLineLength: this.MaxLineLength,
+ maxRecordWidth: this.MaxRecordWidth,
+ maxRecordNumberOfItems: this.MaxRecordNumberOfItems,
+ multilineBlockBracketsOnSameColumn: this.MultilineBlockBracketsOnSameColumn,
+ recordMultilineFormatter: this.RecordMultilineFormatter,
+ arrayOrListMultilineFormatter: this.ArrayOrListMultilineFormatter,
+ maxArrayOrListNumberOfItems: this.MaxArrayOrListNumberOfItems,
+ maxDotGetExpressionWidth: this.MaxDotGetExpressionWidth,
+ keepIfThenInSameLine: this.KeepIfThenInSameLine,
+ singleArgumentWebMode: this.SingleArgumentWebMode,
+ alignFunctionSignatureToIndentation: this.AlignFunctionSignatureToIndentation,
+ alternativeLongMemberDefinitions: this.AlternativeLongMemberDefinitions,
+ multiLineLambdaClosingNewline: this.MultiLineLambdaClosingNewline,
+ endOfLine: this.EndOfLine,
+
+ semicolonAtEndOfLine: this.SemicolonAtEndOfLine,
+
+ spaceBeforeClassConstructor: this.SpaceBeforeClassConstructor,
+ spaceBeforeLowercaseInvocation: this.SpaceBeforeLowercaseInvocation,
+ spaceBeforeUppercaseInvocation: this.SpaceBeforeUppercaseInvocation,
+ spaceBeforeMember: this.SpaceBeforeMember,
+ spaceBeforeParameter: this.SpaceBeforeParameter,
+ spaceBeforeColon: this.SpaceBeforeColon,
+ spaceAfterComma: this.SpaceAfterComma,
+ spaceAfterSemicolon: this.SpaceAfterSemicolon,
+ spaceBeforeSemicolon: this.SpaceBeforeSemicolon,
+ spaceAroundDelimiter: this.SpaceAroundDelimiter,
+
+ newlineBetweenTypeDefinitionAndMembers: this.NewlineBetweenTypeDefinitionAndMembers,
+ keepIndentInBranch: this.KeepIndentInBranch,
+ blankLinesAroundNestedMultilineExpressions: this.BlankLinesAroundNestedMultilineExpressions,
+
+ strictMode: this.StrictMode
+ );
+
+ return config;
+ }
+ }
+}
diff --git a/config/editor/MainWindow.xaml b/config/editor/MainWindow.xaml
new file mode 100644
index 0000000..9cfe9ff
--- /dev/null
+++ b/config/editor/MainWindow.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/config/editor/MainWindow.xaml.cs b/config/editor/MainWindow.xaml.cs
new file mode 100644
index 0000000..951990f
--- /dev/null
+++ b/config/editor/MainWindow.xaml.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace editor
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ public MainWindow()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/config/editor/PropertyEditors.cs b/config/editor/PropertyEditors.cs
new file mode 100644
index 0000000..6d150d4
--- /dev/null
+++ b/config/editor/PropertyEditors.cs
@@ -0,0 +1,218 @@
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+
+namespace FantomasVs.Editor
+{
+
+ public interface IPropertyEditor : INotifyPropertyChanged, IDataErrorInfo
+ {
+ string DisplayName { get; set; }
+ string Description { get; set; }
+ string Category { get; set; }
+ string CustomEditorTemplate { get; set; }
+ object BoxedValue { get; set; }
+ PropertyInfo SourceProperty { get; set; }
+ object Target { get; set; }
+
+ event EventHandler ValueChanged;
+ }
+
+ public class PropertyEditor : IPropertyEditor
+ {
+ private string _displayName;
+ private string _description;
+ private string _category;
+
+ public PropertyInfo SourceProperty { get; set; }
+
+ public object Target { get; set; }
+
+ public string DisplayName
+ {
+ get => _displayName;
+ set { _displayName = value; OnPropertyChanged(); }
+ }
+
+ public string Description
+ {
+ get => _description;
+ set { _description = value; OnPropertyChanged(); }
+ }
+
+ public string Category
+ {
+ get => _category;
+ set { _category = value; OnPropertyChanged(); }
+ }
+
+ public TValue Value
+ {
+ get => (TValue)BoxedValue;
+ set { BoxedValue = value; OnPropertyChanged(); }
+ }
+
+ public event EventHandler ValueChanged;
+
+ public object BoxedValue
+ {
+ get => SourceProperty.GetValue(Target);
+ set
+ {
+ SourceProperty.SetValue(Target, value);
+ ValueChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ public string ErrorMessage { get; set; } = "This value is invalid";
+
+ public string CustomEditorTemplate { get; set; } = null;
+
+ public virtual Predicate Validate { get; set; } = _ => true;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected void OnPropertyChanged([CallerMemberName] string name = default) =>
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+
+ string IDataErrorInfo.Error => default;
+
+
+ string IDataErrorInfo.this[string columnName] =>
+ columnName == nameof(Value) ?
+ (Validate(Value) ? ErrorMessage : default)
+ : default;
+
+ public record NamedValue(string Label, TValue Value);
+
+ public static string Humanize(TValue input) =>
+ System.Text.RegularExpressions.Regex.Replace(
+ input.ToString(), "(?<=[a-z])(?[A-Z])|(?<=.)(?[A-Z])(?=[a-z])", " ${x}");
+
+ public NamedValue[] Values =>
+ typeof(TValue).IsEnum ?
+ Enum.GetValues(typeof(TValue))
+ .Cast()
+ .Select(v => new NamedValue(Humanize(v), v))
+ .ToArray()
+ :
+ new NamedValue[] { };
+ }
+
+ public class EditorTemplateSelector : DataTemplateSelector
+ {
+ public override DataTemplate SelectTemplate(object item, DependencyObject container)
+ {
+ if (container is FrameworkElement element && item is IPropertyEditor editor)
+ {
+ var propertyType = editor.SourceProperty.PropertyType;
+ var typeName = propertyType.IsEnum ? nameof(Enum) : editor.SourceProperty.PropertyType.Name;
+ var templateName = editor.CustomEditorTemplate ?? $"{typeName}Template";
+ var dataTemplate = element.TryFindResource(templateName) as DataTemplate;
+ return dataTemplate;
+ }
+
+ return null;
+ }
+ }
+
+ public class EditorPage : INotifyPropertyChanged where TOpt : class
+ {
+
+ public TOpt Target { get; }
+
+ public ObservableCollection Editors { get; }
+
+ public event Action PropertyEdited;
+
+ public CollectionView View { get; }
+
+ private string searchText = "";
+ public string SearchText
+ {
+ get => searchText;
+ set
+ {
+ searchText = value;
+ OnPropertyChanged();
+ Refresh();
+ }
+ }
+
+ protected virtual void Refresh()
+ {
+ View.Refresh();
+ }
+
+ public EditorPage(TOpt target)
+ {
+ Target = target;
+
+ var properties = typeof(TOpt).GetProperties(BindingFlags.Public | BindingFlags.Instance);
+ Editors = new ObservableCollection(properties.Where(CanCreateEditor).Select(CreateEditor));
+
+ foreach (var editor in Editors)
+ {
+ editor.ValueChanged += OnValueChanged;
+ }
+
+ View = (CollectionView)CollectionViewSource.GetDefaultView(Editors);
+ View.GroupDescriptions.Add(new PropertyGroupDescription(nameof(IPropertyEditor.Category)));
+ View.Filter = IsMatching;
+ }
+
+ private void OnValueChanged(object sender, EventArgs e)
+ {
+ var editor = (IPropertyEditor)sender;
+ PropertyEdited?.Invoke(editor.SourceProperty.Name);
+ }
+
+ protected virtual bool IsMatching(object obj)
+ {
+ bool TextMatch(string field) =>
+ String.IsNullOrEmpty(field) ||
+ String.IsNullOrEmpty(SearchText) ||
+ field.IndexOf(SearchText, StringComparison.OrdinalIgnoreCase) >= 0;
+
+ if (obj is IPropertyEditor editor)
+ {
+ return
+ TextMatch(editor.DisplayName) ||
+ TextMatch(editor.Description) ||
+ TextMatch(editor.Category);
+ }
+
+ return false;
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ protected void OnPropertyChanged([CallerMemberName] string name = default) =>
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+
+ private bool CanCreateEditor(PropertyInfo info) =>
+ info.CanRead && info.CanWrite;
+
+ private IPropertyEditor CreateEditor(PropertyInfo info)
+ {
+ var propType = info.PropertyType;
+ var editorType = typeof(PropertyEditor<>).MakeGenericType(propType);
+ var editor = (IPropertyEditor)Activator.CreateInstance(editorType);
+
+ editor.DisplayName = info.GetCustomAttribute()?.DisplayName ?? info.Name;
+ editor.Category = info.GetCustomAttribute()?.Category ?? "General";
+ editor.Description = info.GetCustomAttribute()?.Description ?? "TBD";
+ editor.SourceProperty = info;
+ editor.Target = Target;
+ editor.CustomEditorTemplate = info.GetCustomAttribute()?.EditorTypeName;
+
+ return editor;
+ }
+ }
+
+}
diff --git a/config/editor/SpinControl.cs b/config/editor/SpinControl.cs
new file mode 100644
index 0000000..657b7af
--- /dev/null
+++ b/config/editor/SpinControl.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Documents;
+
+namespace FantomasVs.Editor
+{
+ [TemplatePart(Name = "PART_UpButton", Type = typeof(RepeatButton))]
+ [TemplatePart(Name = "PART_DownButton", Type = typeof(RepeatButton))]
+ [TemplatePart(Name = "PART_Text", Type = typeof(TextBox))]
+ public class SpinControl : Control
+ {
+ public int Value
+ {
+ get { return (int)GetValue(ValueProperty); }
+ set { SetValue(ValueProperty, value); }
+ }
+
+ private RepeatButton UpButtonElement;
+ private RepeatButton DownButtonElement;
+
+ public static readonly DependencyProperty ValueProperty =
+ DependencyProperty.Register("Value", typeof(int), typeof(SpinControl), new PropertyMetadata());
+
+ public override void OnApplyTemplate()
+ {
+ if (UpButtonElement != null) UpButtonElement.Click -= OnUpClick;
+ if (DownButtonElement != null) DownButtonElement.Click -= OnDownClick;
+
+ UpButtonElement = GetTemplateChild("PART_UpButton") as RepeatButton;
+ DownButtonElement = GetTemplateChild("PART_DownButton") as RepeatButton;
+
+ if (UpButtonElement != null) UpButtonElement.Click += OnUpClick;
+ if (DownButtonElement != null) DownButtonElement.Click += OnDownClick;
+ }
+
+ private void OnUpClick(object _, object e) => Value += 1;
+ private void OnDownClick(object _, object e) => Value -= 1;
+
+ }
+}
diff --git a/config/highlighting b/config/highlighting
new file mode 160000
index 0000000..befcfd0
--- /dev/null
+++ b/config/highlighting
@@ -0,0 +1 @@
+Subproject commit befcfd0904890b498739cfb911b0d9fadfaf7a86
diff --git a/src/FantomasHandler.cs b/src/FantomasHandler.cs
index dbd1250..d2a7445 100644
--- a/src/FantomasHandler.cs
+++ b/src/FantomasHandler.cs
@@ -66,54 +66,7 @@ protected FormatConfig.FormatConfig GetOptions(EditorCommandArgs args, FantomasO
{
var localOptions = args.TextView.Options;
var indentSpaces = localOptions?.GetIndentSize();
-
- var config = new FormatConfig.FormatConfig(
- indentSize: indentSpaces ?? fantopts.IndentSize,
- indentOnTryWith: fantopts.IndentOnTryWith,
- keepIndentInBranch: fantopts.KeepIndentInBranch,
-
- disableElmishSyntax: fantopts.DisableElmishSyntax,
- maxArrayOrListWidth: fantopts.MaxArrayOrListWidth,
- maxElmishWidth: fantopts.MaxElmishWidth,
- maxFunctionBindingWidth: fantopts.MaxFunctionBindingWidth,
- maxValueBindingWidth: fantopts.MaxValueBindingWidth,
- maxIfThenElseShortWidth: fantopts.MaxIfThenElseShortWidth,
- maxInfixOperatorExpression: fantopts.MaxInfixOperatorExpression,
- maxLineLength: fantopts.MaxLineLength,
- maxRecordWidth: fantopts.MaxRecordWidth,
- maxRecordNumberOfItems: fantopts.MaxRecordNumberOfItems,
- multilineBlockBracketsOnSameColumn: fantopts.MultilineBlockBracketsOnSameColumn,
- recordMultilineFormatter: fantopts.RecordMultilineFormatter,
- arrayOrListMultilineFormatter: fantopts.ArrayOrListMultilineFormatter,
- maxArrayOrListNumberOfItems: fantopts.MaxArrayOrListNumberOfItems,
- maxDotGetExpressionWidth: fantopts.MaxDotGetExpressionWidth,
- keepIfThenInSameLine: fantopts.KeepIfThenInSameLine,
- singleArgumentWebMode: fantopts.SingleArgumentWebMode,
- alignFunctionSignatureToIndentation: fantopts.AlignFunctionSignatureToIndentation,
- alternativeLongMemberDefinitions: fantopts.AlternativeLongMemberDefinitions,
- multiLineLambdaClosingNewline: fantopts.MultiLineLambdaClosingNewline,
- endOfLine: fantopts.EndOfLine,
- blankLinesAroundNestedMultilineExpressions: fantopts.BlankLinesAroundNestedMultilineExpressions,
-
- semicolonAtEndOfLine: fantopts.SemicolonAtEndOfLine,
-
- spaceBeforeClassConstructor: fantopts.SpaceBeforeClassConstructor,
- spaceBeforeLowercaseInvocation: fantopts.SpaceBeforeLowercaseInvocation,
- spaceBeforeUppercaseInvocation: fantopts.SpaceBeforeUppercaseInvocation,
- spaceBeforeMember: fantopts.SpaceBeforeMember,
- spaceBeforeParameter: fantopts.SpaceBeforeParameter,
- spaceBeforeColon: fantopts.SpaceBeforeColon,
- spaceAfterComma: fantopts.SpaceAfterComma,
- spaceAfterSemicolon: fantopts.SpaceAfterSemicolon,
- spaceBeforeSemicolon: fantopts.SpaceBeforeSemicolon,
- spaceAroundDelimiter: fantopts.SpaceAroundDelimiter,
-
- newlineBetweenTypeDefinitionAndMembers: fantopts.NewlineBetweenTypeDefinitionAndMembers,
-
- strictMode: fantopts.StrictMode
- );
-
- return config;
+ return fantopts.ToOptions(indentSpaces);
}
diff --git a/src/FantomasOptionsDialogPage.cs b/src/FantomasOptionsDialogPage.cs
new file mode 100644
index 0000000..84387b9
--- /dev/null
+++ b/src/FantomasOptionsDialogPage.cs
@@ -0,0 +1,12 @@
+namespace FantomasVs
+{
+ using Microsoft.VisualStudio.Shell;
+ using System.Windows;
+ using System.Windows.Controls;
+
+ public class FantomasOptionsDialogPage : UIElementDialogPage
+ {
+ //private FantomasOptionsPage2 _page;
+ protected override UIElement Child => new Label { Content = "Under construction" };
+ }
+}
diff --git a/src/FantomasOptionsPage.cs b/src/FantomasOptionsPage.cs
index 3d13b8b..fbbdbc5 100644
--- a/src/FantomasOptionsPage.cs
+++ b/src/FantomasOptionsPage.cs
@@ -5,8 +5,10 @@
using System.ComponentModel;
using Microsoft.VisualStudio.Shell;
using System.Runtime.InteropServices;
-using FormatConfig = Fantomas.FormatConfig;
-
+using Fantomas;
+using FantomasConfig = Fantomas.FormatConfig.FormatConfig;
+using System.Xml.Serialization;
+using EditorConfig.Core;
namespace FantomasVs
{
@@ -16,7 +18,7 @@ public class FantomasOptionsPage : DialogPage
public const string GuidString = "74927147-72e8-4b47-a70d-5568807d6878";
public static FormatConfig.FormatConfig Defaults => FormatConfig.FormatConfig.Default;
-
+
#region Indent
[Category("Indentation")]
@@ -40,7 +42,7 @@ public class FantomasOptionsPage : DialogPage
[Category("Elmish")]
[DisplayName("Disable for Elmish Syntax")]
- public bool DisableElmishSyntax { get; set; } = Defaults.DisableElmishSyntax;
+ public bool DisableElmishSyntax { get; set; } = Defaults.DisableElmishSyntax;
#endregion
@@ -113,7 +115,7 @@ public static FormatConfig.MultilineFormatterType ConvertFormatterType(Multiline
[Category("Boundaries")]
[DisplayName("MultiLine Lambda Closing Newline")]
- public bool MultiLineLambdaClosingNewline { get; set; } = Defaults.MultiLineLambdaClosingNewline;
+ public bool MultiLineLambdaClosingNewline { get; set; } = Defaults.MultiLineLambdaClosingNewline;
[Category("Boundaries")]
[DisplayName("Blank Lines Around Nested Multiline Expressions")]
@@ -282,5 +284,57 @@ public enum Version
public Version BuildVersion { get; set; } = Version.Stable;
#endregion
+
+ public FantomasConfig ToOptions(int? spaces)
+ {
+
+ var config = new FantomasConfig(
+ indentSize: spaces ?? this.IndentSize,
+ indentOnTryWith: this.IndentOnTryWith,
+
+ disableElmishSyntax: this.DisableElmishSyntax,
+ maxArrayOrListWidth: this.MaxArrayOrListWidth,
+ maxElmishWidth: this.MaxElmishWidth,
+ maxFunctionBindingWidth: this.MaxFunctionBindingWidth,
+ maxValueBindingWidth: this.MaxValueBindingWidth,
+ maxIfThenElseShortWidth: this.MaxIfThenElseShortWidth,
+ maxInfixOperatorExpression: this.MaxInfixOperatorExpression,
+ maxLineLength: this.MaxLineLength,
+ maxRecordWidth: this.MaxRecordWidth,
+ maxRecordNumberOfItems: this.MaxRecordNumberOfItems,
+ multilineBlockBracketsOnSameColumn: this.MultilineBlockBracketsOnSameColumn,
+ recordMultilineFormatter: this.RecordMultilineFormatter,
+ arrayOrListMultilineFormatter: this.ArrayOrListMultilineFormatter,
+ maxArrayOrListNumberOfItems: this.MaxArrayOrListNumberOfItems,
+ maxDotGetExpressionWidth: this.MaxDotGetExpressionWidth,
+ keepIfThenInSameLine: this.KeepIfThenInSameLine,
+ singleArgumentWebMode: this.SingleArgumentWebMode,
+ alignFunctionSignatureToIndentation: this.AlignFunctionSignatureToIndentation,
+ alternativeLongMemberDefinitions: this.AlternativeLongMemberDefinitions,
+ multiLineLambdaClosingNewline: this.MultiLineLambdaClosingNewline,
+ endOfLine: this.EndOfLine,
+
+ semicolonAtEndOfLine: this.SemicolonAtEndOfLine,
+
+ spaceBeforeClassConstructor: this.SpaceBeforeClassConstructor,
+ spaceBeforeLowercaseInvocation: this.SpaceBeforeLowercaseInvocation,
+ spaceBeforeUppercaseInvocation: this.SpaceBeforeUppercaseInvocation,
+ spaceBeforeMember: this.SpaceBeforeMember,
+ spaceBeforeParameter: this.SpaceBeforeParameter,
+ spaceBeforeColon: this.SpaceBeforeColon,
+ spaceAfterComma: this.SpaceAfterComma,
+ spaceAfterSemicolon: this.SpaceAfterSemicolon,
+ spaceBeforeSemicolon: this.SpaceBeforeSemicolon,
+ spaceAroundDelimiter: this.SpaceAroundDelimiter,
+
+ newlineBetweenTypeDefinitionAndMembers: this.NewlineBetweenTypeDefinitionAndMembers,
+ keepIndentInBranch: this.KeepIndentInBranch,
+ blankLinesAroundNestedMultilineExpressions: this.BlankLinesAroundNestedMultilineExpressions,
+
+ strictMode: this.StrictMode
+ );
+
+ return config;
+ }
}
}
diff --git a/src/FantomasVs.csproj b/src/FantomasVs.csproj
index cc4994d..cead8bd 100644
--- a/src/FantomasVs.csproj
+++ b/src/FantomasVs.csproj
@@ -50,6 +50,9 @@
+
+ Component
+
Component
diff --git a/src/FantomasVs.sln b/src/FantomasVs.sln
index b83c781..acc1006 100644
--- a/src/FantomasVs.sln
+++ b/src/FantomasVs.sln
@@ -11,6 +11,10 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FantomasLatest", "FantomasL
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FantomasCommon", "FantomasCommon\FantomasCommon.fsproj", "{647E5969-F130-4400-ADF6-232B5BB22F32}"
EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Highlighting", "..\config\highlighting\src\FSharp.Highlighting.fsproj", "{75A6C266-2323-4EE4-9F17-FA04BF580656}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Editor", "..\config\editor\Editor.csproj", "{552B9907-F265-4BEE-BAFC-4D4FC5663D24}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +37,18 @@ Global
{647E5969-F130-4400-ADF6-232B5BB22F32}.Debug|Any CPU.Build.0 = Debug|Any CPU
{647E5969-F130-4400-ADF6-232B5BB22F32}.Release|Any CPU.ActiveCfg = Release|Any CPU
{647E5969-F130-4400-ADF6-232B5BB22F32}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A96FC3FB-AD45-4420-878D-78E63E3FB137}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A96FC3FB-AD45-4420-878D-78E63E3FB137}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A96FC3FB-AD45-4420-878D-78E63E3FB137}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A96FC3FB-AD45-4420-878D-78E63E3FB137}.Release|Any CPU.Build.0 = Release|Any CPU
+ {75A6C266-2323-4EE4-9F17-FA04BF580656}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {75A6C266-2323-4EE4-9F17-FA04BF580656}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {75A6C266-2323-4EE4-9F17-FA04BF580656}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {75A6C266-2323-4EE4-9F17-FA04BF580656}.Release|Any CPU.Build.0 = Release|Any CPU
+ {552B9907-F265-4BEE-BAFC-4D4FC5663D24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {552B9907-F265-4BEE-BAFC-4D4FC5663D24}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {552B9907-F265-4BEE-BAFC-4D4FC5663D24}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {552B9907-F265-4BEE-BAFC-4D4FC5663D24}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/FantomasVsPackage.cs b/src/FantomasVsPackage.cs
index 7207376..902d38a 100644
--- a/src/FantomasVsPackage.cs
+++ b/src/FantomasVsPackage.cs
@@ -11,7 +11,7 @@
namespace FantomasVs
{
- // DO NOT REMOVE THIS MAGICAL INCANTATION NO MATTER HOW MUCH VS WARNS YOU OF DEPRECATION
+ // DO NOT REMOVE THIS MAGICAL INCANTATION NO MATTER HOW MUCH VS WARNS YOU OF DEPRECATION
// --------------------------------------------------------------------------------------
[InstalledProductRegistration("F# Formatting", "F# source code formatting using Fantomas.", "0.9", IconResourceID = 400)]
// --------------------------------------------------------------------------------------
@@ -25,6 +25,7 @@ namespace FantomasVs
// Options page
[ProvideOptionPage(typeof(FantomasOptionsPage), "F# Tools", "Formatting", 0, 0, supportsAutomation: true)]
+ [ProvideOptionPage(typeof(FantomasOptionsDialogPage), "F# Tools", "Formatting 2", 0, 0, supportsAutomation: true)]
public sealed partial class FantomasVsPackage : AsyncPackage
{
@@ -37,6 +38,7 @@ public sealed partial class FantomasVsPackage : AsyncPackage
public static Task Instance => _instance.Task;
public FantomasOptionsPage Options => GetDialogPage(typeof(FantomasOptionsPage)) as FantomasOptionsPage ?? new FantomasOptionsPage();
+ public FantomasOptionsDialogPage Options2 => GetDialogPage(typeof(FantomasOptionsDialogPage)) as FantomasOptionsDialogPage ?? new FantomasOptionsDialogPage();
public IComponentModel MefHost { get; private set; }
@@ -52,12 +54,12 @@ public sealed partial class FantomasVsPackage : AsyncPackage
/// A provider for progress updates.
/// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress)
- {
+ {
Trace.WriteLine("Fantomas Vs Package Loaded");
MefHost = await this.GetServiceAsync();
Statusbar = await this.GetServiceAsync();
-
+
// signal that package is ready
_instance.SetResult(this);