github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/cli/term/reader_unix_test.go (about) 1 //go:build !windows && !plan9 2 // +build !windows,!plan9 3 4 package term 5 6 import ( 7 "os" 8 "strings" 9 "testing" 10 11 "github.com/markusbkk/elvish/pkg/testutil" 12 "github.com/markusbkk/elvish/pkg/ui" 13 ) 14 15 var readEventTests = []struct { 16 input string 17 want Event 18 }{ 19 // Simple graphical key. 20 {"x", K('x')}, 21 {"X", K('X')}, 22 {" ", K(' ')}, 23 24 // Ctrl key. 25 {"\001", K('A', ui.Ctrl)}, 26 {"\033", K('[', ui.Ctrl)}, 27 28 // Special Ctrl keys that do not obey the usual 0x40 rule. 29 {"\000", K('`', ui.Ctrl)}, 30 {"\x1e", K('6', ui.Ctrl)}, 31 {"\x1f", K('/', ui.Ctrl)}, 32 33 // Ambiguous Ctrl keys; the reader uses the non-Ctrl form as canonical. 34 {"\n", K('\n')}, 35 {"\t", K('\t')}, 36 {"\x7f", K('\x7f')}, // backspace 37 38 // Alt plus simple graphical key. 39 {"\033a", K('a', ui.Alt)}, 40 {"\033[", K('[', ui.Alt)}, 41 42 // G3-style key. 43 {"\033OA", K(ui.Up)}, 44 {"\033OH", K(ui.Home)}, 45 46 // G3-style key with leading Escape. 47 {"\033\033OA", K(ui.Up, ui.Alt)}, 48 {"\033\033OH", K(ui.Home, ui.Alt)}, 49 50 // Alt-O. This is handled as a special case because it looks like a G3-style 51 // key. 52 {"\033O", K('O', ui.Alt)}, 53 54 // CSI-sequence key identified by the ending rune. 55 {"\033[A", K(ui.Up)}, 56 {"\033[H", K(ui.Home)}, 57 // Modifiers. 58 {"\033[1;0A", K(ui.Up)}, 59 {"\033[1;1A", K(ui.Up)}, 60 {"\033[1;2A", K(ui.Up, ui.Shift)}, 61 {"\033[1;3A", K(ui.Up, ui.Alt)}, 62 {"\033[1;4A", K(ui.Up, ui.Shift, ui.Alt)}, 63 {"\033[1;5A", K(ui.Up, ui.Ctrl)}, 64 {"\033[1;6A", K(ui.Up, ui.Shift, ui.Ctrl)}, 65 {"\033[1;7A", K(ui.Up, ui.Alt, ui.Ctrl)}, 66 {"\033[1;8A", K(ui.Up, ui.Shift, ui.Alt, ui.Ctrl)}, 67 // The modifiers below should be for Meta, but we conflate Alt and Meta. 68 {"\033[1;9A", K(ui.Up, ui.Alt)}, 69 {"\033[1;10A", K(ui.Up, ui.Shift, ui.Alt)}, 70 {"\033[1;11A", K(ui.Up, ui.Alt)}, 71 {"\033[1;12A", K(ui.Up, ui.Shift, ui.Alt)}, 72 {"\033[1;13A", K(ui.Up, ui.Alt, ui.Ctrl)}, 73 {"\033[1;14A", K(ui.Up, ui.Shift, ui.Alt, ui.Ctrl)}, 74 {"\033[1;15A", K(ui.Up, ui.Alt, ui.Ctrl)}, 75 {"\033[1;16A", K(ui.Up, ui.Shift, ui.Alt, ui.Ctrl)}, 76 77 // CSI-sequence key with one argument, ending in '~'. 78 {"\033[1~", K(ui.Home)}, 79 {"\033[11~", K(ui.F1)}, 80 // Modified. 81 {"\033[1;2~", K(ui.Home, ui.Shift)}, 82 // Urxvt-flavor modifier, shifting the '~' to reflect the modifier 83 {"\033[1$", K(ui.Home, ui.Shift)}, 84 {"\033[1^", K(ui.Home, ui.Ctrl)}, 85 {"\033[1@", K(ui.Home, ui.Shift, ui.Ctrl)}, 86 // With a leading Escape. 87 {"\033\033[1~", K(ui.Home, ui.Alt)}, 88 89 // CSI-sequence key with three arguments and ending in '~'. The first 90 // argument is always 27, the second identifies the modifier and the last 91 // identifies the key. 92 {"\033[27;4;63~", K(';', ui.Shift, ui.Alt)}, 93 94 // Cursor Position Report. 95 {"\033[3;4R", CursorPosition{3, 4}}, 96 97 // Paste setting. 98 {"\033[200~", PasteSetting(true)}, 99 {"\033[201~", PasteSetting(false)}, 100 101 // Mouse event. 102 {"\033[M\x00\x23\x24", MouseEvent{Pos{4, 3}, true, 0, 0}}, 103 // Other buttons. 104 {"\033[M\x01\x23\x24", MouseEvent{Pos{4, 3}, true, 1, 0}}, 105 // Button up. 106 {"\033[M\x03\x23\x24", MouseEvent{Pos{4, 3}, false, -1, 0}}, 107 // Modified. 108 {"\033[M\x04\x23\x24", MouseEvent{Pos{4, 3}, true, 0, ui.Shift}}, 109 {"\033[M\x08\x23\x24", MouseEvent{Pos{4, 3}, true, 0, ui.Alt}}, 110 {"\033[M\x10\x23\x24", MouseEvent{Pos{4, 3}, true, 0, ui.Ctrl}}, 111 {"\033[M\x14\x23\x24", MouseEvent{Pos{4, 3}, true, 0, ui.Shift | ui.Ctrl}}, 112 113 // SGR-style mouse event. 114 {"\033[<0;3;4M", MouseEvent{Pos{4, 3}, true, 0, 0}}, 115 // Other buttons. 116 {"\033[<1;3;4M", MouseEvent{Pos{4, 3}, true, 1, 0}}, 117 // Button up. 118 {"\033[<0;3;4m", MouseEvent{Pos{4, 3}, false, 0, 0}}, 119 // Modified. 120 {"\033[<4;3;4M", MouseEvent{Pos{4, 3}, true, 0, ui.Shift}}, 121 {"\033[<16;3;4M", MouseEvent{Pos{4, 3}, true, 0, ui.Ctrl}}, 122 } 123 124 func TestReader_ReadEvent(t *testing.T) { 125 r, w := setupReader(t) 126 127 for _, test := range readEventTests { 128 t.Run(test.input, func(t *testing.T) { 129 w.WriteString(test.input) 130 ev, err := r.ReadEvent() 131 if ev != test.want { 132 t.Errorf("got event %v, want %v", ev, test.want) 133 } 134 if err != nil { 135 t.Errorf("got err %v, want %v", err, nil) 136 } 137 }) 138 } 139 } 140 141 var readEventBadSeqTests = []struct { 142 input string 143 wantErrMsg string 144 }{ 145 // mouse event should have exactly 3 bytes after \033[M 146 {"\033[M", "incomplete mouse event"}, 147 {"\033[M1", "incomplete mouse event"}, 148 {"\033[M12", "incomplete mouse event"}, 149 150 // CSI needs to be terminated by something that is not a parameter 151 {"\033[1", "incomplete CSI"}, 152 {"\033[;", "incomplete CSI"}, 153 {"\033[1;", "incomplete CSI"}, 154 155 // CPR should have exactly 2 parameters 156 {"\033[1R", "bad CPR"}, 157 {"\033[1;2;3R", "bad CPR"}, 158 159 // SGR mouse event should have exactly 3 parameters 160 {"\033[<1;2m", "bad SGR mouse event"}, 161 162 // csiSeqByLast should have 0 or 2 parameters 163 {"\033[1;2;3A", "bad CSI"}, 164 // csiSeqByLast with 2 parameters should have first parameter = 1 165 {"\033[2;1A", "bad CSI"}, 166 // xterm-style modifier should be 0 to 16 167 {"\033[1;17A", "bad CSI"}, 168 // unknown CSI terminator 169 {"\033[x", "bad CSI"}, 170 171 // G3 allows a small list of allowed bytes after \033O 172 {"\033Ox", "bad G3"}, 173 } 174 175 func TestReader_ReadEvent_BadSeq(t *testing.T) { 176 r, w := setupReader(t) 177 178 for _, test := range readEventBadSeqTests { 179 t.Run(test.input, func(t *testing.T) { 180 w.WriteString(test.input) 181 ev, err := r.ReadEvent() 182 if err == nil { 183 t.Fatalf("got nil err with event %v, want non-nil error", ev) 184 } 185 errMsg := err.Error() 186 if !strings.HasPrefix(errMsg, test.wantErrMsg) { 187 t.Errorf("got err with message %v, want message starting with %v", 188 errMsg, test.wantErrMsg) 189 } 190 }) 191 } 192 } 193 194 func TestReader_ReadRawEvent(t *testing.T) { 195 rd, w := setupReader(t) 196 197 for _, test := range readEventTests { 198 input := test.input 199 t.Run(input, func(t *testing.T) { 200 w.WriteString(input) 201 for _, r := range input { 202 ev, err := rd.ReadRawEvent() 203 if err != nil { 204 t.Errorf("got error %v, want nil", err) 205 } 206 if ev != K(r) { 207 t.Errorf("got event %v, want %v", ev, K(r)) 208 } 209 } 210 }) 211 } 212 } 213 214 func setupReader(t *testing.T) (Reader, *os.File) { 215 pr, pw := testutil.MustPipe() 216 r := NewReader(pr) 217 t.Cleanup(func() { 218 r.Close() 219 pr.Close() 220 pw.Close() 221 }) 222 return r, pw 223 }