Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
F
Ftt_Common
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Ftt_Common
Commits
0f91d19b
Commit
0f91d19b
authored
Jun 25, 2026
by
段廷瑞
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
pref: 格式化优化(缩进层级,字符串前导字符识别,换行等)
parent
b6d55a05
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
125 additions
and
37 deletions
+125
-37
wTextFormatter.cs
Tools/UI/wTextFormatter.cs
+125
-37
No files found.
Tools/UI/wTextFormatter.cs
View file @
0f91d19b
...
...
@@ -319,14 +319,19 @@ namespace Tools.UI
}
/// <summary>
/// SQL格式化辅助类
/// 格式化规则参考 db_helper:
/// - 复合关键字(LEFT JOIN, GROUP BY等)作为整体换行
/// - 短字段列表(≤3)内联, 长字段列表(≥4)每字段一行
/// - JOIN + ON 同行
/// - WHERE 首个条件与WHERE同行, 后续AND/OR换行缩进
/// - GROUP BY / ORDER BY 紧凑同行
/// - 子查询缩进
/// SQL格式化辅助类 — 支持 SqlServer / MySQL / PostgreSQL / Oracle / SQLite
///
/// 核心规则:
/// 1. 复合关键字整体换行:LEFT JOIN, GROUP BY, ORDER BY, UNION ALL 等合并为单个 Token
/// 2. 子句首尾同层级:SELECT / FROM / WHERE / GROUP BY / ORDER BY / HAVING 同级缩进
/// 3. 子句内容缩进一层(4 空格),AND/OR 与内容同级
/// 4. 左括号开始层级、右括号结束层级,括号内逗号不换行(函数参数同行)
/// 5. 子查询在前序基础上递进缩进(子查询 SELECT 缩进于括号层级)
/// 6. JOIN + ON 同行;JOIN / ON 条件中的 AND/OR 同行
/// 7. WHERE 首个条件同行,后续 AND/OR 换行缩进(与 WHERE 内容同级)
/// 8. 语义段不拆行:BETWEEN...AND、N'...'、isnull(a, '') 等保持完整
/// 9. 字符串前缀 N 与紧随的字面量之间不插入空格
/// 10. GROUP BY / ORDER BY 紧凑同行
/// </summary>
private
static
class
SqlFormatHelper
{
...
...
@@ -403,6 +408,8 @@ namespace Tools.UI
"ILIKE"
,
"LATERAL"
,
"CONNECT"
,
"START"
,
"PRIOR"
,
"NOCYCLE"
,
// 字符串前缀(各方言)
"N"
,
"E"
,
"X"
,
"B"
,
"Q"
,
};
/// <summary>
...
...
@@ -826,17 +833,26 @@ namespace Tools.UI
}
/// <summary>
/// 将 Token 列表格式化为
db_helper 风格 SQL
/// 将 Token 列表格式化为
统一风格 SQL(适配多方言)
/// </summary>
private
static
StringBuilder
FormatTokens
(
List
<
Token
>
tokens
)
{
var
sb
=
new
StringBuilder
();
int
indent
=
0
;
int
indent
=
0
;
// 括号嵌套缩进(仅受 OpenParen/CloseParen 影响)
int
clauseBase
=
0
;
// 当前子句基准缩进(子查询内缩进基于此前序)
int
clauseParenBase
=
0
;
// clauseBase 设定时的 parenDepth(计算局部嵌套)
var
clauseBaseStack
=
new
System
.
Collections
.
Generic
.
Stack
<
int
>();
// 子查询退出时恢复外层 clauseBase
var
clauseParenStack
=
new
System
.
Collections
.
Generic
.
Stack
<
int
>();
// 同步恢复 clauseParenBase
int
parenDepth
=
0
;
// 括号嵌套深度(函数参数内逗号不换行)
bool
seenBetween
=
false
;
// BETWEEN...AND 视为语义段不拆行
bool
newline
=
true
;
TokenType
prevType
=
TokenType
.
Whitespace
;
string
prevValue
=
""
;
ClauseCtx
ctx
=
ClauseCtx
.
None
;
// 局部函数:计算内容缩进 = clauseBase + IndentSize * (局部嵌套层数 + 1)
int
ContentIndent
()
=>
clauseBase
+
IndentSize
*
(
parenDepth
-
clauseParenBase
+
1
);
int
maxIdx
=
tokens
.
Count
;
for
(
int
ti
=
0
;
ti
<
maxIdx
;
ti
++)
{
...
...
@@ -866,9 +882,9 @@ namespace Tools.UI
{
if
(
ctx
==
ClauseCtx
.
WhereClause
)
{
//
db_helper: WHERE 首个条件同行, AND/OR 换行
缩进
//
WHERE 内 AND/OR 与条件首行同级
缩进
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
indent
+
IndentSize
);
WriteIndent
(
sb
,
ref
newline
,
ContentIndent
()
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
...
...
@@ -889,11 +905,24 @@ namespace Tools.UI
continue
;
}
// 其他位置: 换行缩进
if
(
seenBetween
&&
kw
==
"AND"
)
{
// BETWEEN...AND 语义段内不换行
if
(
sb
.
Length
>
0
&&
sb
[
sb
.
Length
-
1
]
!=
' '
)
sb
.
Append
(
' '
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
seenBetween
=
false
;
}
else
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
indent
+
IndentSize
);
WriteIndent
(
sb
,
ref
newline
,
ContentIndent
()
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
}
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
continue
;
...
...
@@ -917,7 +946,7 @@ namespace Tools.UI
if
(
kw
==
"WHEN"
||
kw
==
"ELSE"
)
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
indent
+
IndentSize
);
WriteIndent
(
sb
,
ref
newline
,
ContentIndent
()
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
...
...
@@ -938,7 +967,7 @@ namespace Tools.UI
if
(
kw
==
"END"
)
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
indent
);
WriteIndent
(
sb
,
ref
newline
,
clauseBase
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
...
...
@@ -951,10 +980,11 @@ namespace Tools.UI
if
(
IsJoinKeyword
(
kw
))
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
IndentSiz
e
);
WriteIndent
(
sb
,
ref
newline
,
clauseBas
e
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
ctx
=
ClauseCtx
.
JoinClause
;
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
...
...
@@ -965,10 +995,11 @@ namespace Tools.UI
if
(
kw
==
"SET"
||
kw
==
"VALUES"
)
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
IndentSiz
e
);
WriteIndent
(
sb
,
ref
newline
,
clauseBas
e
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
ctx
=
kw
==
"SET"
?
ClauseCtx
.
SetClause
:
ClauseCtx
.
ValuesClause
;
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
...
...
@@ -979,10 +1010,11 @@ namespace Tools.UI
if
(
kw
==
"GROUP BY"
||
kw
==
"ORDER BY"
||
kw
==
"HAVING"
)
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
IndentSiz
e
);
WriteIndent
(
sb
,
ref
newline
,
clauseBas
e
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
ctx
=
kw
==
"GROUP BY"
?
ClauseCtx
.
GroupBy
:
ClauseCtx
.
OrderBy
;
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
...
...
@@ -993,10 +1025,11 @@ namespace Tools.UI
if
(
kw
==
"WHERE"
)
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
IndentSiz
e
);
WriteIndent
(
sb
,
ref
newline
,
clauseBas
e
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
ctx
=
ClauseCtx
.
WhereClause
;
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
...
...
@@ -1007,10 +1040,11 @@ namespace Tools.UI
if
(
kw
==
"FROM"
)
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
IndentSiz
e
);
WriteIndent
(
sb
,
ref
newline
,
clauseBas
e
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
ctx
=
ClauseCtx
.
FromClause
;
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
...
...
@@ -1021,11 +1055,18 @@ namespace Tools.UI
if
(
IsTopLevelKeyword
(
kw
))
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
0
);
// 子查询内保存外层 clauseBase/clauseParenBase,退出括号时恢复
if
(
parenDepth
>
0
)
{
clauseBaseStack
.
Push
(
clauseBase
);
clauseParenStack
.
Push
(
clauseParenBase
);
}
clauseBase
=
indent
;
clauseParenBase
=
parenDepth
;
WriteIndent
(
sb
,
ref
newline
,
clauseBase
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
indent
=
IndentSize
;
ctx
=
kw
==
"SELECT"
?
ClauseCtx
.
SelectList
:
ClauseCtx
.
None
;
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
...
...
@@ -1038,11 +1079,10 @@ namespace Tools.UI
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
sb
.
AppendLine
();
newline
=
true
;
WriteIndent
(
sb
,
ref
newline
,
0
);
WriteIndent
(
sb
,
ref
newline
,
clauseBase
);
sb
.
Append
(
kw
);
sb
.
AppendLine
();
newline
=
true
;
indent
=
IndentSize
;
ctx
=
ClauseCtx
.
None
;
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
...
...
@@ -1053,7 +1093,7 @@ namespace Tools.UI
if
(
kw
==
"LIMIT"
||
kw
==
"OFFSET"
||
kw
==
"FETCH NEXT"
)
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
IndentSiz
e
);
WriteIndent
(
sb
,
ref
newline
,
clauseBas
e
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
...
...
@@ -1066,10 +1106,11 @@ namespace Tools.UI
if
(
ClauseStarters
.
Contains
(
kw
))
{
if
(!
newline
)
{
sb
.
AppendLine
();
newline
=
true
;
}
WriteIndent
(
sb
,
ref
newline
,
indent
);
WriteIndent
(
sb
,
ref
newline
,
clauseBase
);
sb
.
Append
(
kw
);
sb
.
Append
(
' '
);
newline
=
false
;
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
continue
;
...
...
@@ -1079,7 +1120,12 @@ namespace Tools.UI
if
(
NeedsLeadingSpace
(
prevType
))
sb
.
Append
(
' '
);
sb
.
Append
(
kw
);
// 字符串前缀(N等)与紧随的字符串字面量之间不能有空格
if
(!
IsStringPrefix
(
kw
)
||
!
NextIsStringLiteral
(
tokens
,
ti
))
sb
.
Append
(
' '
);
// 标记 BETWEEN...AND 语义段
if
(
kw
==
"BETWEEN"
)
seenBetween
=
true
;
newline
=
false
;
prevType
=
TokenType
.
Keyword
;
prevValue
=
kw
;
...
...
@@ -1089,9 +1135,18 @@ namespace Tools.UI
case
TokenType
.
Comma
:
{
sb
.
Append
(
','
);
// 逗号后换行缩进
if
(
parenDepth
>
0
)
{
// 函数参数内逗号不换行
sb
.
Append
(
' '
);
newline
=
false
;
}
else
{
// 顶层逗号(SELECT 字段列表等)换行缩进
sb
.
AppendLine
();
newline
=
true
;
}
prevType
=
TokenType
.
Comma
;
prevValue
=
","
;
break
;
...
...
@@ -1100,6 +1155,7 @@ namespace Tools.UI
case
TokenType
.
OpenParen
:
sb
.
Append
(
'('
);
indent
=
Math
.
Min
(
indent
+
IndentSize
,
MaxIndent
);
parenDepth
++;
newline
=
false
;
prevType
=
TokenType
.
OpenParen
;
prevValue
=
"("
;
...
...
@@ -1107,6 +1163,13 @@ namespace Tools.UI
case
TokenType
.
CloseParen
:
indent
=
Math
.
Max
(
0
,
indent
-
IndentSize
);
parenDepth
=
Math
.
Max
(
0
,
parenDepth
-
1
);
// 恢复外层 clauseBase/clauseParenBase(子查询退出时)
if
(
clauseBaseStack
.
Count
>
parenDepth
)
{
clauseBase
=
clauseBaseStack
.
Pop
();
clauseParenBase
=
clauseParenStack
.
Pop
();
}
if
(
newline
)
{
WriteIndent
(
sb
,
ref
newline
,
indent
);
}
sb
.
Append
(
')'
);
newline
=
false
;
...
...
@@ -1137,7 +1200,7 @@ namespace Tools.UI
case
TokenType
.
BacktickIdentifier
:
case
TokenType
.
Number
:
case
TokenType
.
Identifier
:
if
(
newline
)
{
WriteIndent
(
sb
,
ref
newline
,
indent
);
}
if
(
newline
)
{
WriteIndent
(
sb
,
ref
newline
,
ContentIndent
()
);
}
// 仅由当前 token 根据前驱类型决定是否补前导空格
// Keyword/Operator 已在自身末尾追加空格,故排除,避免双空格
if
(
NeedsLeadingSpace
(
prevType
))
...
...
@@ -1149,7 +1212,7 @@ namespace Tools.UI
break
;
case
TokenType
.
Operator
:
if
(
newline
)
{
WriteIndent
(
sb
,
ref
newline
,
indent
);
}
if
(
newline
)
{
WriteIndent
(
sb
,
ref
newline
,
ContentIndent
()
);
}
sb
.
Append
(
' '
);
sb
.
Append
(
token
.
Value
);
sb
.
Append
(
' '
);
...
...
@@ -1167,7 +1230,7 @@ namespace Tools.UI
break
;
default
:
if
(
newline
)
{
WriteIndent
(
sb
,
ref
newline
,
indent
);
}
if
(
newline
)
{
WriteIndent
(
sb
,
ref
newline
,
ContentIndent
()
);
}
sb
.
Append
(
token
.
Value
);
newline
=
false
;
prevType
=
token
.
Type
;
...
...
@@ -1230,6 +1293,31 @@ namespace Tools.UI
||
kw
==
"DECLARE"
||
kw
==
"BEGIN"
||
kw
==
"GO"
||
kw
==
"RETURN"
||
kw
==
"IF"
;
}
/// <summary>
/// 关键字是否为字符串前缀,各方言:
/// SqlServer: N
/// MySQL: N, X, B
/// PostgreSQL: E, B, X
/// Oracle: N, Q
/// SQLite: N, X, B
/// </summary>
private
static
bool
IsStringPrefix
(
string
kw
)
{
return
kw
==
"N"
||
kw
==
"E"
||
kw
==
"X"
||
kw
==
"B"
||
kw
==
"Q"
;
}
/// <summary>当前关键字后紧跟的下一个有效 token 是否为字符串字面量</summary>
private
static
bool
NextIsStringLiteral
(
List
<
Token
>
tokens
,
int
currentIndex
)
{
for
(
int
i
=
currentIndex
+
1
;
i
<
tokens
.
Count
;
i
++)
{
if
(
tokens
[
i
].
Type
==
TokenType
.
Whitespace
)
continue
;
return
tokens
[
i
].
Type
==
TokenType
.
StringLiteral
;
}
return
false
;
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment