Skip to content

Commit 2958568

Browse files
Merge pull request #243 from erikdarlingdata/dev
Release v1.6.0
2 parents a1f8362 + 78d3a46 commit 2958568

12 files changed

Lines changed: 840 additions & 42 deletions

THIRD-PARTY-NOTICES.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Third-Party Notices
2+
3+
This project incorporates material from the projects listed below.
4+
5+
---
6+
7+
## SqlFormatter
8+
9+
- **Source:** https://github.com/madskristensen/SqlFormatter
10+
- **License:** MIT (Apache-2.0 per repository; individual files carry MIT terms)
11+
- **Copyright:** Copyright (c) Mads Kristensen
12+
13+
The `SqlFormattingService` in this project was inspired by and partially derived
14+
from the SqlFormatter extension for Visual Studio by Mads Kristensen. It uses
15+
`Microsoft.SqlServer.TransactSql.ScriptDom` for T-SQL parsing and formatting.
16+
17+
### MIT License
18+
19+
Permission is hereby granted, free of charge, to any person obtaining a copy
20+
of this software and associated documentation files (the "Software"), to deal
21+
in the Software without restriction, including without limitation the rights
22+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
23+
copies of the Software, and to permit persons to whom the Software is
24+
furnished to do so, subject to the following conditions:
25+
26+
The above copyright notice and this permission notice shall be included in all
27+
copies or substantial portions of the Software.
28+
29+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35+
SOFTWARE.

src/PlanViewer.App/Controls/PlanViewerControl.axaml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,39 @@
1313
<Border Grid.Row="0" Background="{DynamicResource BackgroundDarkBrush}" Padding="8,6"
1414
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
1515
<DockPanel>
16-
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
16+
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left" Spacing="4">
1717
<Button x:Name="PlanConnectButton" Content="Connect" Click="PlanConnect_Click"
18-
Height="28" Padding="10,0" FontSize="12"
18+
Height="28" Padding="8,0" FontSize="12"
1919
Theme="{StaticResource AppButton}"
2020
ToolTip.Tip="Connect to a SQL Server for schema lookups"/>
2121
<TextBlock x:Name="PlanServerLabel" Text=""
2222
VerticalAlignment="Center" FontSize="12"
23-
Foreground="{DynamicResource ForegroundBrush}" Margin="6,0,0,0"/>
23+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0,0,0"/>
2424
<TextBlock Text="|" VerticalAlignment="Center"
25-
Foreground="{DynamicResource ForegroundBrush}" Margin="6,0"/>
25+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0"/>
2626
<ComboBox x:Name="PlanDatabaseBox" Width="180" Height="28" FontSize="12"
2727
IsEnabled="False" PlaceholderText="Database"
2828
SelectionChanged="PlanDatabase_SelectionChanged"/>
2929
<TextBlock Text="|" VerticalAlignment="Center"
30-
Foreground="{DynamicResource ForegroundBrush}" Margin="6,0"/>
30+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0"/>
3131
<Button Content="+" Click="ZoomIn_Click" Width="28" Height="28" Padding="0" FontSize="16"
3232
FontWeight="Bold" ToolTip.Tip="Zoom In"
3333
Theme="{StaticResource AppButton}"/>
3434
<Button Content="&#x2212;" Click="ZoomOut_Click" Width="28" Height="28" Padding="0" FontSize="16"
35-
FontWeight="Bold" Margin="4,0,0,0" ToolTip.Tip="Zoom Out"
35+
FontWeight="Bold" ToolTip.Tip="Zoom Out"
3636
Theme="{StaticResource AppButton}"/>
37-
<Button Content="Fit" Click="ZoomFit_Click" Height="28" Padding="8,0" Margin="4,0,0,0"
37+
<Button Content="Fit" Click="ZoomFit_Click" Height="28" Padding="8,0"
3838
ToolTip.Tip="Zoom to Fit"
3939
Theme="{StaticResource AppButton}"/>
4040
<TextBlock x:Name="ZoomLevelText" Text="100%" VerticalAlignment="Center"
41-
Foreground="{DynamicResource ForegroundBrush}" Margin="8,0,0,0" FontSize="11"/>
41+
Foreground="{DynamicResource ForegroundBrush}" Margin="4,0,0,0" FontSize="11"/>
4242
<TextBlock Text="|" VerticalAlignment="Center"
43-
Foreground="{DynamicResource ForegroundBrush}" Margin="12,0"/>
43+
Foreground="{DynamicResource ForegroundBrush}" Margin="6,0"/>
4444
<Button Content="Save .sqlplan" Click="SavePlan_Click" Height="28" Padding="8,0"
4545
ToolTip.Tip="Save plan as .sqlplan file"
4646
Theme="{StaticResource AppButton}"/>
4747
<TextBlock Text="|" VerticalAlignment="Center"
48-
Foreground="{DynamicResource ForegroundBrush}" Margin="12,0"
48+
Foreground="{DynamicResource ForegroundBrush}" Margin="6,0"
4949
x:Name="StatementsButtonSeparator" IsVisible="False"/>
5050
<Button x:Name="StatementsButton" Content="Statements" Click="ToggleStatements_Click"
5151
Height="28" Padding="8,0" IsVisible="False"

src/PlanViewer.App/Controls/QuerySessionControl.axaml

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,67 +8,77 @@
88
<Border Grid.Row="0" Background="{DynamicResource BackgroundDarkBrush}" Padding="8,6"
99
BorderBrush="{DynamicResource BorderBrush}" BorderThickness="0,0,0,1">
1010
<DockPanel>
11-
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left" Spacing="6">
11+
<StackPanel Orientation="Horizontal" DockPanel.Dock="Left" Spacing="4">
1212
<Button x:Name="ConnectButton" Content="Connect" Click="Connect_Click"
13-
Height="28" Padding="10,0" FontSize="12"
13+
Height="28" Padding="8,0" FontSize="12"
1414
Theme="{StaticResource AppButton}"
1515
ToolTip.Tip="Connect to a SQL Server"/>
1616
<TextBlock x:Name="ServerLabel" Text="Not connected"
1717
VerticalAlignment="Center" FontSize="12"
18-
Foreground="{DynamicResource ForegroundBrush}"/>
18+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0,0,0"/>
1919
<TextBlock Text="|" VerticalAlignment="Center"
20-
Foreground="{DynamicResource ForegroundBrush}" Margin="4,0"/>
20+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0"/>
2121
<ComboBox x:Name="DatabaseBox" Width="200" Height="28" FontSize="12"
2222
IsEnabled="False" PlaceholderText="Database"
2323
SelectionChanged="Database_SelectionChanged"/>
2424
<TextBlock Text="|" VerticalAlignment="Center"
25-
Foreground="{DynamicResource ForegroundBrush}" Margin="4,0"/>
25+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0"/>
2626
<Button x:Name="ExecuteButton" Content="&#x25B6; Actual Plan" Click="Execute_Click"
27-
Height="28" Padding="10,0" FontSize="12" IsEnabled="False"
27+
Height="28" Padding="8,0" FontSize="12" IsEnabled="False"
2828
Theme="{StaticResource AppButton}"
2929
ToolTip.Tip="Run query and capture execution plan with runtime stats (F5 / Ctrl+E)"/>
3030
<Button x:Name="ExecuteEstButton" Content="&#x25C7; Estimated Plan" Click="ExecuteEstimated_Click"
31-
Height="28" Padding="10,0" FontSize="12" IsEnabled="False"
31+
Height="28" Padding="8,0" FontSize="12" IsEnabled="False"
3232
Theme="{StaticResource AppButton}"
3333
ToolTip.Tip="Capture estimated plan without executing (Ctrl+L)"/>
3434
<TextBlock Text="|" VerticalAlignment="Center"
35-
Foreground="{DynamicResource ForegroundBrush}" Margin="4,0"/>
35+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0"/>
3636
<Button x:Name="HumanAdviceButton" Content="&#x1F9D1; Human Advice"
3737
Click="HumanAdvice_Click"
38-
Height="28" Padding="10,0" FontSize="12" IsEnabled="False"
38+
Height="28" Padding="8,0" FontSize="12" IsEnabled="False"
3939
Theme="{StaticResource AppButton}"
4040
ToolTip.Tip="Human-readable plan analysis"/>
4141
<Button x:Name="RobotAdviceButton" Content="&#x1F916; Robot Advice"
4242
Click="RobotAdvice_Click"
43-
Height="28" Padding="10,0" FontSize="12" IsEnabled="False"
43+
Height="28" Padding="8,0" FontSize="12" IsEnabled="False"
4444
Theme="{StaticResource AppButton}"
4545
ToolTip.Tip="JSON analysis for LLMs and automation"/>
4646
<TextBlock Text="|" VerticalAlignment="Center"
47-
Foreground="{DynamicResource ForegroundBrush}" Margin="4,0"/>
47+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0"/>
4848
<Button x:Name="ComparePlansButton" Content="&#x2194; Compare Plans"
4949
Click="ComparePlans_Click"
50-
Height="28" Padding="10,0" FontSize="12" IsEnabled="False"
50+
Height="28" Padding="8,0" FontSize="12" IsEnabled="False"
5151
Theme="{StaticResource AppButton}"
5252
ToolTip.Tip="Compare two plan tabs"/>
5353
<TextBlock Text="|" VerticalAlignment="Center"
54-
Foreground="{DynamicResource ForegroundBrush}" Margin="4,0"/>
54+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0"/>
5555
<Button x:Name="QueryStoreButton" Content="&#x1F4CA; Query Store"
5656
Click="QueryStore_Click"
57-
Height="28" Padding="10,0" FontSize="12"
57+
Height="28" Padding="8,0" FontSize="12"
5858
Theme="{StaticResource AppButton}"
5959
ToolTip.Tip="Analyze top queries from Query Store"/>
6060
<TextBlock Text="|" VerticalAlignment="Center"
61-
Foreground="{DynamicResource ForegroundBrush}" Margin="4,0"/>
61+
Foreground="{DynamicResource ForegroundBrush}" Margin="2,0"/>
6262
<Button x:Name="CopyReproButton" Content="&#x1F4CB; Copy Repro"
6363
Click="CopyRepro_Click"
64-
Height="28" Padding="10,0" FontSize="12" IsEnabled="False"
64+
Height="28" Padding="8,0" FontSize="12" IsEnabled="False"
6565
Theme="{StaticResource AppButton}"
6666
ToolTip.Tip="Copy reproduction script to clipboard"/>
6767
<Button x:Name="GetActualPlanButton" Content="&#x25B6; Run Repro"
6868
Click="GetActualPlan_Click"
69-
Height="28" Padding="10,0" FontSize="12" IsEnabled="False"
69+
Height="28" Padding="8,0" FontSize="12" IsEnabled="False"
7070
Theme="{StaticResource AppButton}"
7171
ToolTip.Tip="Execute the repro script and capture actual plan with runtime stats"/>
72+
<TextBlock Text="|" VerticalAlignment="Center"
73+
Foreground="{DynamicResource ForegroundBrush}" Margin="4,0"/>
74+
<Button x:Name="FormatButton" Content="&#x1F4DD; Format" Click="Format_Click"
75+
Height="28" Padding="10,0" FontSize="12"
76+
Theme="{StaticResource AppButton}"
77+
ToolTip.Tip="Format the SQL query"/>
78+
<Button x:Name="FormatOptionsButton" Content="&#x2699; Format Options" Click="FormatOptions_Click"
79+
Height="28" Padding="10,0" FontSize="12"
80+
Theme="{StaticResource AppButton}"
81+
ToolTip.Tip="Configure SQL formatting options"/>
7282
</StackPanel>
7383
<TextBlock x:Name="StatusText" DockPanel.Dock="Right"
7484
HorizontalAlignment="Right" VerticalAlignment="Center"

src/PlanViewer.App/Controls/QuerySessionControl.axaml.cs

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,22 @@ public QuerySessionControl(ICredentialService credentialService, ConnectionStore
6868
QueryEditor.TextArea.TextEntered += OnTextEntered;
6969

7070
// Focus the editor when the control is attached to the visual tree
71+
// Re-install TextMate if it was disposed on detach (tab switching disposes it)
7172
AttachedToVisualTree += (_, _) =>
7273
{
74+
if (_textMateInstallation == null)
75+
SetupSyntaxHighlighting();
76+
7377
QueryEditor.Focus();
7478
QueryEditor.TextArea.Focus();
7579
};
7680

77-
DetachedFromVisualTree += (_, _) => _textMateInstallation?.Dispose();
81+
// Dispose TextMate when detached (e.g. tab switch) to release renderers/transformers
82+
DetachedFromVisualTree += (_, _) =>
83+
{
84+
_textMateInstallation?.Dispose();
85+
_textMateInstallation = null;
86+
};
7887

7988
// Focus the editor when the Editor tab is selected; toggle plan-dependent buttons
8089
SubTabControl.SelectionChanged += (_, _) =>
@@ -1996,4 +2005,86 @@ private Window GetParentWindow()
19962005
var parent = this.VisualRoot;
19972006
return parent as Window ?? throw new InvalidOperationException("No parent window");
19982007
}
2008+
2009+
private async void Format_Click(object? sender, RoutedEventArgs e)
2010+
{
2011+
var sql = QueryEditor.Text;
2012+
if (string.IsNullOrWhiteSpace(sql))
2013+
return;
2014+
2015+
FormatButton.IsEnabled = false;
2016+
SetStatus("Formatting...");
2017+
2018+
try
2019+
{
2020+
var settings = SqlFormatSettingsService.Load(out var loadError);
2021+
if (loadError != null)
2022+
SetStatus("Warning: using default format settings (load failed)");
2023+
2024+
var (formatted, errors) = await Task.Run(() => SqlFormattingService.Format(sql, settings));
2025+
2026+
if (errors != null && errors.Count > 0)
2027+
{
2028+
var errorMessages = string.Join("\n", errors.Select(err => $"Line {err.Line}: {err.Message}"));
2029+
var dialog = new Window
2030+
{
2031+
Title = "SQL Format Error",
2032+
Width = 500,
2033+
Height = 250,
2034+
WindowStartupLocation = WindowStartupLocation.CenterOwner,
2035+
Icon = GetParentWindow().Icon,
2036+
Background = (IBrush)this.FindResource("BackgroundBrush")!,
2037+
Foreground = (IBrush)this.FindResource("ForegroundBrush")!,
2038+
Content = new StackPanel
2039+
{
2040+
Margin = new Avalonia.Thickness(20),
2041+
Children =
2042+
{
2043+
new TextBlock
2044+
{
2045+
Text = $"Could not format: {errors.Count} parse error(s)",
2046+
FontWeight = Avalonia.Media.FontWeight.Bold,
2047+
FontSize = 14,
2048+
Margin = new Avalonia.Thickness(0, 0, 0, 10)
2049+
},
2050+
new TextBlock
2051+
{
2052+
Text = errorMessages,
2053+
TextWrapping = TextWrapping.Wrap,
2054+
FontSize = 12
2055+
}
2056+
}
2057+
}
2058+
};
2059+
await dialog.ShowDialog(GetParentWindow());
2060+
SetStatus($"Format failed: {errors.Count} error(s)");
2061+
return;
2062+
}
2063+
2064+
var caretOffset = QueryEditor.CaretOffset;
2065+
2066+
QueryEditor.Document.BeginUpdate();
2067+
try
2068+
{
2069+
QueryEditor.Document.Replace(0, QueryEditor.Document.TextLength, formatted);
2070+
}
2071+
finally
2072+
{
2073+
QueryEditor.Document.EndUpdate();
2074+
}
2075+
2076+
QueryEditor.CaretOffset = Math.Min(caretOffset, QueryEditor.Document.TextLength);
2077+
SetStatus("Formatted");
2078+
}
2079+
finally
2080+
{
2081+
FormatButton.IsEnabled = true;
2082+
}
2083+
}
2084+
2085+
private void FormatOptions_Click(object? sender, RoutedEventArgs e)
2086+
{
2087+
var dialog = new Dialogs.FormatOptionsWindow();
2088+
dialog.ShowDialog(GetParentWindow());
2089+
}
19992090
}

0 commit comments

Comments
 (0)