diff --git a/AxialSqlTools/Commands/FormatQueryDialog/FormatOptionsDialog.xaml b/AxialSqlTools/Commands/FormatQueryDialog/FormatOptionsDialog.xaml
index bba808e..6cbdea9 100644
--- a/AxialSqlTools/Commands/FormatQueryDialog/FormatOptionsDialog.xaml
+++ b/AxialSqlTools/Commands/FormatQueryDialog/FormatOptionsDialog.xaml
@@ -69,6 +69,10 @@
Content="Break sproc definition parameters per line"
Checked="formatSetting_Checked"
Unchecked="formatSetting_Unchecked"/>
+
SprocDefinitionsCreate = new List();
public List SprocDefinitionsAlter = new List();
public List SprocDefinitionsCreateAlter = new List();
- public List SelectsWithTop = new List();
+ public List SelectsWithTopOrDistinct = new List();
public override void ExplicitVisit(QualifiedJoin node)
{
@@ -197,8 +197,8 @@ public override void ExplicitVisit(DeclareVariableStatement node)
public override void ExplicitVisit(QuerySpecification node)
{
base.ExplicitVisit(node);
- if (node.TopRowFilter != null)
- SelectsWithTop.Add(node);
+ if (node.TopRowFilter != null || node.UniqueRowFilter == UniqueRowFilter.Distinct)
+ SelectsWithTopOrDistinct.Add(node);
}
}
@@ -836,43 +836,146 @@ void SplitParams(IList parameters)
}
- // special case #11 - split SELECT fields after TOP, fixed indent up to FROM
- // TODO - can't get this right, so disabled for now
+ // special case #11 - split SELECT fields after DISTINCT/TOP, fixed indent up to FROM
if (formatSettings.breakSelectFieldsAfterTopAndUnindent)
{
- //var tokens = sqlFragment.ScriptTokenStream;
- //const string indent = "\t";
+ var tokens = sqlFragment.ScriptTokenStream;
+ // Keep field indentation predictable: always one indent level past the SELECT line.
+ // Prefer tabs when the surrounding block already uses tabs; otherwise fall back to 4 spaces.
+ string indentAfterSelect = "\t";
- //foreach (var qs in visitor.SelectsWithTop)
- //{
- // // 1) break right after TOP(...) into "\r\n\t"
- // int splitIdx = qs.TopRowFilter.LastTokenIndex + 1;
- // if (splitIdx < tokens.Count
- // && tokens[splitIdx].TokenType == TSqlTokenType.WhiteSpace)
- // {
- // tokens[splitIdx].Text = "\r\n" + indent;
- // }
-
- // //// 2) for every selectâelement after the first, break before it into "\r\n\t"
- // //for (int i = 1; i < qs.SelectElements.Count; i++)
- // //{
- // // var elem = qs.SelectElements[i];
- // // int wsIdx = elem.FirstTokenIndex - 1;
- // // if (wsIdx >= 0
- // // && tokens[wsIdx].TokenType == TSqlTokenType.WhiteSpace)
- // // {
- // // tokens[wsIdx].Text = "\r\n" + indent;
- // // }
- // //}
-
- // //// 3) unindent FROM back to column 1
- // //int wsBeforeFrom = qs.FromClause.FirstTokenIndex - 1;
- // //if (wsBeforeFrom >= 0
- // // && tokens[wsBeforeFrom].TokenType == TSqlTokenType.WhiteSpace)
- // //{
- // // tokens[wsBeforeFrom].Text = "\r\n";
- // //}
- //}
+ string ExtractLineIndent(string whitespace)
+ {
+ int lastNl = whitespace.LastIndexOf("\r\n");
+ return lastNl >= 0 ? whitespace.Substring(lastNl + 2) : whitespace;
+ }
+
+ void ReduceInnerIndent(int startIdx, int endIdx, string indentUnit, int times)
+ {
+ if (times <= 0 || string.IsNullOrEmpty(indentUnit))
+ return;
+
+ for (int i = startIdx; i <= endIdx && i < tokens.Count; i++)
+ {
+ if (tokens[i].TokenType != TSqlTokenType.WhiteSpace)
+ continue;
+
+ string ws = tokens[i].Text;
+ for (int n = 0; n < times; n++)
+ {
+ ws = RemoveOneIndent(ws, indentUnit);
+ }
+ tokens[i].Text = ws;
+ }
+ }
+
+ string SingleLineIndent(string indent)
+ {
+ // collapse any accidental blank lines and keep a single newline + indent
+ return "\r\n" + (indent ?? string.Empty);
+ }
+
+ string CollapseBlankLines(string ws)
+ {
+ if (string.IsNullOrEmpty(ws))
+ return ws;
+
+ var lines = ws.Split(new[] { "\r\n" }, StringSplitOptions.None);
+ List compact = new List();
+ bool previousBlank = false;
+
+ foreach (var line in lines)
+ {
+ bool isBlank = string.IsNullOrWhiteSpace(line);
+
+ if (isBlank)
+ {
+ if (previousBlank)
+ continue;
+
+ compact.Add(string.Empty);
+ }
+ else
+ {
+ compact.Add(line);
+ }
+
+ previousBlank = isBlank;
+ }
+
+ return string.Join("\r\n", compact);
+ }
+
+ void CollapseRangeWhitespace(int startIdx, int endIdx)
+ {
+ for (int i = startIdx; i <= endIdx && i < tokens.Count; i++)
+ {
+ if (i < 0)
+ continue;
+
+ if (tokens[i].TokenType == TSqlTokenType.WhiteSpace)
+ {
+ tokens[i].Text = CollapseBlankLines(tokens[i].Text);
+ }
+ }
+ }
+
+ foreach (var qs in visitor.SelectsWithTopOrDistinct)
+ {
+ if (qs.SelectElements == null || qs.SelectElements.Count == 0)
+ continue;
+
+ int normalizeStart = Math.Max(0, qs.SelectElements[0].FirstTokenIndex - 1);
+ int normalizeEnd = qs.FromClause?.FirstTokenIndex - 1 ?? qs.LastTokenIndex;
+ CollapseRangeWhitespace(normalizeStart, normalizeEnd);
+
+ string baseIndent = "";
+ int wsBeforeSelect = qs.FirstTokenIndex - 1;
+ if (wsBeforeSelect >= 0 && tokens[wsBeforeSelect].TokenType == TSqlTokenType.WhiteSpace)
+ {
+ baseIndent = ExtractLineIndent(tokens[wsBeforeSelect].Text);
+ }
+
+ string indentUnit = baseIndent.Contains("\t") ? indentAfterSelect : new string(' ', 4);
+ string fieldIndent = baseIndent + indentUnit;
+
+ void ReplaceWhitespaceWithIndent(int idx, string indent)
+ {
+ if (idx >= 0
+ && idx < tokens.Count
+ && tokens[idx].TokenType == TSqlTokenType.WhiteSpace)
+ {
+ string original = tokens[idx].Text;
+ string originalIndent = ExtractLineIndent(original);
+ tokens[idx].Text = SingleLineIndent(indent);
+
+ // If we are pulling the element to the left, keep nested structure
+ // aligned by reducing inner whitespace by the same indent units.
+ if (originalIndent.Length > indent.Length)
+ {
+ int diff = originalIndent.Length - indent.Length;
+ int unitSize = Math.Max(1, indentUnit.Length);
+ int times = diff / unitSize;
+ ReduceInnerIndent(idx + 1, qs.FromClause?.FirstTokenIndex - 1 ?? qs.LastTokenIndex, indentUnit, times);
+ }
+ }
+ }
+
+ int firstFieldWs = qs.SelectElements[0].FirstTokenIndex - 1;
+ ReplaceWhitespaceWithIndent(firstFieldWs, fieldIndent);
+
+ for (int i = 1; i < qs.SelectElements.Count; i++)
+ {
+ int wsIdx = qs.SelectElements[i].FirstTokenIndex - 1;
+ ReplaceWhitespaceWithIndent(wsIdx, fieldIndent);
+ }
+
+ if (qs.FromClause != null)
+ {
+ int wsBeforeFrom = qs.FromClause.FirstTokenIndex - 1;
+ ReplaceWhitespaceWithIndent(wsBeforeFrom, baseIndent);
+ }
+ }
}
// return full recompiled result
diff --git a/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml b/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml
index cce5183..4e79109 100644
--- a/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml
+++ b/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml
@@ -363,9 +363,8 @@
Content="Break sproc definition parameters per line"
Checked="formatSetting_Checked"
Unchecked="formatSetting_Unchecked"/>
-
-
diff --git a/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml.cs b/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml.cs
index 89a965e..00309e5 100644
--- a/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml.cs
+++ b/AxialSqlTools/WindowSettings/SettingsWindowControl.xaml.cs
@@ -102,9 +102,9 @@ private void LoadSavedSettings()
BreakSprocParametersPerLine.IsChecked = tsqlCodeFormatSettings.breakSprocParametersPerLine;
UppercaseBuiltInFunctions.IsChecked = tsqlCodeFormatSettings.uppercaseBuiltInFunctions;
UnindentBeginEndBlocks.IsChecked = tsqlCodeFormatSettings.unindentBeginEndBlocks;
- BreakVariableDefinitionsPerLine.IsChecked = tsqlCodeFormatSettings.breakVariableDefinitionsPerLine;
+ BreakVariableDefinitionsPerLine.IsChecked = tsqlCodeFormatSettings.breakVariableDefinitionsPerLine;
BreakSprocDefinitionParametersPerLine.IsChecked = tsqlCodeFormatSettings.breakSprocDefinitionParametersPerLine;
- // BreakSelectFieldsAfterTopAndUnindent.IsChecked = tsqlCodeFormatSettings.breakSelectFieldsAfterTopAndUnindent;
+ BreakSelectFieldsAfterTopAndUnindent.IsChecked = tsqlCodeFormatSettings.breakSelectFieldsAfterTopAndUnindent;
OpenAiApiKey.Password = SettingsManager.GetOpenAiApiKey();
@@ -327,7 +327,7 @@ private void Button_SaveApplyAdditionalFormat_Click(object sender, RoutedEventAr
unindentBeginEndBlocks = UnindentBeginEndBlocks.IsChecked.GetValueOrDefault(false),
breakVariableDefinitionsPerLine = BreakVariableDefinitionsPerLine.IsChecked.GetValueOrDefault(false),
breakSprocDefinitionParametersPerLine = BreakSprocDefinitionParametersPerLine.IsChecked.GetValueOrDefault(false),
- // breakSelectFieldsAfterTopAndUnindent = BreakSelectFieldsAfterTopAndUnindent.IsChecked.GetValueOrDefault(false)
+ breakSelectFieldsAfterTopAndUnindent = BreakSelectFieldsAfterTopAndUnindent.IsChecked.GetValueOrDefault(false)
};
SettingsManager.SaveTSqlCodeFormatSettings(settings);
@@ -515,7 +515,7 @@ private void formatTSqlExample()
unindentBeginEndBlocks = UnindentBeginEndBlocks.IsChecked.GetValueOrDefault(false),
breakVariableDefinitionsPerLine = BreakVariableDefinitionsPerLine.IsChecked.GetValueOrDefault(false),
breakSprocDefinitionParametersPerLine = BreakSprocDefinitionParametersPerLine.IsChecked.GetValueOrDefault(false),
- // breakSelectFieldsAfterTopAndUnindent = BreakSelectFieldsAfterTopAndUnindent.IsChecked.GetValueOrDefault(false)
+ breakSelectFieldsAfterTopAndUnindent = BreakSelectFieldsAfterTopAndUnindent.IsChecked.GetValueOrDefault(false)
};
FormattedQueryPreview.Text = TSqlFormatter.FormatCode(SourceQueryPreview.Text, settings);