mirror of
https://github.com/golang/term.git
synced 2026-01-29 07:02:06 +03:00
term: allow multi-line bracketed paste to not create single line with verbatim LFs
Treat "\n" (LF) like "Enter" (CR)
Avoids that when pasting 3 lines
(with a terminal like kitty, ghostty, alacritty that do not change the clipboard
in bracketed paste mode)
it turns into 1 prompt looking like:
Test> line one
..............line.two
......................line.three
Fixes golang/go#74600
Change-Id: I4a86044a4a175eccb3a96dbf7021fee97a5940ce
GitHub-Last-Rev: 0cf26df9ae
GitHub-Pull-Request: golang/term#21
Reviewed-on: https://go-review.googlesource.com/c/term/+/687755
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
committed by
Michael Pratt
parent
27f29d8328
commit
4f53e0cd39
@@ -146,6 +146,7 @@ const (
|
||||
keyCtrlD = 4
|
||||
keyCtrlU = 21
|
||||
keyEnter = '\r'
|
||||
keyLF = '\n'
|
||||
keyEscape = 27
|
||||
keyBackspace = 127
|
||||
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
||||
@@ -497,7 +498,7 @@ func (t *Terminal) historyAdd(entry string) {
|
||||
// handleKey processes the given key and, optionally, returns a line of text
|
||||
// that the user has entered.
|
||||
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||
if t.pasteActive && key != keyEnter {
|
||||
if t.pasteActive && key != keyEnter && key != keyLF {
|
||||
t.addKeyToLine(key)
|
||||
return
|
||||
}
|
||||
@@ -567,7 +568,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||
t.setLine(runes, len(runes))
|
||||
}
|
||||
}
|
||||
case keyEnter:
|
||||
case keyEnter, keyLF:
|
||||
t.moveCursorToPos(len(t.line))
|
||||
t.queue([]rune("\r\n"))
|
||||
line = string(t.line)
|
||||
@@ -812,6 +813,10 @@ func (t *Terminal) readLine() (line string, err error) {
|
||||
if !t.pasteActive {
|
||||
lineIsPasted = false
|
||||
}
|
||||
// If we have CR, consume LF if present (CRLF sequence) to avoid returning an extra empty line.
|
||||
if key == keyEnter && len(rest) > 0 && rest[0] == keyLF {
|
||||
rest = rest[1:]
|
||||
}
|
||||
line, lineOk = t.handleKey(key)
|
||||
}
|
||||
if len(rest) > 0 {
|
||||
|
||||
@@ -6,6 +6,8 @@ package term
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
@@ -208,12 +210,24 @@ var keyPressTests = []struct {
|
||||
line: "efgh",
|
||||
throwAwayLines: 1,
|
||||
},
|
||||
{
|
||||
// Newline in bracketed paste mode should still work.
|
||||
in: "abc\x1b[200~d\nefg\x1b[201~h\r",
|
||||
line: "efgh",
|
||||
throwAwayLines: 1,
|
||||
},
|
||||
{
|
||||
// Lines consisting entirely of pasted data should be indicated as such.
|
||||
in: "\x1b[200~a\r",
|
||||
line: "a",
|
||||
err: ErrPasteIndicator,
|
||||
},
|
||||
{
|
||||
// Lines consisting entirely of pasted data should be indicated as such (\n paste).
|
||||
in: "\x1b[200~a\n",
|
||||
line: "a",
|
||||
err: ErrPasteIndicator,
|
||||
},
|
||||
{
|
||||
// Ctrl-C terminates readline
|
||||
in: "\003",
|
||||
@@ -296,6 +310,36 @@ func TestRender(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCRLF(t *testing.T) {
|
||||
c := &MockTerminal{
|
||||
toSend: []byte("line1\rline2\r\nline3\n"),
|
||||
// bytesPerRead 0 in this test means read all at once
|
||||
// CR+LF need to be in same read for ReadLine to not produce an extra empty line
|
||||
// which is what terminals do for reasonably small paste. if way many lines are pasted
|
||||
// and going over say 1k-16k buffer, readline current implementation will possibly generate 1
|
||||
// extra empty line, if the CR is in chunk1 and LF in chunk2 (and that's fine).
|
||||
}
|
||||
|
||||
ss := NewTerminal(c, "> ")
|
||||
for i := range 3 {
|
||||
line, err := ss.ReadLine()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read line %d: %v", i+1, err)
|
||||
}
|
||||
expected := fmt.Sprintf("line%d", i+1)
|
||||
if line != expected {
|
||||
t.Fatalf("expected '%s', got '%s'", expected, line)
|
||||
}
|
||||
}
|
||||
line, err := ss.ReadLine()
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.Fatalf("expected EOF after 3 lines, got '%s' with error %v", line, err)
|
||||
}
|
||||
if line != "" {
|
||||
t.Fatalf("expected empty line after EOF, got '%s'", line)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPasswordNotSaved(t *testing.T) {
|
||||
c := &MockTerminal{
|
||||
toSend: []byte("password\r\x1b[A\r"),
|
||||
|
||||
Reference in New Issue
Block a user