From 92a5cabc2491740c485ff352c7e6fa056c1835e8 Mon Sep 17 00:00:00 2001
From: rferraton <16419423+rferraton@users.noreply.github.com>
Date: Fri, 17 Apr 2026 23:26:16 +0200
Subject: [PATCH 01/11] Query Editor SQL Formater with parameters options -
init working
---
.../Controls/QuerySessionControl.axaml | 10 ++
.../Controls/QuerySessionControl.axaml.cs | 25 ++++
.../Dialogs/FormatOptionsWindow.axaml | 41 ++++++
.../Dialogs/FormatOptionsWindow.axaml.cs | 114 ++++++++++++++++
src/PlanViewer.App/PlanViewer.App.csproj | 1 +
.../Services/SqlFormatSettingsService.cs | 129 ++++++++++++++++++
.../Services/SqlFormattingService.cs | 71 ++++++++++
7 files changed, 391 insertions(+)
create mode 100644 src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml
create mode 100644 src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
create mode 100644 src/PlanViewer.App/Services/SqlFormatSettingsService.cs
create mode 100644 src/PlanViewer.App/Services/SqlFormattingService.cs
diff --git a/src/PlanViewer.App/Controls/QuerySessionControl.axaml b/src/PlanViewer.App/Controls/QuerySessionControl.axaml
index 9d44cc6..fcafef1 100644
--- a/src/PlanViewer.App/Controls/QuerySessionControl.axaml
+++ b/src/PlanViewer.App/Controls/QuerySessionControl.axaml
@@ -69,6 +69,16 @@
Height="28" Padding="10,0" FontSize="12" IsEnabled="False"
Theme="{StaticResource AppButton}"
ToolTip.Tip="Execute the repro script and capture actual plan with runtime stats"/>
+
+
+
0)
+ {
+ SetStatus($"Format: {errors.Count} parse error(s) — {errors[0].Message}");
+ return;
+ }
+
+ QueryEditor.Text = formatted;
+ SetStatus("Formatted");
+ }
+
+ private void FormatOptions_Click(object? sender, RoutedEventArgs e)
+ {
+ var dialog = new Dialogs.FormatOptionsWindow();
+ dialog.ShowDialog(GetParentWindow());
+ }
}
diff --git a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml
new file mode 100644
index 0000000..d64a34e
--- /dev/null
+++ b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
new file mode 100644
index 0000000..63da0a0
--- /dev/null
+++ b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using PlanViewer.App.Services;
+
+namespace PlanViewer.App.Dialogs;
+
+public partial class FormatOptionsWindow : Window
+{
+ private readonly ObservableCollection _rows = new();
+ private readonly SqlFormatSettings _defaults = new();
+
+ public FormatOptionsWindow()
+ {
+ InitializeComponent();
+ LoadSettings();
+ }
+
+ private void LoadSettings()
+ {
+ var current = SqlFormatSettingsService.Load();
+ _rows.Clear();
+
+ foreach (var prop in typeof(SqlFormatSettings).GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ var currentVal = prop.GetValue(current);
+ var defaultVal = prop.GetValue(_defaults);
+
+ _rows.Add(new FormatOptionRow
+ {
+ Name = prop.Name,
+ CurrentValue = currentVal?.ToString() ?? "",
+ DefaultValue = defaultVal?.ToString() ?? "",
+ PropertyInfo = prop
+ });
+ }
+
+ OptionsGrid.ItemsSource = _rows;
+ }
+
+ private void Save_Click(object? sender, RoutedEventArgs e)
+ {
+ var settings = new SqlFormatSettings();
+
+ foreach (var row in _rows)
+ {
+ try
+ {
+ var prop = row.PropertyInfo;
+ object? value;
+
+ if (prop.PropertyType == typeof(bool))
+ value = bool.Parse(row.CurrentValue);
+ else if (prop.PropertyType == typeof(int))
+ value = int.Parse(row.CurrentValue);
+ else
+ value = row.CurrentValue;
+
+ prop.SetValue(settings, value);
+ }
+ catch
+ {
+ // Skip invalid values — keep default
+ }
+ }
+
+ SqlFormatSettingsService.Save(settings);
+ }
+
+ private void Revert_Click(object? sender, RoutedEventArgs e)
+ {
+ foreach (var row in _rows)
+ {
+ row.CurrentValue = row.DefaultValue;
+ }
+
+ // Refresh the grid
+ OptionsGrid.ItemsSource = null;
+ OptionsGrid.ItemsSource = _rows;
+ }
+
+ private void Close_Click(object? sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+}
+
+public class FormatOptionRow : INotifyPropertyChanged
+{
+ private string _currentValue = "";
+
+ public string Name { get; set; } = "";
+
+ public string CurrentValue
+ {
+ get => _currentValue;
+ set
+ {
+ _currentValue = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentValue)));
+ }
+ }
+
+ public string DefaultValue { get; set; } = "";
+
+ internal PropertyInfo PropertyInfo { get; set; } = null!;
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+}
diff --git a/src/PlanViewer.App/PlanViewer.App.csproj b/src/PlanViewer.App/PlanViewer.App.csproj
index 51b3a7a..da69d71 100644
--- a/src/PlanViewer.App/PlanViewer.App.csproj
+++ b/src/PlanViewer.App/PlanViewer.App.csproj
@@ -32,6 +32,7 @@
+
+
+
+
+
diff --git a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
index 63da0a0..380ffb3 100644
--- a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
+++ b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
@@ -30,12 +30,21 @@ private void LoadSettings()
{
var currentVal = prop.GetValue(current);
var defaultVal = prop.GetValue(_defaults);
+ var isBool = prop.PropertyType == typeof(bool);
+
+ string[]? choiceOptions = null;
+ if (prop.Name == "KeywordCasing")
+ choiceOptions = ["Uppercase", "Lowercase", "PascalCase"];
_rows.Add(new FormatOptionRow
{
Name = prop.Name,
CurrentValue = currentVal?.ToString() ?? "",
DefaultValue = defaultVal?.ToString() ?? "",
+ IsBool = isBool,
+ BoolValue = isBool && currentVal is true,
+ DefaultBoolValue = isBool && defaultVal is true,
+ ChoiceOptions = choiceOptions,
PropertyInfo = prop
});
}
@@ -55,7 +64,7 @@ private void Save_Click(object? sender, RoutedEventArgs e)
object? value;
if (prop.PropertyType == typeof(bool))
- value = bool.Parse(row.CurrentValue);
+ value = row.BoolValue;
else if (prop.PropertyType == typeof(int))
value = int.Parse(row.CurrentValue);
else
@@ -70,6 +79,7 @@ private void Save_Click(object? sender, RoutedEventArgs e)
}
SqlFormatSettingsService.Save(settings);
+ Close();
}
private void Revert_Click(object? sender, RoutedEventArgs e)
@@ -77,9 +87,10 @@ private void Revert_Click(object? sender, RoutedEventArgs e)
foreach (var row in _rows)
{
row.CurrentValue = row.DefaultValue;
+ if (row.IsBool)
+ row.BoolValue = row.DefaultBoolValue;
}
- // Refresh the grid
OptionsGrid.ItemsSource = null;
OptionsGrid.ItemsSource = _rows;
}
@@ -93,9 +104,36 @@ private void Close_Click(object? sender, RoutedEventArgs e)
public class FormatOptionRow : INotifyPropertyChanged
{
private string _currentValue = "";
+ private bool _boolValue;
public string Name { get; set; } = "";
+ public bool IsBool { get; set; }
+
+ public bool BoolValue
+ {
+ get => _boolValue;
+ set
+ {
+ _boolValue = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BoolValue)));
+ // Keep CurrentValue in sync for serialization
+ if (IsBool)
+ {
+ _currentValue = value.ToString();
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentValue)));
+ }
+ }
+ }
+
+ public bool DefaultBoolValue { get; set; }
+
+ public string[]? ChoiceOptions { get; set; }
+
+ public bool IsChoice => ChoiceOptions != null;
+
+ public bool IsText => !IsBool && !IsChoice;
+
public string CurrentValue
{
get => _currentValue;
From a2cb944fdd137338f47633ceee89b8ca55219214 Mon Sep 17 00:00:00 2001
From: rferraton <16419423+rferraton@users.noreply.github.com>
Date: Sat, 18 Apr 2026 00:08:32 +0200
Subject: [PATCH 03/11] undo possible after format
---
.../Controls/QuerySessionControl.axaml.cs | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs b/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs
index b91f570..ed149ff 100644
--- a/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs
+++ b/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs
@@ -2021,7 +2021,15 @@ private void Format_Click(object? sender, RoutedEventArgs e)
return;
}
- QueryEditor.Text = formatted;
+ QueryEditor.Document.BeginUpdate();
+ try
+ {
+ QueryEditor.Document.Replace(0, QueryEditor.Document.TextLength, formatted);
+ }
+ finally
+ {
+ QueryEditor.Document.EndUpdate();
+ }
SetStatus("Formatted");
}
From 944e299a2699f0bfea8619d917a314e1efdd3a82 Mon Sep 17 00:00:00 2001
From: rferraton <16419423+rferraton@users.noreply.github.com>
Date: Sat, 18 Apr 2026 08:59:29 +0200
Subject: [PATCH 04/11] Dynamic brushes instead of hard coded color
---
src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml
index 0dfcdf0..e2bf547 100644
--- a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml
+++ b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml
@@ -5,8 +5,8 @@
Title="SQL Format Options"
Width="650" Height="580"
MinWidth="520" MinHeight="400"
- Background="#1A1D23"
- Foreground="#E4E6EB"
+ Background="{DynamicResource BackgroundBrush}"
+ Foreground="{DynamicResource ForegroundBrush}"
WindowStartupLocation="CenterOwner">
From 2452a42f102d16674576b7597ea66f98d48ca4fc Mon Sep 17 00:00:00 2001
From: rferraton <16419423+rferraton@users.noreply.github.com>
Date: Sat, 18 Apr 2026 09:03:38 +0200
Subject: [PATCH 05/11] remove unused using lib
---
src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
index 380ffb3..2f58932 100644
--- a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
+++ b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
@@ -1,8 +1,5 @@
-using System;
-using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
-using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Interactivity;
From 87991b122e142de264e71b2c8ab849b8351b5271 Mon Sep 17 00:00:00 2001
From: rferraton <16419423+rferraton@users.noreply.github.com>
Date: Sat, 18 Apr 2026 09:14:46 +0200
Subject: [PATCH 06/11] Fix Reviewer remarks phase 1
---
.../Controls/QuerySessionControl.axaml | 2 +-
.../Controls/QuerySessionControl.axaml.cs | 7 +++++--
.../Dialogs/FormatOptionsWindow.axaml.cs | 18 ++++++++++++------
.../Services/SqlFormatSettingsService.cs | 17 +++++++++--------
.../Services/SqlFormattingService.cs | 7 ++++---
5 files changed, 31 insertions(+), 20 deletions(-)
diff --git a/src/PlanViewer.App/Controls/QuerySessionControl.axaml b/src/PlanViewer.App/Controls/QuerySessionControl.axaml
index fcafef1..46fa6bf 100644
--- a/src/PlanViewer.App/Controls/QuerySessionControl.axaml
+++ b/src/PlanViewer.App/Controls/QuerySessionControl.axaml
@@ -74,7 +74,7 @@
+ ToolTip.Tip="Format the SQL query"/>
@@ -39,8 +39,8 @@
HeadersVisibility="Column"
GridLinesVisibility="Horizontal"
IsReadOnly="False"
- Background="#1A1D23"
- Foreground="#E4E6EB"
+ Background="{DynamicResource BackgroundBrush}"
+ Foreground="{DynamicResource ForegroundBrush}"
Margin="0,0,0,12">
From 3b275cf02375ea1de8e1cd9757c45e028de45e60 Mon Sep 17 00:00:00 2001
From: rferraton <16419423+rferraton@users.noreply.github.com>
Date: Mon, 20 Apr 2026 08:55:16 +0200
Subject: [PATCH 11/11] Build passes. All three callers of Load(out
string?)/Save(SqlFormatSettings, out string?) now surface errors:
---
.../Controls/QuerySessionControl.axaml.cs | 5 ++-
.../Dialogs/FormatOptionsWindow.axaml.cs | 37 ++++++++++++++++++-
.../Services/SqlFormatSettingsService.cs | 10 ++++-
3 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs b/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs
index b3a84f0..de27e6b 100644
--- a/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs
+++ b/src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs
@@ -2017,7 +2017,10 @@ private async void Format_Click(object? sender, RoutedEventArgs e)
try
{
- var settings = SqlFormatSettingsService.Load();
+ var settings = SqlFormatSettingsService.Load(out var loadError);
+ if (loadError != null)
+ SetStatus("Warning: using default format settings (load failed)");
+
var (formatted, errors) = await Task.Run(() => SqlFormattingService.Format(sql, settings));
if (errors != null && errors.Count > 0)
diff --git a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
index ffd5a27..0e4e24a 100644
--- a/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
+++ b/src/PlanViewer.App/Dialogs/FormatOptionsWindow.axaml.cs
@@ -52,7 +52,9 @@ public FormatOptionsWindow()
private void LoadSettings()
{
- var current = SqlFormatSettingsService.Load();
+ var current = SqlFormatSettingsService.Load(out var loadError);
+ if (loadError != null)
+ ShowErrorPopup("Load Error", loadError);
_rows.Clear();
var props = typeof(SqlFormatSettings).GetProperties(BindingFlags.Public | BindingFlags.Instance)
@@ -122,7 +124,11 @@ private void Save_Click(object? sender, RoutedEventArgs e)
}
}
- SqlFormatSettingsService.Save(settings);
+ if (!SqlFormatSettingsService.Save(settings, out var saveError))
+ {
+ ShowErrorPopup("Save Error", saveError!);
+ return;
+ }
_isDirty = false;
Close();
}
@@ -137,6 +143,33 @@ private void Revert_Click(object? sender, RoutedEventArgs e)
}
}
+ private void ShowErrorPopup(string title, string message)
+ {
+ var dialog = new Window
+ {
+ Title = title,
+ Width = 480,
+ Height = 220,
+ WindowStartupLocation = WindowStartupLocation.CenterOwner,
+ Background = (IBrush)this.FindResource("BackgroundBrush")!,
+ Foreground = (IBrush)this.FindResource("ForegroundBrush")!,
+ Content = new StackPanel
+ {
+ Margin = new Avalonia.Thickness(20),
+ Children =
+ {
+ new TextBlock
+ {
+ Text = message,
+ TextWrapping = TextWrapping.Wrap,
+ FontSize = 13
+ }
+ }
+ }
+ };
+ dialog.ShowDialog(this);
+ }
+
private void Close_Click(object? sender, RoutedEventArgs e)
{
TryClose();
diff --git a/src/PlanViewer.App/Services/SqlFormatSettingsService.cs b/src/PlanViewer.App/Services/SqlFormatSettingsService.cs
index f3bc4cb..23e8cc5 100644
--- a/src/PlanViewer.App/Services/SqlFormatSettingsService.cs
+++ b/src/PlanViewer.App/Services/SqlFormatSettingsService.cs
@@ -97,8 +97,9 @@ internal static class SqlFormatSettingsService
WriteIndented = true
};
- public static SqlFormatSettings Load()
+ public static SqlFormatSettings Load(out string? error)
{
+ error = null;
try
{
if (!File.Exists(SettingsPath))
@@ -110,21 +111,26 @@ public static SqlFormatSettings Load()
catch (Exception ex)
{
Debug.WriteLine($"SqlFormatSettings: failed to load settings: {ex.Message}");
+ error = $"Could not load format settings from:\n{SettingsPath}\n\n{ex.Message}\n\nUsing defaults. Delete or fix the file to clear this error.";
return new SqlFormatSettings();
}
}
- public static void Save(SqlFormatSettings settings)
+ public static bool Save(SqlFormatSettings settings, out string? error)
{
+ error = null;
try
{
Directory.CreateDirectory(SettingsDir);
var json = JsonSerializer.Serialize(settings, JsonOptions);
File.WriteAllText(SettingsPath, json);
+ return true;
}
catch (Exception ex)
{
Debug.WriteLine($"SqlFormatSettings: failed to save settings: {ex.Message}");
+ error = $"Could not save format settings to:\n{SettingsPath}\n\n{ex.Message}\n\nCheck that the folder is writable and not locked by another process.";
+ return false;
}
}
}