From 04c931c440298233cb38ed7de0ebaf9cabc157b4 Mon Sep 17 00:00:00 2001 From: Wang Qi Date: Sun, 28 Jul 2024 21:46:20 +0800 Subject: [PATCH] fix(terminal): prepare for DECRQM, XTGETTCAP, CSI U. #74, #88, #89 --- terminal/handler.go | 66 +++++++++++++++++++++++++++++++++++++++-- terminal/parser.go | 41 +++++++++++++++++++++++++ terminal/parser_test.go | 53 +++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 2 deletions(-) diff --git a/terminal/handler.go b/terminal/handler.go index 6ae1d26..53e993b 100644 --- a/terminal/handler.go +++ b/terminal/handler.go @@ -6,6 +6,7 @@ package terminal import ( "encoding/base64" + "encoding/hex" "fmt" "sort" "strconv" @@ -51,6 +52,7 @@ const ( CSI_DECSCL CSI_DECSCUSR CSI_privSM + CSI_DECRQM CSI_DECSTBM CSI_DECSTR CSI_ECMA48_SL @@ -81,7 +83,9 @@ const ( CSI_VPR CSI_XTMODKEYS CSI_XTWINOPS + CSI_U DCS_DECRQSS + DCS_XTGETTCAP ESC_BI ESC_DCS ESC_DECALN @@ -139,6 +143,7 @@ var strHandlerID = [...]string{ "csi_decscl", "csi_decscusr", "csi_decset", + "csi_decrqm", "csi_decstbm", "csi_decstr", "csi_ecma48_SL", @@ -169,7 +174,9 @@ var strHandlerID = [...]string{ "csi_vpr", "csi_xtmodkeys", "csi_xtwinops", + "csi_u", "dcs_decrqss", + "dcs_xtgettcap", "esc_bi", "esc_dcs", "esc_decaln", @@ -1499,7 +1506,7 @@ func hdl_csi_privSM(emu *Emulator, params []int) { case 2: resetCharsetState(&emu.charsetState) // Designate USASCII for character sets G0-G3 (DECANM), VT100, and set VT100 mode. emu.setCompatLevel(CompatLevel_VT400) - // emu.framebuffer.DS.compatLevel = CompatLevelVT400 + // util.Logger.Warn("DECANM", "changeTo", "Designate USASCII for character sets G0-G3 (DECANM), VT100, and set VT100 mode", "params", params) case 3: emu.switchColMode(ColMode_C132) case 4: @@ -1593,6 +1600,18 @@ func hdl_csi_privSM(emu *Emulator, params []int) { } } +// TODO: implement it +func hdl_csi_decrqm(emu *Emulator, params []int) { + resp := fmt.Sprintf("\x1B?%d;%d$y", params[0], 0) + util.Logger.Warn("DECRQM is not implemented!") + emu.writePty(resp) +} + +// TODO: implement it +func hdl_csi_u(_ *Emulator, _ []int) { + util.Logger.Warn("CSI U is not implemented!") +} + // CSI ? Pm l // DEC Private Mode Reset (DECRST). func hdl_csi_privRM(emu *Emulator, params []int) { @@ -1604,7 +1623,7 @@ func hdl_csi_privRM(emu *Emulator, params []int) { case 2: resetCharsetState(&emu.charsetState) // Designate VT52 mode (DECANM), VT100. emu.setCompatLevel(CompatLevel_VT52) - // emu.framebuffer.DS.compatLevel = CompatLevel_VT52 + // util.Logger.Warn("DECANM", "changeTo", "Designate VT52 mode", "params", params) case 3: emu.switchColMode(ColMode_C80) case 4: @@ -1760,6 +1779,49 @@ func hdl_dcs_decrqss(emu *Emulator, arg string) { } } +/* +DCS + q Pt ST + + Request Termcap/Terminfo String (XTGETTCAP), xterm. The + string following the "q" is a list of names encoded in + hexadecimal (2 digits per character) separated by ; which + correspond to termcap or terminfo key names. + A few special features are also recognized, which are not key + names: + + o Co for termcap colors (or colors for terminfo colors), and + + o TN for termcap name (or name for terminfo name). + + o RGB for the ncurses direct-color extension. + Only a terminfo name is provided, since termcap + applications cannot use this information. + + xterm responds with + DCS 1 + r Pt ST for valid requests, adding to Pt an = , and + the value of the corresponding string that xterm would send, + or + DCS 0 + r ST for invalid requests. + The strings are encoded in hexadecimal (2 digits per + character). If more than one name is given, xterm replies + with each name/value pair in the same response. An invalid + name (one not found in xterm's tables) ends processing of the + list of names. +*/ +func hdl_dcs_xtgettcap(_ *Emulator, arg string) { + name := strings.Split(arg, ";") + for i := range name { + dst := make([]byte, hex.DecodedLen(len(name[i]))) + n, err := hex.Decode(dst, []byte(name[i])) + if err != nil { + util.Logger.Warn("XTGETTCAP decode error", "i", i, "name[i]", name[i], "error", err) + } + util.Logger.Warn("XTGETTCAP", "i", i, "name[i]", dst[:n]) + // TODO: lookup terminfo and return the result + } + util.Logger.Warn("XTGETTCAP is not implemented!", "arg", arg, "name", name) +} + // CSI Pl ; Pr s // // Set left and right margins (DECSLRM), VT420 and up. This is diff --git a/terminal/parser.go b/terminal/parser.go index 876ba27..61daf8e 100644 --- a/terminal/parser.go +++ b/terminal/parser.go @@ -1189,6 +1189,30 @@ func (p *Parser) handle_privRM() (hd *Handler) { return hd } +func (p *Parser) handle_DECRQM() (hd *Handler) { + params := p.copyArgs() + + hd = &Handler{id: CSI_DECRQM, ch: p.ch, sequence: p.historyString()} + hd.handle = func(emu *Emulator) { + hdl_csi_decrqm(emu, params) + } + + p.setState(InputState_Normal) + return hd +} + +func (p *Parser) handle_CSI_U() (hd *Handler) { + params := p.copyArgs() + + hd = &Handler{id: CSI_U, ch: p.ch, sequence: p.historyString()} + hd.handle = func(emu *Emulator) { + hdl_csi_u(emu, params) + } + + p.setState(InputState_Normal) + return hd +} + // Set Top and Bottom Margins func (p *Parser) handle_DECSTBM() (hd *Handler) { params := p.copyArgs() @@ -1230,6 +1254,11 @@ func (p *Parser) handle_DCS() (hd *Handler) { hd.handle = func(emu *Emulator) { hdl_dcs_decrqss(emu, arg) } + } else if strings.HasPrefix(arg, "+q") { + hd = &Handler{id: DCS_XTGETTCAP, ch: p.ch, sequence: p.historyString()} + hd.handle = func(emu *Emulator) { + hdl_dcs_xtgettcap(emu, arg[2:]) + } } else { util.Logger.Warn("DCS", "unimplement", "DCS", "arg", arg, "seq", p.historyString()) } @@ -1973,6 +2002,18 @@ func (p *Parser) ProcessInput(chs ...rune) (hd *Handler) { hd = p.handle_privSM() // DECSET case 'l': hd = p.handle_privRM() // DECRST + case 'u': + hd = p.handle_CSI_U() + case '$': + p.argBuf.WriteRune(ch) + case 'p': + p.argBuf.WriteRune(ch) + if p.argBuf.String() == "$p" { + hd = p.handle_DECRQM() + } else { + p.unhandledInput() + } + p.argBuf.Reset() default: p.unhandledInput() } diff --git a/terminal/parser_test.go b/terminal/parser_test.go index 5ecf671..d184784 100644 --- a/terminal/parser_test.go +++ b/terminal/parser_test.go @@ -4928,3 +4928,56 @@ func TestMixSequence(t *testing.T) { }) } } + +func TestNvimClean(t *testing.T) { + tc := []struct { + label string + seq string + hdIDs []int + }{ + { + "first", "\x1b[?1049h\x1b[22;0;0t\x1b[?1h\x1b=\x1b[H\x1b[2J\x1b[?2004h\x1b[?2026$p\x1b[0m\x1b[4:3m\x1bP$qm\x1b\\\x1b[?u\x1b[c\x1b[?25h", + + []int{ + CSI_privSM, CSI_XTWINOPS, CSI_privSM, ESC_DECKPAM, CSI_CUP, CSI_ED, CSI_privSM, + CSI_DECRQM, CSI_SGR, CSI_SGR, DCS_DECRQSS, CSI_U, CSI_priDA, CSI_privSM, + }, + }, + { + "second", "\x1b]11;?\a\x1bP+q5463;524742;73657472676266;73657472676262\x1b\\\x1b[0m\x1b[48;2;1;2;3m\x1bP$qm\x1b\\", + []int{OSC_10_11_12_17_19, DCS_XTGETTCAP, CSI_SGR, CSI_SGR, DCS_DECRQSS}, + }, + } + + p := NewParser() + emu := NewEmulator3(8, 4, 0) + for _, v := range tc { + t.Run(v.label, func(t *testing.T) { + // process control sequence + hds := make([]*Handler, 0, 16) + hds = p.processStream(v.seq, hds) + + if len(hds) != len(v.hdIDs) { + for i := range hds { + if i <= len(v.hdIDs)-1 && hds[i].id != v.hdIDs[i] { + t.Logf("NvimClean %s: i=%d, got %s, expect =%s, seq=%q\n", + v.label, i, strHandlerID[hds[i].id], strHandlerID[v.hdIDs[i]], hds[i].sequence) + } else { + t.Logf("NvimClean %s: i=%d, got %s, seq=%q\n", + v.label, i, strHandlerID[hds[i].id], hds[i].sequence) + } + } + t.Fatalf("%s got %d handlers, expect %d handlers", v.label, len(hds), len(v.hdIDs)) + } + + // handle the control sequence + for j, hd := range hds { + hd.handle(emu) + if hd.id != v.hdIDs[j] { // validate the control sequences id + t.Fatalf("%s: seq=%q \n hd.index=%d expect %s, got %s\n", + v.label, v.seq, j, strHandlerID[v.hdIDs[j]], strHandlerID[hd.id]) + } + } + }) + } +}