forked from Mirrors/bubbletea
feat: bracketed paste (#397)
* feat: bracketed paste This introduces support for input via bracketed paste, where escape characters in the pasted input are not interpreted. Pasted input are marked as a special field in the KeyMsg. This is useful because pasted input may need sanitation in individual widgets. * fix(key): support bracketed paste with short reads Some terminal emulators feed the bracketed paste data in multiple chunks, which may not be aligned on a 256 byte boundary. So it's possible for `input.Read` to return less than 256 bytes read but while there's still more data to be read to complete a bracketed paste input. --------- Co-authored-by: Christian Muehlhaeuser <muesli@gmail.com>
This commit is contained in:
parent
ab7e5ea8b2
commit
2b46020ca0
|
@ -1,3 +1,3 @@
|
|||
[?25lHi. This program will exit in 10 seconds. To quit sooner press any key
|
||||
[?25l[?2004hHi. This program will exit in 10 seconds. To quit sooner press any key
|
||||
[70D[1A[70D[2KHi. This program will exit in 9 seconds. To quit sooner press any key.
|
||||
[70D[2K[?25h[?1002l[?1003l[?1006l
|
||||
[70D[2K[?2004l[?25h[?1002l[?1003l[?1006l
|
32
key.go
32
key.go
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
|
@ -54,6 +55,7 @@ type Key struct {
|
|||
Type KeyType
|
||||
Runes []rune
|
||||
Alt bool
|
||||
Paste bool
|
||||
}
|
||||
|
||||
// String returns a friendly string representation for a key. It's safe (and
|
||||
|
@ -63,15 +65,28 @@ type Key struct {
|
|||
// fmt.Println(k)
|
||||
// // Output: enter
|
||||
func (k Key) String() (str string) {
|
||||
var buf strings.Builder
|
||||
if k.Alt {
|
||||
str += "alt+"
|
||||
buf.WriteString("alt+")
|
||||
}
|
||||
if k.Type == KeyRunes {
|
||||
str += string(k.Runes)
|
||||
return str
|
||||
if k.Paste {
|
||||
// Note: bubbles/keys bindings currently do string compares to
|
||||
// recognize shortcuts. Since pasted text should never activate
|
||||
// shortcuts, we need to ensure that the binding code doesn't
|
||||
// match Key events that result from pastes. We achieve this
|
||||
// here by enclosing pastes in '[...]' so that the string
|
||||
// comparison in Matches() fails in that case.
|
||||
buf.WriteByte('[')
|
||||
}
|
||||
buf.WriteString(string(k.Runes))
|
||||
if k.Paste {
|
||||
buf.WriteByte(']')
|
||||
}
|
||||
return buf.String()
|
||||
} else if s, ok := keyNames[k.Type]; ok {
|
||||
str += s
|
||||
return str
|
||||
buf.WriteString(s)
|
||||
return buf.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -613,6 +628,13 @@ func detectOneMsg(b []byte, canHaveMoreData bool) (w int, msg Msg) {
|
|||
}
|
||||
}
|
||||
|
||||
// Detect bracketed paste.
|
||||
var foundbp bool
|
||||
foundbp, w, msg = detectBracketedPaste(b)
|
||||
if foundbp {
|
||||
return
|
||||
}
|
||||
|
||||
// Detect escape sequence and control characters other than NUL,
|
||||
// possibly with an escape character in front to mark the Alt
|
||||
// modifier.
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package tea
|
||||
|
||||
import "sort"
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// extSequences is used by the map-based algorithm below. It contains
|
||||
// the sequences plus their alternatives with an escape character
|
||||
|
@ -69,3 +73,47 @@ func detectSequence(input []byte) (hasSeq bool, width int, msg Msg) {
|
|||
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
// detectBracketedPaste detects an input pasted while bracketed
|
||||
// paste mode was enabled.
|
||||
//
|
||||
// Note: this function is a no-op if bracketed paste was not enabled
|
||||
// on the terminal, since in that case we'd never see this
|
||||
// particular escape sequence.
|
||||
func detectBracketedPaste(input []byte) (hasBp bool, width int, msg Msg) {
|
||||
// Detect the start sequence.
|
||||
const bpStart = "\x1b[200~"
|
||||
if len(input) < len(bpStart) || string(input[:len(bpStart)]) != bpStart {
|
||||
return false, 0, nil
|
||||
}
|
||||
|
||||
// Skip over the start sequence.
|
||||
input = input[len(bpStart):]
|
||||
|
||||
// If we saw the start sequence, then we must have an end sequence
|
||||
// as well. Find it.
|
||||
const bpEnd = "\x1b[201~"
|
||||
idx := bytes.Index(input, []byte(bpEnd))
|
||||
inputLen := len(bpStart) + idx + len(bpEnd)
|
||||
if idx == -1 {
|
||||
// We have encountered the end of the input buffer without seeing
|
||||
// the marker for the end of the bracketed paste.
|
||||
// Tell the outer loop we have done a short read and we want more.
|
||||
return true, 0, nil
|
||||
}
|
||||
|
||||
// The paste is everything in-between.
|
||||
paste := input[:idx]
|
||||
|
||||
// All there is in-between is runes, not to be interpreted further.
|
||||
k := Key{Type: KeyRunes, Paste: true}
|
||||
for len(paste) > 0 {
|
||||
r, w := utf8.DecodeRune(paste)
|
||||
if r != utf8.RuneError {
|
||||
k.Runes = append(k.Runes, r)
|
||||
}
|
||||
paste = paste[w:]
|
||||
}
|
||||
|
||||
return true, inputLen, KeyMsg(k)
|
||||
}
|
||||
|
|
26
key_test.go
26
key_test.go
|
@ -434,23 +434,25 @@ func TestReadInput(t *testing.T) {
|
|||
[]byte{'\x1b', '\x1b'},
|
||||
[]Msg{KeyMsg{Type: KeyEsc, Alt: true}},
|
||||
},
|
||||
// Bracketed paste does not work yet.
|
||||
{"?CSI[50 48 48 126]? a b ?CSI[50 48 49 126]?",
|
||||
{"[a b] o",
|
||||
[]byte{
|
||||
'\x1b', '[', '2', '0', '0', '~',
|
||||
'a', ' ', 'b',
|
||||
'\x1b', '[', '2', '0', '1', '~',
|
||||
'o',
|
||||
},
|
||||
[]Msg{
|
||||
KeyMsg{Type: KeyRunes, Runes: []rune("a b"), Paste: true},
|
||||
KeyMsg{Type: KeyRunes, Runes: []rune("o")},
|
||||
},
|
||||
},
|
||||
{"[a\x03\nb]",
|
||||
[]byte{
|
||||
'\x1b', '[', '2', '0', '0', '~',
|
||||
'a', '\x03', '\n', 'b',
|
||||
'\x1b', '[', '2', '0', '1', '~'},
|
||||
[]Msg{
|
||||
// What we expect once bracketed paste is recognized properly:
|
||||
//
|
||||
// KeyMsg{Type: KeyRunes, Runes: []rune("a b")},
|
||||
//
|
||||
// What we get instead (for now):
|
||||
unknownCSISequenceMsg{0x1b, 0x5b, 0x32, 0x30, 0x30, 0x7e},
|
||||
KeyMsg{Type: KeyRunes, Runes: []rune{'a'}},
|
||||
KeyMsg{Type: KeySpace, Runes: []rune{' '}},
|
||||
KeyMsg{Type: KeyRunes, Runes: []rune{'b'}},
|
||||
unknownCSISequenceMsg{0x1b, 0x5b, 0x32, 0x30, 0x31, 0x7e},
|
||||
KeyMsg{Type: KeyRunes, Runes: []rune("a\x03\nb"), Paste: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,20 +2,23 @@ package tea
|
|||
|
||||
type nilRenderer struct{}
|
||||
|
||||
func (n nilRenderer) start() {}
|
||||
func (n nilRenderer) stop() {}
|
||||
func (n nilRenderer) kill() {}
|
||||
func (n nilRenderer) write(_ string) {}
|
||||
func (n nilRenderer) repaint() {}
|
||||
func (n nilRenderer) clearScreen() {}
|
||||
func (n nilRenderer) altScreen() bool { return false }
|
||||
func (n nilRenderer) enterAltScreen() {}
|
||||
func (n nilRenderer) exitAltScreen() {}
|
||||
func (n nilRenderer) showCursor() {}
|
||||
func (n nilRenderer) hideCursor() {}
|
||||
func (n nilRenderer) enableMouseCellMotion() {}
|
||||
func (n nilRenderer) disableMouseCellMotion() {}
|
||||
func (n nilRenderer) enableMouseAllMotion() {}
|
||||
func (n nilRenderer) disableMouseAllMotion() {}
|
||||
func (n nilRenderer) enableMouseSGRMode() {}
|
||||
func (n nilRenderer) disableMouseSGRMode() {}
|
||||
func (n nilRenderer) start() {}
|
||||
func (n nilRenderer) stop() {}
|
||||
func (n nilRenderer) kill() {}
|
||||
func (n nilRenderer) write(_ string) {}
|
||||
func (n nilRenderer) repaint() {}
|
||||
func (n nilRenderer) clearScreen() {}
|
||||
func (n nilRenderer) altScreen() bool { return false }
|
||||
func (n nilRenderer) enterAltScreen() {}
|
||||
func (n nilRenderer) exitAltScreen() {}
|
||||
func (n nilRenderer) showCursor() {}
|
||||
func (n nilRenderer) hideCursor() {}
|
||||
func (n nilRenderer) enableMouseCellMotion() {}
|
||||
func (n nilRenderer) disableMouseCellMotion() {}
|
||||
func (n nilRenderer) enableMouseAllMotion() {}
|
||||
func (n nilRenderer) disableMouseAllMotion() {}
|
||||
func (n nilRenderer) enableBracketedPaste() {}
|
||||
func (n nilRenderer) disableBracketedPaste() {}
|
||||
func (n nilRenderer) enableMouseSGRMode() {}
|
||||
func (n nilRenderer) disableMouseSGRMode() {}
|
||||
func (n nilRenderer) bracketedPasteActive() bool { return false }
|
||||
|
|
|
@ -101,6 +101,13 @@ func WithAltScreen() ProgramOption {
|
|||
}
|
||||
}
|
||||
|
||||
// WithoutBracketedPaste starts the program with bracketed paste disabled.
|
||||
func WithoutBracketedPaste() ProgramOption {
|
||||
return func(p *Program) {
|
||||
p.startupOptions |= withoutBracketedPaste
|
||||
}
|
||||
}
|
||||
|
||||
// WithMouseCellMotion starts the program with the mouse enabled in "cell
|
||||
// motion" mode.
|
||||
//
|
||||
|
|
|
@ -81,6 +81,10 @@ func TestOptions(t *testing.T) {
|
|||
exercise(t, WithAltScreen(), withAltScreen)
|
||||
})
|
||||
|
||||
t.Run("bracketed paste disabled", func(t *testing.T) {
|
||||
exercise(t, WithoutBracketedPaste(), withoutBracketedPaste)
|
||||
})
|
||||
|
||||
t.Run("ansi compression", func(t *testing.T) {
|
||||
exercise(t, WithANSICompressor(), withANSICompressor)
|
||||
})
|
||||
|
@ -115,8 +119,8 @@ func TestOptions(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("multiple", func(t *testing.T) {
|
||||
p := NewProgram(nil, WithMouseAllMotion(), WithAltScreen(), WithInputTTY())
|
||||
for _, opt := range []startupOptions{withMouseAllMotion, withAltScreen} {
|
||||
p := NewProgram(nil, WithMouseAllMotion(), WithoutBracketedPaste(), WithAltScreen(), WithInputTTY())
|
||||
for _, opt := range []startupOptions{withMouseAllMotion, withoutBracketedPaste, withAltScreen} {
|
||||
if !p.startupOptions.has(opt) {
|
||||
t.Errorf("expected startup options have %v, got %v", opt, p.startupOptions)
|
||||
}
|
||||
|
|
11
renderer.go
11
renderer.go
|
@ -56,6 +56,17 @@ type renderer interface {
|
|||
|
||||
// disableMouseSGRMode disables mouse extended mode (SGR).
|
||||
disableMouseSGRMode()
|
||||
|
||||
// enableBracketedPaste enables bracketed paste, where characters
|
||||
// inside the input are not interpreted when pasted as a whole.
|
||||
enableBracketedPaste()
|
||||
|
||||
// disableBracketedPaste disables bracketed paste.
|
||||
disableBracketedPaste()
|
||||
|
||||
// bracketedPasteActive reports whether bracketed paste mode is
|
||||
// currently enabled.
|
||||
bracketedPasteActive() bool
|
||||
}
|
||||
|
||||
// repaintMsg forces a full repaint.
|
||||
|
|
28
screen.go
28
screen.go
|
@ -116,6 +116,34 @@ func ShowCursor() Msg {
|
|||
// this message with ShowCursor.
|
||||
type showCursorMsg struct{}
|
||||
|
||||
// EnableBracketedPaste is a special command that tells the Bubble Tea program
|
||||
// to accept bracketed paste input.
|
||||
//
|
||||
// Note that bracketed paste will be automatically disabled when the
|
||||
// program quits.
|
||||
func EnableBracketedPaste() Msg {
|
||||
return enableBracketedPasteMsg{}
|
||||
}
|
||||
|
||||
// enableBracketedPasteMsg in an internal message signals that
|
||||
// bracketed paste should be enabled. You can send an
|
||||
// enableBracketedPasteMsg with EnableBracketedPaste.
|
||||
type enableBracketedPasteMsg struct{}
|
||||
|
||||
// DisableBracketedPaste is a special command that tells the Bubble Tea program
|
||||
// to accept bracketed paste input.
|
||||
//
|
||||
// Note that bracketed paste will be automatically disabled when the
|
||||
// program quits.
|
||||
func DisableBracketedPaste() Msg {
|
||||
return disableBracketedPasteMsg{}
|
||||
}
|
||||
|
||||
// disableBracketedPasteMsg in an internal message signals that
|
||||
// bracketed paste should be disabled. You can send an
|
||||
// disableBracketedPasteMsg with DisableBracketedPaste.
|
||||
type disableBracketedPasteMsg struct{}
|
||||
|
||||
// EnterAltScreen enters the alternate screen buffer, which consumes the entire
|
||||
// terminal window. ExitAltScreen will return the terminal to its former state.
|
||||
//
|
||||
|
|
|
@ -14,42 +14,47 @@ func TestClearMsg(t *testing.T) {
|
|||
{
|
||||
name: "clear_screen",
|
||||
cmds: []Cmd{ClearScreen},
|
||||
expected: "\x1b[?25l\x1b[2J\x1b[1;1H\x1b[1;1Hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
expected: "\x1b[?25l\x1b[?2004h\x1b[2J\x1b[1;1H\x1b[1;1Hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
},
|
||||
{
|
||||
name: "altscreen",
|
||||
cmds: []Cmd{EnterAltScreen, ExitAltScreen},
|
||||
expected: "\x1b[?25l\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25l\x1b[?1049l\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25l\x1b[?1049l\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
},
|
||||
{
|
||||
name: "altscreen_autoexit",
|
||||
cmds: []Cmd{EnterAltScreen},
|
||||
expected: "\x1b[?25l\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25lsuccess\r\n\x1b[2;0H\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1049l\x1b[?25h",
|
||||
expected: "\x1b[?25l\x1b[?2004h\x1b[?1049h\x1b[2J\x1b[1;1H\x1b[1;1H\x1b[?25lsuccess\r\n\x1b[2;0H\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1049l\x1b[?25h",
|
||||
},
|
||||
{
|
||||
name: "mouse_cellmotion",
|
||||
cmds: []Cmd{EnableMouseCellMotion},
|
||||
expected: "\x1b[?25l\x1b[?1002h\x1b[?1006hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
expected: "\x1b[?25l\x1b[?2004h\x1b[?1002h\x1b[?1006hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
},
|
||||
{
|
||||
name: "mouse_allmotion",
|
||||
cmds: []Cmd{EnableMouseAllMotion},
|
||||
expected: "\x1b[?25l\x1b[?1003h\x1b[?1006hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
},
|
||||
{
|
||||
name: "mouse_disable",
|
||||
cmds: []Cmd{EnableMouseAllMotion, DisableMouse},
|
||||
expected: "\x1b[?25l\x1b[?1003h\x1b[?1006h\x1b[?1002l\x1b[?1003l\x1b[?1006lsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
expected: "\x1b[?25l\x1b[?2004h\x1b[?1003h\x1b[?1006h\x1b[?1002l\x1b[?1003l\x1b[?1006lsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
},
|
||||
{
|
||||
name: "cursor_hide",
|
||||
cmds: []Cmd{HideCursor},
|
||||
expected: "\x1b[?25l\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
expected: "\x1b[?25l\x1b[?2004h\x1b[?25lsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
},
|
||||
{
|
||||
name: "cursor_hideshow",
|
||||
cmds: []Cmd{HideCursor, ShowCursor},
|
||||
expected: "\x1b[?25l\x1b[?25l\x1b[?25hsuccess\r\n\x1b[0D\x1b[2K\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
expected: "\x1b[?25l\x1b[?2004h\x1b[?25l\x1b[?25hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
},
|
||||
{
|
||||
name: "bp_stop_start",
|
||||
cmds: []Cmd{DisableBracketedPaste, EnableBracketedPaste},
|
||||
expected: "\x1b[?25l\x1b[?2004h\x1b[?2004l\x1b[?2004hsuccess\r\n\x1b[0D\x1b[2K\x1b[?2004l\x1b[?25h\x1b[?1002l\x1b[?1003l\x1b[?1006l",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -69,7 +74,7 @@ func TestClearMsg(t *testing.T) {
|
|||
}
|
||||
|
||||
if buf.String() != test.expected {
|
||||
t.Errorf("expected embedded sequence, got %q", buf.String())
|
||||
t.Errorf("expected embedded sequence:\n%q\ngot:\n%q", test.expected, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -45,6 +45,9 @@ type standardRenderer struct {
|
|||
// essentially whether or not we're using the full size of the terminal
|
||||
altScreenActive bool
|
||||
|
||||
// whether or not we're currently using bracketed paste
|
||||
bpActive bool
|
||||
|
||||
// renderer dimensions; usually the size of the window
|
||||
width int
|
||||
height int
|
||||
|
@ -410,6 +413,29 @@ func (r *standardRenderer) disableMouseSGRMode() {
|
|||
r.out.DisableMouseExtendedMode()
|
||||
}
|
||||
|
||||
func (r *standardRenderer) enableBracketedPaste() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.out.EnableBracketedPaste()
|
||||
r.bpActive = true
|
||||
}
|
||||
|
||||
func (r *standardRenderer) disableBracketedPaste() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.out.DisableBracketedPaste()
|
||||
r.bpActive = false
|
||||
}
|
||||
|
||||
func (r *standardRenderer) bracketedPasteActive() bool {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
return r.bpActive
|
||||
}
|
||||
|
||||
// setIgnoredLines specifies lines not to be touched by the standard Bubble Tea
|
||||
// renderer.
|
||||
func (r *standardRenderer) setIgnoredLines(from int, to int) {
|
||||
|
|
20
tea.go
20
tea.go
|
@ -81,7 +81,7 @@ func (i inputType) String() string {
|
|||
// generally set with ProgramOptions.
|
||||
//
|
||||
// The options here are treated as bits.
|
||||
type startupOptions byte
|
||||
type startupOptions int16
|
||||
|
||||
func (s startupOptions) has(option startupOptions) bool {
|
||||
return s&option != 0
|
||||
|
@ -93,12 +93,12 @@ const (
|
|||
withMouseAllMotion
|
||||
withANSICompressor
|
||||
withoutSignalHandler
|
||||
|
||||
// Catching panics is incredibly useful for restoring the terminal to a
|
||||
// usable state after a panic occurs. When this is set, Bubble Tea will
|
||||
// recover from panics, print the stack trace, and disable raw mode. This
|
||||
// feature is on by default.
|
||||
withoutCatchPanics
|
||||
withoutBracketedPaste
|
||||
)
|
||||
|
||||
// handlers manages series of channels returned by various processes. It allows
|
||||
|
@ -156,6 +156,8 @@ type Program struct {
|
|||
altScreenWasActive bool
|
||||
ignoreSignals uint32
|
||||
|
||||
bpWasActive bool // was the bracketed paste mode active before releasing the terminal?
|
||||
|
||||
// Stores the original reference to stdin for cases where input is not a
|
||||
// TTY on windows and we've automatically opened CONIN$ to receive input.
|
||||
// When the program exits this will be restored.
|
||||
|
@ -360,6 +362,12 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
|||
case hideCursorMsg:
|
||||
p.renderer.hideCursor()
|
||||
|
||||
case enableBracketedPasteMsg:
|
||||
p.renderer.enableBracketedPaste()
|
||||
|
||||
case disableBracketedPasteMsg:
|
||||
p.renderer.disableBracketedPaste()
|
||||
|
||||
case execMsg:
|
||||
// NB: this blocks.
|
||||
p.exec(msg.cmd, msg.fn)
|
||||
|
@ -496,6 +504,9 @@ func (p *Program) Run() (Model, error) {
|
|||
if p.startupOptions&withAltScreen != 0 {
|
||||
p.renderer.enterAltScreen()
|
||||
}
|
||||
if p.startupOptions&withoutBracketedPaste == 0 {
|
||||
p.renderer.enableBracketedPaste()
|
||||
}
|
||||
if p.startupOptions&withMouseCellMotion != 0 {
|
||||
p.renderer.enableMouseCellMotion()
|
||||
p.renderer.enableMouseSGRMode()
|
||||
|
@ -656,6 +667,7 @@ func (p *Program) ReleaseTerminal() error {
|
|||
}
|
||||
|
||||
p.altScreenWasActive = p.renderer.altScreen()
|
||||
p.bpWasActive = p.renderer.bracketedPasteActive()
|
||||
return p.restoreTerminalState()
|
||||
}
|
||||
|
||||
|
@ -671,7 +683,6 @@ func (p *Program) RestoreTerminal() error {
|
|||
if err := p.initCancelReader(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.altScreenWasActive {
|
||||
p.renderer.enterAltScreen()
|
||||
} else {
|
||||
|
@ -681,6 +692,9 @@ func (p *Program) RestoreTerminal() error {
|
|||
if p.renderer != nil {
|
||||
p.renderer.start()
|
||||
}
|
||||
if p.bpWasActive {
|
||||
p.renderer.enableBracketedPaste()
|
||||
}
|
||||
|
||||
// If the output is a terminal, it may have been resized while another
|
||||
// process was at the foreground, in which case we may not have received
|
||||
|
|
Loading…
Reference in New Issue