enabled multi-line paste
This commit is contained in:
+139
@@ -3477,3 +3477,142 @@ func TestOffloadToolResultUsesReadPrompt(t *testing.T) {
|
||||
cleanupTemps(tmps)
|
||||
}
|
||||
|
||||
// ---------- editor.feed (bracketed paste / key handling) ----------
|
||||
|
||||
// feedAll decodes every rune in s through e.feed, returning the resulting
|
||||
// actions. It uses a real bufio.Reader so escape sequences are consumed
|
||||
// exactly as they would be in the REPL loop.
|
||||
func feedAll(e *editor, s string) []keyKind {
|
||||
r := bufio.NewReader(strings.NewReader(s))
|
||||
var kinds []keyKind
|
||||
for {
|
||||
rn, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
kinds = append(kinds, e.feed(rn, r))
|
||||
}
|
||||
return kinds
|
||||
}
|
||||
|
||||
// TestFeedBracketedPasteInsertsNewlines verifies that a block pasted between
|
||||
// ESC[200~ and ESC[201~ becomes literal line breaks in the buffer and that only
|
||||
// the final Enter (outside the paste) submits the prompt.
|
||||
func TestFeedBracketedPasteInsertsNewlines(t *testing.T) {
|
||||
e := &editor{hpos: -1}
|
||||
kinds := feedAll(e, "\x1b[200~line1\nline2\nline3\x1b[201~\r")
|
||||
if got := string(e.buf); got != "line1\nline2\nline3" {
|
||||
t.Fatalf("buf = %q, want %q", got, "line1\nline2\nline3")
|
||||
}
|
||||
subs := 0
|
||||
for _, k := range kinds {
|
||||
if k == keySubmit {
|
||||
subs++
|
||||
}
|
||||
}
|
||||
if subs != 1 {
|
||||
t.Fatalf("keySubmit count = %d, want 1 (paste must not trigger submission)", subs)
|
||||
}
|
||||
if kinds[len(kinds)-1] != keySubmit {
|
||||
t.Fatalf("last action = %v, want keySubmit", kinds[len(kinds)-1])
|
||||
}
|
||||
}
|
||||
|
||||
// TestFeedCRLFInsidePasteCountsOnce verifies a CRLF inside a pasted block is
|
||||
// swallowed as a single line break (not two).
|
||||
func TestFeedCRLFInsidePasteCountsOnce(t *testing.T) {
|
||||
e := &editor{hpos: -1}
|
||||
feedAll(e, "\x1b[200~a\r\nb\x1b[201~")
|
||||
if got := string(e.buf); got != "a\nb" {
|
||||
t.Fatalf("buf = %q, want %q", got, "a\nb")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFeedEnterSubmitsOutsidePaste verifies a plain Enter outside a paste
|
||||
// submits the buffer.
|
||||
func TestFeedEnterSubmitsOutsidePaste(t *testing.T) {
|
||||
e := &editor{hpos: -1, buf: []rune("hi"), pos: 2}
|
||||
if k := e.feed('\r', bufio.NewReader(strings.NewReader(""))); k != keySubmit {
|
||||
t.Fatalf("enter action = %v, want keySubmit", k)
|
||||
}
|
||||
if got := string(e.buf); got != "hi" {
|
||||
t.Fatalf("buf = %q, want %q", got, "hi")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFeedCtrlCAbortsOutsidePaste verifies Ctrl+C interrupts when not pasting.
|
||||
func TestFeedCtrlCAbortsOutsidePaste(t *testing.T) {
|
||||
e := &editor{hpos: -1}
|
||||
if k := e.feed(0x03, bufio.NewReader(strings.NewReader(""))); k != keyAbort {
|
||||
t.Fatalf("ctrl-c action = %v, want keyAbort", k)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFeedCtrlCIgnoredInsidePaste verifies a stray ^C byte inside a pasted block
|
||||
// is ignored instead of interrupting.
|
||||
func TestFeedCtrlCIgnoredInsidePaste(t *testing.T) {
|
||||
e := &editor{hpos: -1, pasting: true}
|
||||
if k := e.feed(0x03, bufio.NewReader(strings.NewReader(""))); k != keyNone {
|
||||
t.Fatalf("ctrl-c in paste action = %v, want keyNone", k)
|
||||
}
|
||||
if len(e.buf) != 0 {
|
||||
t.Fatalf("buf = %q, want empty", string(e.buf))
|
||||
}
|
||||
}
|
||||
|
||||
// TestFeedCtrlDEOFEmptyBuffer verifies Ctrl+D exits only on an empty buffer.
|
||||
func TestFeedCtrlDEOFEmptyBuffer(t *testing.T) {
|
||||
e := &editor{hpos: -1}
|
||||
if k := e.feed(0x04, bufio.NewReader(strings.NewReader(""))); k != keyEOF {
|
||||
t.Fatalf("ctrl-d action = %v, want keyEOF", k)
|
||||
}
|
||||
e2 := &editor{hpos: -1, buf: []rune("x"), pos: 1}
|
||||
if k := e2.feed(0x04, bufio.NewReader(strings.NewReader(""))); k != keyNone {
|
||||
t.Fatalf("ctrl-d non-empty action = %v, want keyNone", k)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFeedArrowKeysAndHomeEnd verifies the CSI/SS3 cursor keys still move the
|
||||
// cursor after the refactor.
|
||||
func TestFeedArrowKeysAndHomeEnd(t *testing.T) {
|
||||
e := &editor{hpos: -1, buf: []rune("abc"), pos: 3}
|
||||
e.feed(0x1b, bufio.NewReader(strings.NewReader("[D")))
|
||||
if e.pos != 2 {
|
||||
t.Fatalf("after left arrow pos = %d, want 2", e.pos)
|
||||
}
|
||||
e.feed(0x1b, bufio.NewReader(strings.NewReader("[H")))
|
||||
if e.pos != 0 {
|
||||
t.Fatalf("after home pos = %d, want 0", e.pos)
|
||||
}
|
||||
e.feed(0x1b, bufio.NewReader(strings.NewReader("[F")))
|
||||
if e.pos != 3 {
|
||||
t.Fatalf("after end pos = %d, want 3", e.pos)
|
||||
}
|
||||
// Up arrow with history should restore the newest entry.
|
||||
e.hist = []string{"first", "second"}
|
||||
e.feed(0x1b, bufio.NewReader(strings.NewReader("[A")))
|
||||
if got := string(e.buf); got != "second" {
|
||||
t.Fatalf("after up arrow buf = %q, want %q", got, "second")
|
||||
}
|
||||
}
|
||||
|
||||
// TestFeedTabInserted verifies Tab is inserted literally (not a control no-op).
|
||||
func TestFeedTabInserted(t *testing.T) {
|
||||
e := &editor{hpos: -1}
|
||||
e.feed('\t', bufio.NewReader(strings.NewReader("")))
|
||||
if got := string(e.buf); got != "\t" {
|
||||
t.Fatalf("buf = %q, want a tab", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFeedInsertAtCursor verifies insert() splices at the cursor.
|
||||
func TestFeedInsertAtCursor(t *testing.T) {
|
||||
e := &editor{hpos: -1, buf: []rune("ac"), pos: 1}
|
||||
e.feed('b', bufio.NewReader(strings.NewReader("")))
|
||||
if got := string(e.buf); got != "abc" {
|
||||
t.Fatalf("buf = %q, want %q", got, "abc")
|
||||
}
|
||||
if e.pos != 2 {
|
||||
t.Fatalf("pos = %d, want 2", e.pos)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user