feat(tui): 定位写与按列对齐(第一轮第三块)

Format-BakNRetPaddedText:按**列宽**补齐或裁剪,且绝不切开宽字符 —— 剩余宽度差 1 列而下一个字符要占 2 列时,停下用空格补那 1 列。返回的字符串保证恰好 N 列,这条不变式是画边框的基础(按 .Length 补会让边框歪,按 .Length 裁会把汉字劈成半个显示成乱码)。

Write-BakNRetAt:第一道闸门是 [Console]::IsOutputRedirected,**不是** $Host.UI.SupportsVirtualTerminal(实测后者无控制台时仍返回 True,而 SetCursorPosition 会抛异常)。没有控制台就完全不定位、只把文本写出去;坐标越界时跳过定位但仍写出文本 —— 计划任务与窄窗口不该让 TUI 崩掉,只是画得难看一点。颜色走 Write-Host -ForegroundColor(零 VT 依赖)。

验收:断言 10 条(含"裁剪后恰好 5 列且不是 4 个字符"这条关键判据);两版手工输出一致;test.ps1 9/9 全绿。
This commit is contained in:
Shuery committed 2026-09-27 15:52:34 +08:00
1 parent f2959a4279
commit d9d7967e64
5 files changed
+126

No files matched your search

@@ -0,0 +1,49 @@
function Format-BakNRetPaddedText {
<#
.SYNOPSIS
把文本按**列宽**补齐或裁剪到指定宽度。
.DESCRIPTION
为什么不能用 `.PadRight()` / `.Substring()`:中文 1 个 char 占 2 列。按字符数补,边框会歪;
按字符数裁,会把一个汉字劈成半个、显示成乱码。
这里一律按 Get-BakNRetCellWidth 算,并且**绝不切开一个宽字符** —— 当剩余宽度差 1 列而下一个
字符要占 2 列时,就停下并用空格补上那 1 列。返回的字符串保证**恰好 Width 列**(Width ≤ 0 时
返回空串),这条不变式是画边框与对齐的基础。
#>
param(
[Parameter(Mandatory = $true)][AllowEmptyString()][string]$Text,
[Parameter(Mandatory = $true)][int]$Width
)
if ($Width -le 0) { return '' }
$textWidth = Get-BakNRetCellWidth -Text $Text
if ($textWidth -le $Width) {
return $Text + (' ' * ($Width - $textWidth))
}
# 超宽:按列裁剪,绝不切开代理对或宽字符
$kept = ''
$used = 0
$index = 0
while ($index -lt $Text.Length) {
$chunk = [string]$Text[$index]
$high = [int]$Text[$index]
if ($high -ge 0xD800 -and $high -le 0xDBFF -and ($index + 1) -lt $Text.Length) {
$low = [int]$Text[$index + 1]
if ($low -ge 0xDC00 -and $low -le 0xDFFF) {
$chunk = $Text.Substring($index, 2)
$index++
}
}
$index++
$chunkWidth = Get-BakNRetCellWidth -Text $chunk
if (($used + $chunkWidth) -gt $Width) { break }
$kept += $chunk
$used += $chunkWidth
}
return $kept + (' ' * ($Width - $used))
}