Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ 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 a streamlined solution:
- `TSqlFormatter.SSMS21.sln` - Streamlined solution for SSMS 21+ (2 projects: Core + SSMS)
- Supports SSMS 21, 22, and later versions
- `TSqlFormatter.SSMS21.sln` - Streamlined solution (3 projects: Core + SSMS + VS2026)
- Supports SSMS 21+ and Visual Studio 2022+

## Build Commands

### Building the Solution
```bash
# Build the SSMS solution (requires Visual Studio 2022+ or MSBuild)
# Supports SSMS 21, 22, and later versions
# Build the solution (creates both SSMS and VS extensions)
msbuild TSqlFormatter.SSMS21.sln /p:Configuration=Release /p:Platform="Any CPU"

# Restore NuGet packages before building (if needed)
msbuild /t:Restore TSqlFormatter.SSMS21.sln

# Build the SSMS package directly
# Build specific packages
msbuild TSqlFormatter.SSMS\TSqlFormatter.SSMS.csproj /p:Configuration=Release
msbuild TSqlFormatter.VS2026\TSqlFormatter.VS2026.csproj /p:Configuration=Release
```

### Running Tests
Expand Down Expand Up @@ -65,8 +65,16 @@ msbuild TSqlFormatter.SSMS\TSqlFormatter.SSMS.csproj /p:Configuration=Release
- 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="Microsoft.VisualStudio.Ssms"` with version range like `[21.0,)` for SSMS 21+ compatibility. The open-ended range automatically supports future versions including SSMS 22.
- **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:
Expand Down
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -34,19 +40,21 @@ The project has been restructured into a modern, maintainable architecture:

#### Build Commands:
```bash
# Build the streamlined SSMS solution (supports SSMS 21, 22, and later)
# 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
Expand All @@ -55,21 +63,14 @@ msbuild TSqlFormatter.SSMS21.sln /p:Configuration=Release
- **Tools > Format T-SQL Code** (or Ctrl+K, Ctrl+F)
- **Tools > T-SQL Formatter Options...** for settings

### Debugging the Extension

To debug the extension in Visual Studio:

1. Open the solution in Visual Studio 2022 or later
2. Set `TSqlFormatter.SSMS` as the startup project
3. Select a debug profile from the dropdown:
- **SSMS 21 Debug** or **SSMS 22 Debug** for default installation paths
- **SSMS - Custom Path** if SSMS is installed in a non-standard location
4. If using a custom path, update `TSqlFormatter.SSMS\Properties\launchSettings.json`:
- Edit the `executablePath` to point to your SSMS installation
- Example: `C:\Custom\Path\SSMS\Common7\IDE\Ssms.exe`
5. Press F5 to start debugging

The extension will load into an experimental instance of SSMS with `/rootsuffix Exp`.
#### 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

Expand Down
6 changes: 6 additions & 0 deletions TSqlFormatter.SSMS21.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
103 changes: 103 additions & 0 deletions TSqlFormatter.VS2026/FormatCommand.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
109 changes: 109 additions & 0 deletions TSqlFormatter.VS2026/FormatterPackage.cs
Original file line number Diff line number Diff line change
@@ -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<ServiceProgressData> 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");
}
}
}
}
Loading
Loading