fix(remote): preserve multiline bracketed paste
- commit
- 44355e43125dff8864a53cadcfec44ebc63c5a0e
- author
- brice · · 2026-07-04 12:12 UTC
- parent
- 14aa864
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +48 -16
M internal/adapters/term/pty_integration_test.go +1 -1
| @@ -102,7 +102,7 @@ func TestTerminal_RealPTY_RawModeSizeAndEscapes(t *testing.T) { | |||
| 102 | 102 | ||
| 103 | 103 | select { | |
| 104 | 104 | case got := <-captured: | |
| 105 | - | want := altScreenEnter + cursorHide + mouseEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + altScreenExit | |
| 105 | + | want := altScreenEnter + cursorHide + mouseEnable + bracketedPasteEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + bracketedPasteDisable + altScreenExit | |
| 106 | 106 | if string(got) != want { | |
| 107 | 107 | t.Fatalf("captured escapes = %q, want %q", got, want) | |
| 108 | 108 | } | |
| @@ -102,7 +102,7 @@ func TestTerminal_RealPTY_RawModeSizeAndEscapes(t *testing.T) { | |||||
| 102 | 102 | ||||
| 103 | select { | 103 | select { | ||
| 104 | case got := <-captured: | 104 | case got := <-captured: | ||
| 105 | - | want := altScreenEnter + cursorHide + mouseEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + altScreenExit | 105 | + | want := altScreenEnter + cursorHide + mouseEnable + bracketedPasteEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + bracketedPasteDisable + altScreenExit |
| 106 | if string(got) != want { | 106 | if string(got) != want { | ||
| 107 | t.Fatalf("captured escapes = %q, want %q", got, want) | 107 | t.Fatalf("captured escapes = %q, want %q", got, want) | ||
| 108 | } | 108 | } | ||
M internal/adapters/term/terminal.go +12 -10
| @@ -21,14 +21,16 @@ var _ ports.Terminal = (*Terminal)(nil) | |||
| 21 | 21 | // Escape sequences for alt-screen and cursor visibility control. All | |
| 22 | 22 | // emissions go through the batched writer and are explicitly flushed. | |
| 23 | 23 | const ( | |
| 24 | - | altScreenEnter = "\x1b[?1049h" | |
| 25 | - | altScreenExit = "\x1b[?1049l" | |
| 26 | - | cursorHide = "\x1b[?25l" | |
| 27 | - | cursorShow = "\x1b[?25h" | |
| 28 | - | cursorStyleDefault = "\x1b[0 q" | |
| 29 | - | mouseEnable = "\x1b[?1002h\x1b[?1006h" | |
| 30 | - | mouseDisable = "\x1b[?1002l\x1b[?1006l" | |
| 31 | - | oscColorQuery = "\x1b]10;?\x07\x1b]11;?\x07" | |
| 24 | + | altScreenEnter = "\x1b[?1049h" | |
| 25 | + | altScreenExit = "\x1b[?1049l" | |
| 26 | + | cursorHide = "\x1b[?25l" | |
| 27 | + | cursorShow = "\x1b[?25h" | |
| 28 | + | cursorStyleDefault = "\x1b[0 q" | |
| 29 | + | mouseEnable = "\x1b[?1002h\x1b[?1006h" | |
| 30 | + | mouseDisable = "\x1b[?1002l\x1b[?1006l" | |
| 31 | + | bracketedPasteEnable = "\x1b[?2004h" | |
| 32 | + | bracketedPasteDisable = "\x1b[?2004l" | |
| 33 | + | oscColorQuery = "\x1b]10;?\x07\x1b]11;?\x07" | |
| 32 | 34 | ) | |
| 33 | 35 | ||
| 34 | 36 | // bufSize is the batched writer's buffer capacity. | |
| @@ -105,7 +107,7 @@ func (t *Terminal) EnterRaw() (func() error, error) { | |||
| 105 | 107 | } | |
| 106 | 108 | t.orig = old | |
| 107 | 109 | ||
| 108 | - | if _, err := t.bw.WriteString(altScreenEnter + cursorHide + mouseEnable + oscColorQuery); err != nil { | |
| 110 | + | if _, err := t.bw.WriteString(altScreenEnter + cursorHide + mouseEnable + bracketedPasteEnable + oscColorQuery); err != nil { | |
| 109 | 111 | _ = t.restoreRawLocked() | |
| 110 | 112 | return nil, fmt.Errorf("term: enter alt screen: %w", err) | |
| 111 | 113 | } | |
| @@ -139,7 +141,7 @@ func (t *Terminal) restore() error { | |||
| 139 | 141 | t.mu.Lock() | |
| 140 | 142 | defer t.mu.Unlock() | |
| 141 | 143 | ||
| 142 | - | _, werr := t.bw.WriteString(cursorShow + cursorStyleDefault + mouseDisable + altScreenExit) | |
| 144 | + | _, werr := t.bw.WriteString(cursorShow + cursorStyleDefault + mouseDisable + bracketedPasteDisable + altScreenExit) | |
| 143 | 145 | ferr := t.bw.Flush() | |
| 144 | 146 | ||
| 145 | 147 | rerr := t.restoreRawLocked() | |
| @@ -21,14 +21,16 @@ var _ ports.Terminal = (*Terminal)(nil) | |||||
| 21 | // Escape sequences for alt-screen and cursor visibility control. All | 21 | // Escape sequences for alt-screen and cursor visibility control. All | ||
| 22 | // emissions go through the batched writer and are explicitly flushed. | 22 | // emissions go through the batched writer and are explicitly flushed. | ||
| 23 | const ( | 23 | const ( | ||
| 24 | - | altScreenEnter = "\x1b[?1049h" | 24 | + | altScreenEnter = "\x1b[?1049h" |
| 25 | - | altScreenExit = "\x1b[?1049l" | 25 | + | altScreenExit = "\x1b[?1049l" |
| 26 | - | cursorHide = "\x1b[?25l" | 26 | + | cursorHide = "\x1b[?25l" |
| 27 | - | cursorShow = "\x1b[?25h" | 27 | + | cursorShow = "\x1b[?25h" |
| 28 | - | cursorStyleDefault = "\x1b[0 q" | 28 | + | cursorStyleDefault = "\x1b[0 q" |
| 29 | - | mouseEnable = "\x1b[?1002h\x1b[?1006h" | 29 | + | mouseEnable = "\x1b[?1002h\x1b[?1006h" |
| 30 | - | mouseDisable = "\x1b[?1002l\x1b[?1006l" | 30 | + | mouseDisable = "\x1b[?1002l\x1b[?1006l" |
| 31 | - | oscColorQuery = "\x1b]10;?\x07\x1b]11;?\x07" | 31 | + | bracketedPasteEnable = "\x1b[?2004h" |
| 32 | + | bracketedPasteDisable = "\x1b[?2004l" | |||
| 33 | + | oscColorQuery = "\x1b]10;?\x07\x1b]11;?\x07" | |||
| 32 | ) | 34 | ) | ||
| 33 | 35 | ||||
| 34 | // bufSize is the batched writer's buffer capacity. | 36 | // bufSize is the batched writer's buffer capacity. | ||
| @@ -105,7 +107,7 @@ func (t *Terminal) EnterRaw() (func() error, error) { | |||||
| 105 | } | 107 | } | ||
| 106 | t.orig = old | 108 | t.orig = old | ||
| 107 | 109 | ||||
| 108 | - | if _, err := t.bw.WriteString(altScreenEnter + cursorHide + mouseEnable + oscColorQuery); err != nil { | 110 | + | if _, err := t.bw.WriteString(altScreenEnter + cursorHide + mouseEnable + bracketedPasteEnable + oscColorQuery); err != nil { |
| 109 | _ = t.restoreRawLocked() | 111 | _ = t.restoreRawLocked() | ||
| 110 | return nil, fmt.Errorf("term: enter alt screen: %w", err) | 112 | return nil, fmt.Errorf("term: enter alt screen: %w", err) | ||
| 111 | } | 113 | } | ||
| @@ -139,7 +141,7 @@ func (t *Terminal) restore() error { | |||||
| 139 | t.mu.Lock() | 141 | t.mu.Lock() | ||
| 140 | defer t.mu.Unlock() | 142 | defer t.mu.Unlock() | ||
| 141 | 143 | ||||
| 142 | - | _, werr := t.bw.WriteString(cursorShow + cursorStyleDefault + mouseDisable + altScreenExit) | 144 | + | _, werr := t.bw.WriteString(cursorShow + cursorStyleDefault + mouseDisable + bracketedPasteDisable + altScreenExit) |
| 143 | ferr := t.bw.Flush() | 145 | ferr := t.bw.Flush() | ||
| 144 | 146 | ||||
| 145 | rerr := t.restoreRawLocked() | 147 | rerr := t.restoreRawLocked() | ||
M internal/adapters/term/terminal_test.go +2 -2
| @@ -60,7 +60,7 @@ func TestTerminal_EnterRaw_NonTTY_EmitsAltScreenAndCursorEscapes(t *testing.T) { | |||
| 60 | 60 | _ = outW.Close() | |
| 61 | 61 | <-done | |
| 62 | 62 | ||
| 63 | - | want := altScreenEnter + cursorHide + mouseEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + altScreenExit | |
| 63 | + | want := altScreenEnter + cursorHide + mouseEnable + bracketedPasteEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + bracketedPasteDisable + altScreenExit | |
| 64 | 64 | if got := captured.String(); got != want { | |
| 65 | 65 | t.Fatalf("captured escapes = %q, want %q", got, want) | |
| 66 | 66 | } | |
| @@ -101,7 +101,7 @@ func TestTerminal_EnterRaw_IsIdempotentAcrossCalls(t *testing.T) { | |||
| 101 | 101 | // Alt-screen/cursor/theme-query escapes must appear exactly once for enter and | |
| 102 | 102 | // cursor/alt-screen exits exactly once, regardless of how many times | |
| 103 | 103 | // EnterRaw/restore were called. | |
| 104 | - | want := altScreenEnter + cursorHide + mouseEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + altScreenExit | |
| 104 | + | want := altScreenEnter + cursorHide + mouseEnable + bracketedPasteEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + bracketedPasteDisable + altScreenExit | |
| 105 | 105 | if got := captured.String(); got != want { | |
| 106 | 106 | t.Fatalf("captured escapes = %q, want %q", got, want) | |
| 107 | 107 | } | |
| @@ -60,7 +60,7 @@ func TestTerminal_EnterRaw_NonTTY_EmitsAltScreenAndCursorEscapes(t *testing.T) { | |||||
| 60 | _ = outW.Close() | 60 | _ = outW.Close() | ||
| 61 | <-done | 61 | <-done | ||
| 62 | 62 | ||||
| 63 | - | want := altScreenEnter + cursorHide + mouseEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + altScreenExit | 63 | + | want := altScreenEnter + cursorHide + mouseEnable + bracketedPasteEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + bracketedPasteDisable + altScreenExit |
| 64 | if got := captured.String(); got != want { | 64 | if got := captured.String(); got != want { | ||
| 65 | t.Fatalf("captured escapes = %q, want %q", got, want) | 65 | t.Fatalf("captured escapes = %q, want %q", got, want) | ||
| 66 | } | 66 | } | ||
| @@ -101,7 +101,7 @@ func TestTerminal_EnterRaw_IsIdempotentAcrossCalls(t *testing.T) { | |||||
| 101 | // Alt-screen/cursor/theme-query escapes must appear exactly once for enter and | 101 | // Alt-screen/cursor/theme-query escapes must appear exactly once for enter and | ||
| 102 | // cursor/alt-screen exits exactly once, regardless of how many times | 102 | // cursor/alt-screen exits exactly once, regardless of how many times | ||
| 103 | // EnterRaw/restore were called. | 103 | // EnterRaw/restore were called. | ||
| 104 | - | want := altScreenEnter + cursorHide + mouseEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + altScreenExit | 104 | + | want := altScreenEnter + cursorHide + mouseEnable + bracketedPasteEnable + oscColorQuery + cursorShow + cursorStyleDefault + mouseDisable + bracketedPasteDisable + altScreenExit |
| 105 | if got := captured.String(); got != want { | 105 | if got := captured.String(); got != want { | ||
| 106 | t.Fatalf("captured escapes = %q, want %q", got, want) | 106 | t.Fatalf("captured escapes = %q, want %q", got, want) | ||
| 107 | } | 107 | } | ||
M internal/usecase/daemon/input_test.go +15 -0
| @@ -79,6 +79,21 @@ func TestAltCForwardsToPTY(t *testing.T) { | |||
| 79 | 79 | releasePTY() | |
| 80 | 80 | } | |
| 81 | 81 | ||
| 82 | + | func TestBracketedMultilinePasteForwardsDelimitersAndNewlines(t *testing.T) { | |
| 83 | + | writes := make(chan []byte, 1) | |
| 84 | + | p, releasePTY := newBlockingPTYWithWrites(t, writes) | |
| 85 | + | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | |
| 86 | + | pane := sess.tabs[0].focusedPane() | |
| 87 | + | pane.screen.Write([]byte("\x1b[?2004h")) | |
| 88 | + | require.True(t, pane.screen.BracketedPasteMode()) | |
| 89 | + | ||
| 90 | + | paste := []byte("\x1b[200~first line\nsecond line\n\x1b[201~") | |
| 91 | + | d.handleInput(sess, ac, paste) | |
| 92 | + | ||
| 93 | + | require.Equal(t, paste, <-writes) | |
| 94 | + | releasePTY() | |
| 95 | + | } | |
| 96 | + | ||
| 82 | 97 | func TestPaletteNextPreviousSwitchActiveTab(t *testing.T) { | |
| 83 | 98 | cases := []struct { | |
| 84 | 99 | name string | |
| @@ -79,6 +79,21 @@ func TestAltCForwardsToPTY(t *testing.T) { | |||||
| 79 | releasePTY() | 79 | releasePTY() | ||
| 80 | } | 80 | } | ||
| 81 | 81 | ||||
| 82 | + | func TestBracketedMultilinePasteForwardsDelimitersAndNewlines(t *testing.T) { | |||
| 83 | + | writes := make(chan []byte, 1) | |||
| 84 | + | p, releasePTY := newBlockingPTYWithWrites(t, writes) | |||
| 85 | + | d, sess, ac, _ := newManualSessionWithPTYs(t, p) | |||
| 86 | + | pane := sess.tabs[0].focusedPane() | |||
| 87 | + | pane.screen.Write([]byte("\x1b[?2004h")) | |||
| 88 | + | require.True(t, pane.screen.BracketedPasteMode()) | |||
| 89 | + | ||||
| 90 | + | paste := []byte("\x1b[200~first line\nsecond line\n\x1b[201~") | |||
| 91 | + | d.handleInput(sess, ac, paste) | |||
| 92 | + | ||||
| 93 | + | require.Equal(t, paste, <-writes) | |||
| 94 | + | releasePTY() | |||
| 95 | + | } | |||
| 96 | + | ||||
| 82 | func TestPaletteNextPreviousSwitchActiveTab(t *testing.T) { | 97 | func TestPaletteNextPreviousSwitchActiveTab(t *testing.T) { | ||
| 83 | cases := []struct { | 98 | cases := []struct { | ||
| 84 | name string | 99 | name string | ||
M pkg/vt/screen.go +9 -1
| @@ -65,6 +65,7 @@ type Screen struct { | |||
| 65 | 65 | cursorStyleSet bool | |
| 66 | 66 | mouseMode int | |
| 67 | 67 | mouseSGR bool | |
| 68 | + | bracketedPaste bool | |
| 68 | 69 | } | |
| 69 | 70 | ||
| 70 | 71 | func NewScreen(width, height int) *Screen { | |
| @@ -128,6 +129,10 @@ func (s *Screen) ClearDamage() { s.damage = s.damage[:0] } | |||
| 128 | 129 | // is currently enabled by the child process. | |
| 129 | 130 | func (s *Screen) SyncUpdateActive() bool { return s.syncUpdateActive } | |
| 130 | 131 | ||
| 132 | + | // BracketedPasteMode reports whether DEC private mode 2004 is currently enabled | |
| 133 | + | // by the child process. | |
| 134 | + | func (s *Screen) BracketedPasteMode() bool { return s.bracketedPaste } | |
| 135 | + | ||
| 131 | 136 | // ForceSyncEnd forcibly leaves DEC private mode 2026 (synchronized update). | |
| 132 | 137 | // Hosts use this as a safety valve if a child enters synchronized update mode | |
| 133 | 138 | // and never sends the matching end sequence. | |
| @@ -906,6 +911,7 @@ func (s *Screen) reset() { | |||
| 906 | 911 | s.cursorStyleSet = false | |
| 907 | 912 | s.mouseMode = 0 | |
| 908 | 913 | s.mouseSGR = false | |
| 914 | + | s.bracketedPaste = false | |
| 909 | 915 | s.resetScrollRegion() | |
| 910 | 916 | s.fullRedraw() | |
| 911 | 917 | } | |
| @@ -966,7 +972,9 @@ func (s *Screen) setMode(private bool, parts []int, enabled bool) { | |||
| 966 | 972 | } | |
| 967 | 973 | case 1006: | |
| 968 | 974 | s.mouseSGR = enabled | |
| 969 | - | case 1, 1004, 1005, 2004: | |
| 975 | + | case 2004: | |
| 976 | + | s.bracketedPaste = enabled | |
| 977 | + | case 1, 1004, 1005: | |
| 970 | 978 | // Trackable terminal modes that do not directly affect the current | |
| 971 | 979 | // cell model yet. Consuming them prevents mode bytes from leaking. | |
| 972 | 980 | continue | |
| @@ -65,6 +65,7 @@ type Screen struct { | |||||
| 65 | cursorStyleSet bool | 65 | cursorStyleSet bool | ||
| 66 | mouseMode int | 66 | mouseMode int | ||
| 67 | mouseSGR bool | 67 | mouseSGR bool | ||
| 68 | + | bracketedPaste bool | |||
| 68 | } | 69 | } | ||
| 69 | 70 | ||||
| 70 | func NewScreen(width, height int) *Screen { | 71 | func NewScreen(width, height int) *Screen { | ||
| @@ -128,6 +129,10 @@ func (s *Screen) ClearDamage() { s.damage = s.damage[:0] } | |||||
| 128 | // is currently enabled by the child process. | 129 | // is currently enabled by the child process. | ||
| 129 | func (s *Screen) SyncUpdateActive() bool { return s.syncUpdateActive } | 130 | func (s *Screen) SyncUpdateActive() bool { return s.syncUpdateActive } | ||
| 130 | 131 | ||||
| 132 | + | // BracketedPasteMode reports whether DEC private mode 2004 is currently enabled | |||
| 133 | + | // by the child process. | |||
| 134 | + | func (s *Screen) BracketedPasteMode() bool { return s.bracketedPaste } | |||
| 135 | + | ||||
| 131 | // ForceSyncEnd forcibly leaves DEC private mode 2026 (synchronized update). | 136 | // ForceSyncEnd forcibly leaves DEC private mode 2026 (synchronized update). | ||
| 132 | // Hosts use this as a safety valve if a child enters synchronized update mode | 137 | // Hosts use this as a safety valve if a child enters synchronized update mode | ||
| 133 | // and never sends the matching end sequence. | 138 | // and never sends the matching end sequence. | ||
| @@ -906,6 +911,7 @@ func (s *Screen) reset() { | |||||
| 906 | s.cursorStyleSet = false | 911 | s.cursorStyleSet = false | ||
| 907 | s.mouseMode = 0 | 912 | s.mouseMode = 0 | ||
| 908 | s.mouseSGR = false | 913 | s.mouseSGR = false | ||
| 914 | + | s.bracketedPaste = false | |||
| 909 | s.resetScrollRegion() | 915 | s.resetScrollRegion() | ||
| 910 | s.fullRedraw() | 916 | s.fullRedraw() | ||
| 911 | } | 917 | } | ||
| @@ -966,7 +972,9 @@ func (s *Screen) setMode(private bool, parts []int, enabled bool) { | |||||
| 966 | } | 972 | } | ||
| 967 | case 1006: | 973 | case 1006: | ||
| 968 | s.mouseSGR = enabled | 974 | s.mouseSGR = enabled | ||
| 969 | - | case 1, 1004, 1005, 2004: | 975 | + | case 2004: |
| 976 | + | s.bracketedPaste = enabled | |||
| 977 | + | case 1, 1004, 1005: | |||
| 970 | // Trackable terminal modes that do not directly affect the current | 978 | // Trackable terminal modes that do not directly affect the current | ||
| 971 | // cell model yet. Consuming them prevents mode bytes from leaking. | 979 | // cell model yet. Consuming them prevents mode bytes from leaking. | ||
| 972 | continue | 980 | continue | ||
M pkg/vt/screen_test.go +9 -2
| @@ -1352,10 +1352,10 @@ func TestResize(t *testing.T) { | |||
| 1352 | 1352 | }, | |
| 1353 | 1353 | }, | |
| 1354 | 1354 | { | |
| 1355 | - | name: "style mouse and cursor modes survive", | |
| 1355 | + | name: "style mouse cursor and bracketed paste modes survive", | |
| 1356 | 1356 | run: func(t *testing.T) { | |
| 1357 | 1357 | s := NewScreen(5, 2) | |
| 1358 | - | s.Write([]byte("\x1b[1m\x1b[?1000h\x1b[?1006h\x1b[?25l\x1b[3 q")) | |
| 1358 | + | s.Write([]byte("\x1b[1m\x1b[?1000h\x1b[?1006h\x1b[?2004h\x1b[?25l\x1b[3 q")) | |
| 1359 | 1359 | ||
| 1360 | 1360 | s.Resize(6, 3) | |
| 1361 | 1361 | ||
| @@ -1365,12 +1365,19 @@ func TestResize(t *testing.T) { | |||
| 1365 | 1365 | if mode, sgr := s.MouseMode(); mode != 1000 || !sgr { | |
| 1366 | 1366 | t.Fatalf("mouse mode = (%d,%v), want (1000,true)", mode, sgr) | |
| 1367 | 1367 | } | |
| 1368 | + | if !s.BracketedPasteMode() { | |
| 1369 | + | t.Fatal("bracketed paste mode was reset") | |
| 1370 | + | } | |
| 1368 | 1371 | if s.CursorVisible() { | |
| 1369 | 1372 | t.Fatal("cursor visibility was reset") | |
| 1370 | 1373 | } | |
| 1371 | 1374 | if style, ok := s.CursorStyle(); style != 3 || !ok { | |
| 1372 | 1375 | t.Fatalf("cursor style = (%d,%v), want (3,true)", style, ok) | |
| 1373 | 1376 | } | |
| 1377 | + | s.Write([]byte("\x1b[?2004l")) | |
| 1378 | + | if s.BracketedPasteMode() { | |
| 1379 | + | t.Fatal("bracketed paste mode remained enabled after reset") | |
| 1380 | + | } | |
| 1374 | 1381 | }, | |
| 1375 | 1382 | }, | |
| 1376 | 1383 | } | |
| @@ -1352,10 +1352,10 @@ func TestResize(t *testing.T) { | |||||
| 1352 | }, | 1352 | }, | ||
| 1353 | }, | 1353 | }, | ||
| 1354 | { | 1354 | { | ||
| 1355 | - | name: "style mouse and cursor modes survive", | 1355 | + | name: "style mouse cursor and bracketed paste modes survive", |
| 1356 | run: func(t *testing.T) { | 1356 | run: func(t *testing.T) { | ||
| 1357 | s := NewScreen(5, 2) | 1357 | s := NewScreen(5, 2) | ||
| 1358 | - | s.Write([]byte("\x1b[1m\x1b[?1000h\x1b[?1006h\x1b[?25l\x1b[3 q")) | 1358 | + | s.Write([]byte("\x1b[1m\x1b[?1000h\x1b[?1006h\x1b[?2004h\x1b[?25l\x1b[3 q")) |
| 1359 | 1359 | ||||
| 1360 | s.Resize(6, 3) | 1360 | s.Resize(6, 3) | ||
| 1361 | 1361 | ||||
| @@ -1365,12 +1365,19 @@ func TestResize(t *testing.T) { | |||||
| 1365 | if mode, sgr := s.MouseMode(); mode != 1000 || !sgr { | 1365 | if mode, sgr := s.MouseMode(); mode != 1000 || !sgr { | ||
| 1366 | t.Fatalf("mouse mode = (%d,%v), want (1000,true)", mode, sgr) | 1366 | t.Fatalf("mouse mode = (%d,%v), want (1000,true)", mode, sgr) | ||
| 1367 | } | 1367 | } | ||
| 1368 | + | if !s.BracketedPasteMode() { | |||
| 1369 | + | t.Fatal("bracketed paste mode was reset") | |||
| 1370 | + | } | |||
| 1368 | if s.CursorVisible() { | 1371 | if s.CursorVisible() { | ||
| 1369 | t.Fatal("cursor visibility was reset") | 1372 | t.Fatal("cursor visibility was reset") | ||
| 1370 | } | 1373 | } | ||
| 1371 | if style, ok := s.CursorStyle(); style != 3 || !ok { | 1374 | if style, ok := s.CursorStyle(); style != 3 || !ok { | ||
| 1372 | t.Fatalf("cursor style = (%d,%v), want (3,true)", style, ok) | 1375 | t.Fatalf("cursor style = (%d,%v), want (3,true)", style, ok) | ||
| 1373 | } | 1376 | } | ||
| 1377 | + | s.Write([]byte("\x1b[?2004l")) | |||
| 1378 | + | if s.BracketedPasteMode() { | |||
| 1379 | + | t.Fatal("bracketed paste mode remained enabled after reset") | |||
| 1380 | + | } | |||
| 1374 | }, | 1381 | }, | ||
| 1375 | }, | 1382 | }, | ||
| 1376 | } | 1383 | } | ||