Skip to content

Commit 60663a9

Browse files
committed
🐛 CJK input
1 parent b7b530c commit 60663a9

3 files changed

Lines changed: 38 additions & 19 deletions

File tree

tui/dashboard.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"deepx/agent"
55
"fmt"
66
"os"
7-
"runtime"
87
"strings"
98
"time"
109

@@ -38,15 +37,14 @@ func padLinesToWidth(content string, w int) string {
3837
return strings.Join(lines, "\n")
3938
}
4039

40+
// graphemeWidthMode 决定显示宽度按 grapheme / Unicode-core(DEC mode 2027)还是 wcwidth 口径算。
41+
// detectGraphemeMode() 只是初始猜测;真正口径由终端对 mode 2027 的真实应答(ModeReportMsg)
42+
// 在运行时校正(见 model.go 的 applyUnicodeCoreReport)。deepx 自有排版(分割线/横幅等,经
43+
// lineDisplayWidth)和 bubbletea cellbuf(textarea 渲染)都跟着终端真实能力走 —— 否则在不支持
44+
// 2027 的终端(Windows conhost / 传统 PowerShell)强行按 grapheme 算,会与终端实际渲染错位,
45+
// 表现为输入框在 ASCII 间插入宽字符时光标后内容重复(issue #113)。
4146
var graphemeWidthMode = detectGraphemeMode()
4247

43-
var widthFunc = func() func(string) int {
44-
if graphemeWidthMode {
45-
return ansi.StringWidth
46-
}
47-
return ansi.StringWidthWc
48-
}()
49-
5048
func detectGraphemeMode() bool {
5149
switch os.Getenv("TERM_PROGRAM") {
5250
case "vscode", "Apple_Terminal", "iTerm.app", "WezTerm", "ghostty":
@@ -61,14 +59,21 @@ func detectGraphemeMode() bool {
6159
if os.Getenv("VTE_VERSION") != "" || os.Getenv("KONSOLE_VERSION") != "" {
6260
return true
6361
}
64-
if runtime.GOOS == "windows" || os.Getenv("WT_SESSION") != "" {
62+
// Windows Terminal:现代版支持 2027,先乐观猜 true,真实应答若不支持会下调到 wcwidth。
63+
// 注意:不再因 runtime.GOOS == "windows" 一律默认 true —— conhost / 传统 PowerShell 不支持
64+
// 2027,默认 false 走 wcwidth 才与终端实际渲染一致(issue #113)。
65+
if os.Getenv("WT_SESSION") != "" {
6566
return true
6667
}
6768
return false
6869
}
6970

71+
// lineDisplayWidth 每次按当前 graphemeWidthMode 现取口径,运行时被 ModeReport 校正后即时生效。
7072
func lineDisplayWidth(s string) int {
71-
return widthFunc(s)
73+
if graphemeWidthMode {
74+
return ansi.StringWidth(s)
75+
}
76+
return ansi.StringWidthWc(s)
7277
}
7378

7479
// isWhitespaceLike 判断 rune 是否是已经能起字符边界作用的空白。

tui/dashboard_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ func TestDetectGraphemeMode(t *testing.T) {
2222
{"WindowsTerminal → grapheme", map[string]string{"WT_SESSION": "some-guid"}, true},
2323
{"GNOME/VTE → grapheme", map[string]string{"VTE_VERSION": "7200"}, true},
2424
{"Konsole → grapheme", map[string]string{"KONSOLE_VERSION": "220400"}, true},
25+
// conhost / 传统 PowerShell:无任何特征 env → 默认 wcwidth(issue #113)。
26+
{"plain(conhost/PowerShell) → wcwidth", map[string]string{}, false},
2527
}
2628
for _, tc := range cases {
2729
t.Run(tc.name, func(t *testing.T) {

tui/model.go

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,21 +1025,24 @@ func (m model) Init() tea.Cmd {
10251025
}
10261026
// 视觉能力探测:每次启动对各模型重探一次(见 vision.go),结果经 visionCapMsg 回灌。
10271027
cmds = append(cmds, visionProbeCmds(m.models)...)
1028-
if cmd := ForceGraphemeCmd(); cmd != nil {
1029-
cmds = append(cmds, cmd)
1030-
}
1028+
// 不再强制注入伪 ModeReportMsg 切 grapheme —— bubbletea 启动会自己向终端查询 mode 2027,
1029+
// 终端真实应答经 applyUnicodeCoreReport 校正显示宽度口径,让 deepx 与终端一致(issue #113)。
10311030
// 启动即把控制态与已恢复的历史推进 hub 快照,晚连接的浏览器据此与 TUI 对齐。
10321031
m.broadcastControlState()
10331032
m.broadcastSessionLoaded()
10341033
return tea.Batch(cmds...)
10351034
}
10361035

1037-
func ForceGraphemeCmd() tea.Cmd {
1038-
if !graphemeWidthMode {
1039-
return nil
1040-
}
1041-
return func() tea.Msg {
1042-
return tea.ModeReportMsg{Mode: ansi.ModeUnicodeCore, Value: ansi.ModeSet}
1036+
// applyUnicodeCoreReport 据终端对 mode 2027(Unicode-core)的真实应答校正显示宽度口径。
1037+
// 与 bubbletea cellbuf 的判定保持同口径(其 tea.go 内部:Set/Reset/PermanentlySet 才启用 grapheme),
1038+
// 否则 deepx 自有排版与 textarea 渲染口径不一致,在不支持 2027 的终端会让输入框插入宽字符后
1039+
// 光标后内容重复(issue #113)。NotRecognized / PermanentlyReset 视为不支持,退回 wcwidth。
1040+
func applyUnicodeCoreReport(value ansi.ModeSetting) {
1041+
switch value {
1042+
case ansi.ModeSet, ansi.ModeReset, ansi.ModePermanentlySet:
1043+
graphemeWidthMode = true
1044+
default:
1045+
graphemeWidthMode = false
10431046
}
10441047
}
10451048

@@ -1054,6 +1057,15 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
10541057

10551058
switch msg := msg.(type) {
10561059

1060+
case tea.ModeReportMsg:
1061+
// 终端对 mode 2027(Unicode-core)的真实应答。据此把显示宽度口径校正到与终端一致,
1062+
// 修输入框插入宽字符后光标后内容重复的问题(issue #113)。其它 mode(如 2026 同步输出)
1063+
// 由 bubbletea 内部处理,这里只关心 Unicode-core。
1064+
if msg.Mode == ansi.ModeUnicodeCore {
1065+
applyUnicodeCoreReport(msg.Value)
1066+
}
1067+
return m, nil
1068+
10571069
case webInputMsg:
10581070
// 浏览器提交的输入,走和终端 Enter 完全相同的提交逻辑。
10591071
var cmd tea.Cmd

0 commit comments

Comments
 (0)