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

+25
View File
@@ -1465,6 +1465,31 @@ Test-Case '按键归一化与输入缝:映射可无控制台测,序列用尽
Assert-True $threw2 '没有任何输入来源时必须抛错'
}
Test-Case 'TUI 文本对齐:按列宽补齐/裁剪,绝不切开宽字符,无控制台下不抛错' {
# 补齐:按列宽而不是字符数
$padded = Format-BakNRetPaddedText -Text '中文' -Width 10
Assert-Equal 10 (Get-BakNRetCellWidth -Text $padded) '补齐后应当恰好 10 列'
Assert-Equal 8 $padded.Length '10 列 = 8 个字符(两个汉字各占 2 列)'
Assert-Equal 'AB ' (Format-BakNRetPaddedText -Text 'AB' -Width 5) 'ASCII 补齐'
# 裁剪:'中文字' 是 6 列,裁到 5 列必须停在 '中文'(4 列)+ 1 个空格,
# 而不是把第三个汉字劈成半个 —— 那会显示成乱码。
$clipped = Format-BakNRetPaddedText -Text '中文字' -Width 5
Assert-Equal 5 (Get-BakNRetCellWidth -Text $clipped) '裁剪后应当恰好 5 列'
Assert-Equal 3 $clipped.Length '3 个字符(中文 + 1 空格),不是 4'
Assert-Equal '中' $clipped.Substring(0, 1) '前两个汉字保留'
Assert-Equal ' ' $clipped.Substring(2, 1) '剩下的 1 列用空格补'
# 边界:宽度 0 / 负数返回空串;空文本也能补齐
Assert-Equal 0 (Format-BakNRetPaddedText -Text 'abc' -Width 0).Length '宽度 0 返回空串'
Assert-Equal 4 (Format-BakNRetPaddedText -Text '' -Width 4).Length '空文本补成纯空格'
# 无控制台(本进程的真实环境就是重定向)+ 越界坐标:不能抛错
$threw = $false
try { Write-BakNRetAt -X 9999 -Y 9999 -Text 'x' } catch { $threw = $true }
Assert-True (-not $threw) '没有控制台且坐标越界时也不能抛错(TUI 画得难看,好过运行失败)'
}
# ============================================================================
Remove-Item -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $itemSandbox -Recurse -Force -ErrorAction SilentlyContinue