From bb88f2e4f12165a1e9c247c3b0bd64d65cf6faa3 Mon Sep 17 00:00:00 2001 From: Granwest-Iknight Date: Mon, 1 Dec 2025 11:01:44 -0800 Subject: [PATCH 1/2] Add Visual Studio 2026 extension support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created a new Visual Studio extension (TSqlFormatter.VS2026) that brings T-SQL formatting capabilities to Visual Studio 2022, 2026, and later versions. New Project: - TSqlFormatter.VS2026 - Visual Studio IDE extension - Supports VS 2022 (17.0), 2026 (19.0), and later - Targets Community, Professional, and Enterprise editions - Uses traditional VSSDK model for broad compatibility Key Features: - Same formatting engine and options as SSMS extension - Dedicated launch profiles for VS 2022 and VS 2026 - Support for both C: and F: drive installations - Full options dialog with persistent settings - Keyboard shortcut support (Ctrl+K, Ctrl+F) Technical Details: - Based on the proven SSMS extension architecture - Shared TSqlFormatter.Core library for consistent formatting - Open-ended version range [17.0,) for future VS versions - Separate VSIX manifest targeting VS IDE (not SSMS) Documentation: - Updated README.md with VS extension installation instructions - Updated CLAUDE.md with build commands and architecture notes - Added comprehensive launch settings with common installation paths Solution Structure: - Added TSqlFormatter.VS2026 project to TSqlFormatter.SSMS21.sln - Now builds both SSMS and VS extensions in one solution 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 43 +- README.md | 27 +- TSqlFormatter.SSMS21.sln | 6 + TSqlFormatter.VS2026/FormatCommand.cs | 103 +++++ TSqlFormatter.VS2026/FormatterPackage.cs | 109 ++++++ TSqlFormatter.VS2026/FormatterPackage.vsct | 45 +++ TSqlFormatter.VS2026/Guids.cs | 13 + TSqlFormatter.VS2026/OptionsDialog.cs | 368 ++++++++++++++++++ TSqlFormatter.VS2026/OptionsDialogWindow.xaml | 113 ++++++ .../OptionsDialogWindow.xaml.cs | 126 ++++++ .../Properties/AssemblyInfo.cs | 17 + .../Properties/launchSettings.json | 49 +++ .../Resources/FormatterPackage.ico | 1 + .../Resources/FormatterPackage.png | Bin 0 -> 158 bytes TSqlFormatter.VS2026/Settings.cs | 158 ++++++++ .../TSqlFormatter.VS2026.csproj | 109 ++++++ .../source.extension.vsixmanifest | 32 ++ 17 files changed, 1290 insertions(+), 29 deletions(-) create mode 100644 TSqlFormatter.VS2026/FormatCommand.cs create mode 100644 TSqlFormatter.VS2026/FormatterPackage.cs create mode 100644 TSqlFormatter.VS2026/FormatterPackage.vsct create mode 100644 TSqlFormatter.VS2026/Guids.cs create mode 100644 TSqlFormatter.VS2026/OptionsDialog.cs create mode 100644 TSqlFormatter.VS2026/OptionsDialogWindow.xaml create mode 100644 TSqlFormatter.VS2026/OptionsDialogWindow.xaml.cs create mode 100644 TSqlFormatter.VS2026/Properties/AssemblyInfo.cs create mode 100644 TSqlFormatter.VS2026/Properties/launchSettings.json create mode 100644 TSqlFormatter.VS2026/Resources/FormatterPackage.ico create mode 100644 TSqlFormatter.VS2026/Resources/FormatterPackage.png create mode 100644 TSqlFormatter.VS2026/Settings.cs create mode 100644 TSqlFormatter.VS2026/TSqlFormatter.VS2026.csproj create mode 100644 TSqlFormatter.VS2026/source.extension.vsixmanifest diff --git a/CLAUDE.md b/CLAUDE.md index 8bc9bbe..5b845f4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,31 +6,23 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Poor Man's T-SQL Formatter is a .NET 2.0 and JavaScript library for reformatting T-SQL code. It includes multiple components: a core formatting library, WinForms demo app, SSMS/Visual Studio add-ins, command-line utility, Notepad++ plugin, WinMerge plugin, and web service. -The project maintains three main solutions: -- `PoorMansTSqlFormatter.sln` - Main solution with all components -- `PoorMansTSqlFormatterSSMS21.sln` - Legacy SSMS 21 support (4 projects) -- `TSqlFormatter.SSMS21.sln` - New streamlined SSMS 21 solution (2 projects: Core + SSMS) +The project maintains a streamlined solution: +- `TSqlFormatter.SSMS21.sln` - Streamlined solution (3 projects: Core + SSMS + VS2026) + - Supports SSMS 21+ and Visual Studio 2022+ ## Build Commands -### Building the Solutions +### Building the Solution ```bash -# Build the main solution (requires Visual Studio 2013+ or MSBuild) -msbuild PoorMansTSqlFormatter.sln /p:Configuration=Release /p:Platform="Any CPU" - -# Build the streamlined SSMS 21 solution +# Build the solution (creates both SSMS and VS extensions) msbuild TSqlFormatter.SSMS21.sln /p:Configuration=Release /p:Platform="Any CPU" -# Build the legacy SSMS 21 solution -msbuild PoorMansTSqlFormatterSSMS21.sln /p:Configuration=Release /p:Platform="Any CPU" - # Restore NuGet packages before building (if needed) -msbuild /t:Restore PoorMansTSqlFormatter.sln msbuild /t:Restore TSqlFormatter.SSMS21.sln -# Build specific VS/SSMS packages -msbuild PoorMansTSqlFormatterSSMSPackage2021\PoorMansTSqlFormatterSSMSPackage2021.csproj /p:Configuration=Release +# Build specific packages msbuild TSqlFormatter.SSMS\TSqlFormatter.SSMS.csproj /p:Configuration=Release +msbuild TSqlFormatter.VS2026\TSqlFormatter.VS2026.csproj /p:Configuration=Release ``` ### Running Tests @@ -69,17 +61,20 @@ msbuild TSqlFormatter.SSMS\TSqlFormatter.SSMS.csproj /p:Configuration=Release - **PoorMansTSqlFormatterJSLib/** - JavaScript library (transpiled using Bridge.NET) ### Key Integration Points -- **Command Line**: PoorMansTSqlFormatterCmdLine - uses NDesk.Options for argument parsing -- **SSMS Add-ins**: - - PoorMansTSqlFormatterSSMSPackage2021 - SSMS 18-21+ (VSIX package, targets `Id="SSMS"`) - - TSqlFormatter.SSMS - New streamlined SSMS 21 package -- **VS Extensions**: - - PoorMansTSqlFormatterVSPackage2013 - Visual Studio 2013 - - PoorMansTSqlFormatterVSPackage2019 - Visual Studio 2019+ -- **Notepad++ Plugin**: PoorMansTSqlFormatterNppPlugin - uses UnmanagedExports for native integration +- **SSMS Extension**: + - TSqlFormatter.SSMS - SSMS 21+ package (VSIX package, targets `Id="Microsoft.VisualStudio.Ssms"`) + - Supports SSMS 21, 22, and later versions + +- **Visual Studio Extension**: + - TSqlFormatter.VS2026 - Visual Studio 2022+ package (VSIX package) + - Targets `Microsoft.VisualStudio.Community`, `Pro`, and `Enterprise` editions + - Supports VS 2022 (17.0), 2026 (19.0), and later versions + - Uses traditional VSSDK model for compatibility ### VSIX Manifest Configuration -SSMS extensions must target `Id="SSMS"` (not `Microsoft.VisualStudio.Ssms`) with version range like `[21.0,22.0)` for SSMS 21 compatibility. +- **SSMS**: Target `Id="Microsoft.VisualStudio.Ssms"` with version range `[21.0,)` for SSMS 21+ compatibility +- **Visual Studio**: Target `Id="Microsoft.VisualStudio.Community"` (and Pro/Enterprise) with version range `[17.0,)` for VS 2022+ compatibility +- The open-ended ranges automatically support future versions ### Test Data Organization When tests are configured, they use file-based comparisons: diff --git a/README.md b/README.md index c050fcc..7020898 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,15 @@ The project has been restructured into a modern, maintainable architecture: * Keyboard shortcuts (Ctrl+K, Ctrl+F) * Persistent settings via Visual Studio settings store +* **TSqlFormatter.VS2026** - Visual Studio 2022+ extension package + * Supports Visual Studio 2022, 2026, and later + * Same formatting options as SSMS extension + * Works with SQL files in Visual Studio IDE + * Uses traditional VSSDK model for compatibility + ### Current Solution -* **TSqlFormatter.SSMS21.sln** - Main solution file with streamlined 2-project structure +* **TSqlFormatter.SSMS21.sln** - Main solution file with 3 projects (Core + SSMS + VS2026) ### Building the Solution @@ -34,19 +40,21 @@ The project has been restructured into a modern, maintainable architecture: #### Build Commands: ```bash -# Build the new streamlined SSMS 21 solution +# Build the solution (creates both SSMS and VS extensions) msbuild TSqlFormatter.SSMS21.sln /p:Configuration=Release /p:Platform="Any CPU" # Or restore packages first if needed msbuild /t:Restore TSqlFormatter.SSMS21.sln msbuild TSqlFormatter.SSMS21.sln /p:Configuration=Release -# The VSIX package will be created at: -# TSqlFormatter.SSMS\bin\Release\TSqlFormatter.SSMS.vsix +# The VSIX packages will be created at: +# TSqlFormatter.SSMS\bin\Release\TSqlFormatter.SSMS.vsix (for SSMS) +# TSqlFormatter.VS2026\bin\Release\TSqlFormatter.VS2026.vsix (for Visual Studio) ``` -### Installing in SSMS +### Installing the Extensions +#### For SSMS: 1. Close all instances of SSMS 2. Double-click the generated `TSqlFormatter.SSMS.vsix` file 3. Follow the installation wizard @@ -55,6 +63,15 @@ msbuild TSqlFormatter.SSMS21.sln /p:Configuration=Release - **Tools > Format T-SQL Code** (or Ctrl+K, Ctrl+F) - **Tools > T-SQL Formatter Options...** for settings +#### For Visual Studio 2022/2026: +1. Close all instances of Visual Studio +2. Double-click the generated `TSqlFormatter.VS2026.vsix` file +3. Follow the installation wizard +4. Restart Visual Studio +5. Access the formatter via: + - **Tools > Format T-SQL Code** (or Ctrl+K, Ctrl+F) + - **Tools > T-SQL Formatter Options...** for settings + ### Features * **Core Formatting Engine** diff --git a/TSqlFormatter.SSMS21.sln b/TSqlFormatter.SSMS21.sln index ee85639..def9cdd 100644 --- a/TSqlFormatter.SSMS21.sln +++ b/TSqlFormatter.SSMS21.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSqlFormatter.Core", "TSqlF EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSqlFormatter.SSMS", "TSqlFormatter.SSMS\TSqlFormatter.SSMS.csproj", "{879B3998-C5A5-4B64-8674-70BE959ACE6F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSqlFormatter.VS2026", "TSqlFormatter.VS2026\TSqlFormatter.VS2026.csproj", "{A5B94F91-3D2C-4E1F-9A7E-8C3D6F5E4B2A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -25,6 +27,10 @@ Global {879B3998-C5A5-4B64-8674-70BE959ACE6F}.Debug|Any CPU.Build.0 = Debug|Any CPU {879B3998-C5A5-4B64-8674-70BE959ACE6F}.Release|Any CPU.ActiveCfg = Release|Any CPU {879B3998-C5A5-4B64-8674-70BE959ACE6F}.Release|Any CPU.Build.0 = Release|Any CPU + {A5B94F91-3D2C-4E1F-9A7E-8C3D6F5E4B2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5B94F91-3D2C-4E1F-9A7E-8C3D6F5E4B2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5B94F91-3D2C-4E1F-9A7E-8C3D6F5E4B2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5B94F91-3D2C-4E1F-9A7E-8C3D6F5E4B2A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TSqlFormatter.VS2026/FormatCommand.cs b/TSqlFormatter.VS2026/FormatCommand.cs new file mode 100644 index 0000000..f97b7da --- /dev/null +++ b/TSqlFormatter.VS2026/FormatCommand.cs @@ -0,0 +1,103 @@ +using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Text.Editor; +using System; +using TSqlFormatter; +using TSqlFormatter.Formatters; + +namespace TSqlFormatter.VS2026 +{ + public class FormatCommand + { + private readonly AsyncPackage _package; + private Settings _settings; + + public FormatCommand(AsyncPackage package) + { + _package = package ?? throw new ArgumentNullException(nameof(package)); + _settings = new Settings(); + } + + public void Execute() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + try + { + var dte = (DTE2)Package.GetGlobalService(typeof(DTE)); + if (dte?.ActiveDocument == null) + return; + + var textDocument = (TextDocument)dte.ActiveDocument.Object("TextDocument"); + if (textDocument == null) + return; + + var selection = textDocument.Selection; + string sqlToFormat; + bool selectionOnly = false; + + if (selection != null && !selection.IsEmpty) + { + sqlToFormat = selection.Text; + selectionOnly = true; + } + else + { + var startPoint = textDocument.StartPoint.CreateEditPoint(); + var endPoint = textDocument.EndPoint.CreateEditPoint(); + sqlToFormat = startPoint.GetText(endPoint); + } + + if (string.IsNullOrWhiteSpace(sqlToFormat)) + return; + + // Format the SQL with options + var formatterOptions = _settings.GetFormatterOptions(); + var formatter = new SqlFormattingManager(new TSqlStandardFormatter(formatterOptions)); + string formattedSql = formatter.Format(sqlToFormat); + + // Replace the text + if (selectionOnly) + { + selection.Delete(); + selection.Insert(formattedSql); + } + else + { + var startPoint = textDocument.StartPoint.CreateEditPoint(); + var endPoint = textDocument.EndPoint.CreateEditPoint(); + startPoint.ReplaceText(endPoint, formattedSql, 0); + } + } + catch (Exception ex) + { + System.Windows.Forms.MessageBox.Show( + $"Error formatting SQL: {ex.Message}", + "T-SQL Formatter", + System.Windows.Forms.MessageBoxButtons.OK, + System.Windows.Forms.MessageBoxIcon.Error); + } + } + + public void ShowOptions() + { + ThreadHelper.ThrowIfNotOnUIThread(); + + try + { + // Show WPF dialog using VS 2022 DialogWindow + var wpfDialog = new OptionsDialogWindow(_settings); + wpfDialog.ShowModal(); + } + catch (Exception ex) + { + System.Windows.Forms.MessageBox.Show( + $"Error showing options: {ex.Message}", + "T-SQL Formatter", + System.Windows.Forms.MessageBoxButtons.OK, + System.Windows.Forms.MessageBoxIcon.Error); + } + } + } +} \ No newline at end of file diff --git a/TSqlFormatter.VS2026/FormatterPackage.cs b/TSqlFormatter.VS2026/FormatterPackage.cs new file mode 100644 index 0000000..95ddbba --- /dev/null +++ b/TSqlFormatter.VS2026/FormatterPackage.cs @@ -0,0 +1,109 @@ +using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using System; +using System.ComponentModel.Design; +using System.Runtime.InteropServices; +using System.Threading; +using Task = System.Threading.Tasks.Task; + +namespace TSqlFormatter.VS2026 +{ + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [InstalledProductRegistration("#110", "#112", "1.7.0", IconResourceID = 400)] + [ProvideMenuResource("Menus.ctmenu", 1)] + [ProvideAutoLoad(VSConstants.UICONTEXT.NoSolution_string, PackageAutoLoadFlags.BackgroundLoad)] + [Guid(PackageGuidString)] + public sealed class FormatterPackage : AsyncPackage + { + public const string PackageGuidString = "c47a9b21-2692-47d6-972a-976544685f0f"; + public const string CommandSetGuidString = "6fa2e413-8351-4ca9-b0a0-34a9b241648c"; + + public const uint FormatSqlCommandId = 0x0100; + public const uint OptionsCommandId = 0x0101; + + public static readonly Guid CommandSetGuid = new Guid(CommandSetGuidString); + + private FormatCommand _formatCommand; + + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + { + try + { + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await base.InitializeAsync(cancellationToken, progress); + + var mcs = await GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService; + if (mcs != null) + { + // Format SQL command + var formatCommandID = new CommandID(CommandSetGuid, (int)FormatSqlCommandId); + var formatCommand = new OleMenuCommand(FormatSqlCallback, formatCommandID); + formatCommand.BeforeQueryStatus += QueryFormatButtonStatus; + mcs.AddCommand(formatCommand); + + // Options command + var optionsCommandID = new CommandID(CommandSetGuid, (int)OptionsCommandId); + var optionsCommand = new OleMenuCommand(OptionsCallback, optionsCommandID); + mcs.AddCommand(optionsCommand); + } + + _formatCommand = new FormatCommand(this); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error initializing FormatterPackage: {ex}"); + } + } + + private void QueryFormatButtonStatus(object sender, EventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + var command = sender as OleMenuCommand; + if (command != null) + { + var dte = (DTE2)GetService(typeof(DTE)); + command.Enabled = dte?.ActiveDocument != null; + } + } + + private void FormatSqlCallback(object sender, EventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + try + { + if (_formatCommand != null) + { + _formatCommand.Execute(); + } + else + { + System.Diagnostics.Debug.WriteLine("FormatCommand is null!"); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error in FormatSqlCallback: {ex}"); + System.Windows.Forms.MessageBox.Show($"Error formatting SQL: {ex.Message}", "T-SQL Formatter Error"); + } + } + + private void OptionsCallback(object sender, EventArgs e) + { + ThreadHelper.ThrowIfNotOnUIThread(); + try + { + if (_formatCommand != null) + { + _formatCommand.ShowOptions(); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Error in OptionsCallback: {ex}"); + System.Windows.Forms.MessageBox.Show($"Error showing options: {ex.Message}", "T-SQL Formatter Error"); + } + } + } +} \ No newline at end of file diff --git a/TSqlFormatter.VS2026/FormatterPackage.vsct b/TSqlFormatter.VS2026/FormatterPackage.vsct new file mode 100644 index 0000000..97dd013 --- /dev/null +++ b/TSqlFormatter.VS2026/FormatterPackage.vsct @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TSqlFormatter.VS2026/Guids.cs b/TSqlFormatter.VS2026/Guids.cs new file mode 100644 index 0000000..41b2cbd --- /dev/null +++ b/TSqlFormatter.VS2026/Guids.cs @@ -0,0 +1,13 @@ +using System; + +namespace TSqlFormatter.VS2026 +{ + internal static class Guids + { + public const string PackageGuidString = "2e8f4a3b-9c7d-4e1f-a6b5-8d9e7f6a5c4b"; + public const string CommandSetGuidString = "7b3e2f1a-4d9c-5a6e-b8d7-c9f1e2a3d4b5"; + + public static readonly Guid PackageGuid = new Guid(PackageGuidString); + public static readonly Guid CommandSetGuid = new Guid(CommandSetGuidString); + } +} diff --git a/TSqlFormatter.VS2026/OptionsDialog.cs b/TSqlFormatter.VS2026/OptionsDialog.cs new file mode 100644 index 0000000..e906e16 --- /dev/null +++ b/TSqlFormatter.VS2026/OptionsDialog.cs @@ -0,0 +1,368 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace TSqlFormatter.VS2026 +{ + public partial class OptionsDialog : Form + { + private Settings _settings; + + // Controls + private TabControl tabControl; + private TabPage tabFormatting; + private TabPage tabCase; + + // Formatting Tab Controls + private RadioButton radioIndentTabs; + private RadioButton radioIndentSpaces; + private NumericUpDown numSpacesPerTab; + private NumericUpDown numMaxLineWidth; + private CheckBox chkExpandCommaLists; + private CheckBox chkTrailingCommas; + private CheckBox chkExpandBooleanExpressions; + private CheckBox chkExpandCaseStatements; + private CheckBox chkExpandBetweenConditions; + private CheckBox chkExpandInLists; + private CheckBox chkBreakJoinOnSections; + private CheckBox chkSpaceAfterExpandedComma; + private NumericUpDown numNewClauseLineBreaks; + private NumericUpDown numNewStatementLineBreaks; + + // Case Tab Controls + private CheckBox chkUppercaseKeywords; + private CheckBox chkKeywordStandardization; + + // Dialog Buttons + private Button btnOK; + private Button btnCancel; + private Button btnDefaults; + + public OptionsDialog(Settings settings) + { + _settings = settings; + InitializeComponent(); + LoadSettings(); + } + + private void InitializeComponent() + { + this.SuspendLayout(); + + // Form settings + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(950, 600); + this.Text = "T-SQL Formatter Options (v5 - Properly Sized)"; + this.StartPosition = FormStartPosition.CenterScreen; + this.FormBorderStyle = FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + + // Create tab control + tabControl = new TabControl(); + tabControl.Location = new Point(12, 12); + tabControl.Size = new Size(926, 530); + tabControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + + // Create tabs + CreateFormattingTab(); + CreateCaseTab(); + + tabControl.TabPages.Add(tabFormatting); + tabControl.TabPages.Add(tabCase); + this.Controls.Add(tabControl); + + // Create buttons at the bottom with proper spacing + int buttonY = this.ClientSize.Height - 45; + int buttonHeight = 28; + + btnDefaults = new Button(); + btnDefaults.Text = "Defaults"; + btnDefaults.Size = new Size(100, buttonHeight); + btnDefaults.Location = new Point(12, buttonY); + btnDefaults.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + btnDefaults.Click += BtnDefaults_Click; + this.Controls.Add(btnDefaults); + + btnCancel = new Button(); + btnCancel.Text = "Cancel"; + btnCancel.Size = new Size(100, buttonHeight); + btnCancel.Location = new Point(this.ClientSize.Width - 185, buttonY); + btnCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + btnCancel.DialogResult = DialogResult.Cancel; + this.Controls.Add(btnCancel); + + btnOK = new Button(); + btnOK.Text = "OK"; + btnOK.Size = new Size(100, buttonHeight); + btnOK.Location = new Point(this.ClientSize.Width - 90, buttonY); + btnOK.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + btnOK.DialogResult = DialogResult.OK; + btnOK.Click += BtnOK_Click; + this.Controls.Add(btnOK); + + this.AcceptButton = btnOK; + this.CancelButton = btnCancel; + + this.ResumeLayout(false); + this.PerformLayout(); + } + + private void CreateFormattingTab() + { + tabFormatting = new TabPage("Formatting"); + tabFormatting.AutoScroll = false; + + // No panel needed - add controls directly to tab page + + int y = 10; + int leftMargin = 10; + + // Indentation Section + Label lblIndent = new Label(); + lblIndent.Text = "Indentation:"; + lblIndent.Font = new Font(lblIndent.Font, FontStyle.Bold); + lblIndent.Location = new Point(leftMargin, y); + lblIndent.AutoSize = true; + tabFormatting.Controls.Add(lblIndent); + y += 22; + + radioIndentTabs = new RadioButton(); + radioIndentTabs.Text = "Tabs"; + radioIndentTabs.Location = new Point(leftMargin + 15, y); + radioIndentTabs.Size = new Size(60, 20); + radioIndentTabs.Checked = true; + tabFormatting.Controls.Add(radioIndentTabs); + + radioIndentSpaces = new RadioButton(); + radioIndentSpaces.Text = "Spaces"; + radioIndentSpaces.Location = new Point(leftMargin + 85, y); + radioIndentSpaces.Size = new Size(70, 20); + tabFormatting.Controls.Add(radioIndentSpaces); + y += 22; + + Label lblSpacesPerTab = new Label(); + lblSpacesPerTab.Text = "Spaces per Tab:"; + lblSpacesPerTab.Location = new Point(leftMargin + 15, y + 2); + lblSpacesPerTab.Size = new Size(95, 20); + tabFormatting.Controls.Add(lblSpacesPerTab); + + numSpacesPerTab = new NumericUpDown(); + numSpacesPerTab.Location = new Point(leftMargin + 115, y); + numSpacesPerTab.Size = new Size(50, 20); + numSpacesPerTab.Minimum = 1; + numSpacesPerTab.Maximum = 10; + numSpacesPerTab.Value = 4; + tabFormatting.Controls.Add(numSpacesPerTab); + y += 22; + + Label lblMaxWidth = new Label(); + lblMaxWidth.Text = "Max Line Width:"; + lblMaxWidth.Location = new Point(leftMargin + 15, y + 2); + lblMaxWidth.Size = new Size(95, 20); + tabFormatting.Controls.Add(lblMaxWidth); + + numMaxLineWidth = new NumericUpDown(); + numMaxLineWidth.Location = new Point(leftMargin + 115, y); + numMaxLineWidth.Size = new Size(60, 20); + numMaxLineWidth.Minimum = 50; + numMaxLineWidth.Maximum = 999; + numMaxLineWidth.Value = 999; + tabFormatting.Controls.Add(numMaxLineWidth); + y += 30; + + // Expansion Options Section + Label lblExpansion = new Label(); + lblExpansion.Text = "Expansion Options:"; + lblExpansion.Font = new Font(lblExpansion.Font, FontStyle.Bold); + lblExpansion.Location = new Point(leftMargin, y); + lblExpansion.AutoSize = true; + tabFormatting.Controls.Add(lblExpansion); + y += 22; + + // First row of checkboxes + chkExpandCommaLists = new CheckBox(); + chkExpandCommaLists.Text = "Expand comma lists"; + chkExpandCommaLists.Location = new Point(leftMargin + 15, y); + chkExpandCommaLists.Size = new Size(380, 24); + chkExpandCommaLists.Checked = true; + tabFormatting.Controls.Add(chkExpandCommaLists); + + chkTrailingCommas = new CheckBox(); + chkTrailingCommas.Text = "Use trailing commas"; + chkTrailingCommas.Location = new Point(leftMargin + 420, y); + chkTrailingCommas.Size = new Size(380, 24); + tabFormatting.Controls.Add(chkTrailingCommas); + y += 22; + + // Second row + chkExpandBooleanExpressions = new CheckBox(); + chkExpandBooleanExpressions.Text = "Expand boolean expressions"; + chkExpandBooleanExpressions.Location = new Point(leftMargin + 15, y); + chkExpandBooleanExpressions.Size = new Size(380, 24); + chkExpandBooleanExpressions.Checked = true; + tabFormatting.Controls.Add(chkExpandBooleanExpressions); + + chkExpandCaseStatements = new CheckBox(); + chkExpandCaseStatements.Text = "Expand CASE statements"; + chkExpandCaseStatements.Location = new Point(leftMargin + 420, y); + chkExpandCaseStatements.Size = new Size(380, 24); + chkExpandCaseStatements.Checked = true; + tabFormatting.Controls.Add(chkExpandCaseStatements); + y += 22; + + // Third row + chkExpandBetweenConditions = new CheckBox(); + chkExpandBetweenConditions.Text = "Expand BETWEEN conditions"; + chkExpandBetweenConditions.Location = new Point(leftMargin + 15, y); + chkExpandBetweenConditions.Size = new Size(380, 24); + chkExpandBetweenConditions.Checked = true; + tabFormatting.Controls.Add(chkExpandBetweenConditions); + + chkExpandInLists = new CheckBox(); + chkExpandInLists.Text = "Expand IN lists"; + chkExpandInLists.Location = new Point(leftMargin + 420, y); + chkExpandInLists.Size = new Size(380, 24); + chkExpandInLists.Checked = true; + tabFormatting.Controls.Add(chkExpandInLists); + y += 22; + + // Fourth row + chkBreakJoinOnSections = new CheckBox(); + chkBreakJoinOnSections.Text = "Break JOIN on sections"; + chkBreakJoinOnSections.Location = new Point(leftMargin + 15, y); + chkBreakJoinOnSections.Size = new Size(380, 24); + tabFormatting.Controls.Add(chkBreakJoinOnSections); + + chkSpaceAfterExpandedComma = new CheckBox(); + chkSpaceAfterExpandedComma.Text = "Space after expanded comma"; + chkSpaceAfterExpandedComma.Location = new Point(leftMargin + 420, y); + chkSpaceAfterExpandedComma.Size = new Size(380, 24); + tabFormatting.Controls.Add(chkSpaceAfterExpandedComma); + y += 30; + + // Line Breaks Section + Label lblLineBreaks = new Label(); + lblLineBreaks.Text = "Line Breaks:"; + lblLineBreaks.Font = new Font(lblLineBreaks.Font, FontStyle.Bold); + lblLineBreaks.Location = new Point(leftMargin, y); + lblLineBreaks.AutoSize = true; + tabFormatting.Controls.Add(lblLineBreaks); + y += 22; + + Label lblNewClause = new Label(); + lblNewClause.Text = "New clause breaks:"; + lblNewClause.Location = new Point(leftMargin + 15, y + 2); + lblNewClause.Size = new Size(150, 20); + tabFormatting.Controls.Add(lblNewClause); + + numNewClauseLineBreaks = new NumericUpDown(); + numNewClauseLineBreaks.Location = new Point(leftMargin + 170, y); + numNewClauseLineBreaks.Size = new Size(50, 20); + numNewClauseLineBreaks.Minimum = 0; + numNewClauseLineBreaks.Maximum = 5; + numNewClauseLineBreaks.Value = 1; + tabFormatting.Controls.Add(numNewClauseLineBreaks); + + Label lblNewStatement = new Label(); + lblNewStatement.Text = "New statement breaks:"; + lblNewStatement.Location = new Point(leftMargin + 420, y + 2); + lblNewStatement.Size = new Size(160, 20); + tabFormatting.Controls.Add(lblNewStatement); + + numNewStatementLineBreaks = new NumericUpDown(); + numNewStatementLineBreaks.Location = new Point(leftMargin + 585, y); + numNewStatementLineBreaks.Size = new Size(50, 20); + numNewStatementLineBreaks.Minimum = 0; + numNewStatementLineBreaks.Maximum = 5; + numNewStatementLineBreaks.Value = 2; + tabFormatting.Controls.Add(numNewStatementLineBreaks); + } + + private void CreateCaseTab() + { + tabCase = new TabPage("Case"); + + int y = 20; + int leftMargin = 20; + + chkUppercaseKeywords = new CheckBox(); + chkUppercaseKeywords.Text = "Uppercase SQL keywords"; + chkUppercaseKeywords.Location = new Point(leftMargin, y); + chkUppercaseKeywords.Size = new Size(380, 24); + chkUppercaseKeywords.Checked = true; + tabCase.Controls.Add(chkUppercaseKeywords); + y += 25; + + chkKeywordStandardization = new CheckBox(); + chkKeywordStandardization.Text = "Standardize keywords"; + chkKeywordStandardization.Location = new Point(leftMargin, y); + chkKeywordStandardization.Size = new Size(380, 24); + tabCase.Controls.Add(chkKeywordStandardization); + } + + private void LoadSettings() + { + radioIndentTabs.Checked = _settings.IndentString == "\t"; + radioIndentSpaces.Checked = _settings.IndentString != "\t"; + numSpacesPerTab.Value = _settings.SpacesPerTab; + numMaxLineWidth.Value = _settings.MaxLineWidth; + chkExpandCommaLists.Checked = _settings.ExpandCommaLists; + chkTrailingCommas.Checked = _settings.TrailingCommas; + chkExpandBooleanExpressions.Checked = _settings.ExpandBooleanExpressions; + chkExpandCaseStatements.Checked = _settings.ExpandCaseStatements; + chkExpandBetweenConditions.Checked = _settings.ExpandBetweenConditions; + chkExpandInLists.Checked = _settings.ExpandInLists; + chkBreakJoinOnSections.Checked = _settings.BreakJoinOnSections; + chkSpaceAfterExpandedComma.Checked = _settings.SpaceAfterExpandedComma; + numNewClauseLineBreaks.Value = _settings.NewClauseLineBreaks; + numNewStatementLineBreaks.Value = _settings.NewStatementLineBreaks; + chkUppercaseKeywords.Checked = _settings.UppercaseKeywords; + chkKeywordStandardization.Checked = _settings.KeywordStandardization; + } + + private void BtnOK_Click(object sender, EventArgs e) + { + // Save settings + _settings.IndentString = radioIndentTabs.Checked ? "\t" : new string(' ', (int)numSpacesPerTab.Value); + _settings.SpacesPerTab = (int)numSpacesPerTab.Value; + _settings.MaxLineWidth = (int)numMaxLineWidth.Value; + _settings.ExpandCommaLists = chkExpandCommaLists.Checked; + _settings.TrailingCommas = chkTrailingCommas.Checked; + _settings.ExpandBooleanExpressions = chkExpandBooleanExpressions.Checked; + _settings.ExpandCaseStatements = chkExpandCaseStatements.Checked; + _settings.ExpandBetweenConditions = chkExpandBetweenConditions.Checked; + _settings.ExpandInLists = chkExpandInLists.Checked; + _settings.BreakJoinOnSections = chkBreakJoinOnSections.Checked; + _settings.SpaceAfterExpandedComma = chkSpaceAfterExpandedComma.Checked; + _settings.NewClauseLineBreaks = (int)numNewClauseLineBreaks.Value; + _settings.NewStatementLineBreaks = (int)numNewStatementLineBreaks.Value; + _settings.UppercaseKeywords = chkUppercaseKeywords.Checked; + _settings.KeywordStandardization = chkKeywordStandardization.Checked; + + _settings.SaveSettings(); + } + + private void BtnDefaults_Click(object sender, EventArgs e) + { + // Reset to defaults + radioIndentTabs.Checked = true; + radioIndentSpaces.Checked = false; + numSpacesPerTab.Value = 4; + numMaxLineWidth.Value = 999; + chkExpandCommaLists.Checked = true; + chkTrailingCommas.Checked = false; + chkExpandBooleanExpressions.Checked = true; + chkExpandCaseStatements.Checked = true; + chkExpandBetweenConditions.Checked = true; + chkExpandInLists.Checked = true; + chkBreakJoinOnSections.Checked = false; + chkSpaceAfterExpandedComma.Checked = false; + numNewClauseLineBreaks.Value = 1; + numNewStatementLineBreaks.Value = 2; + chkUppercaseKeywords.Checked = true; + chkKeywordStandardization.Checked = false; + } + } +} \ No newline at end of file diff --git a/TSqlFormatter.VS2026/OptionsDialogWindow.xaml b/TSqlFormatter.VS2026/OptionsDialogWindow.xaml new file mode 100644 index 0000000..e5f9f6b --- /dev/null +++ b/TSqlFormatter.VS2026/OptionsDialogWindow.xaml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +