From 27f29d8328742b97c08c2186027d32cdc438345c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 14 Jul 2025 22:08:09 +0000 Subject: [PATCH 1/3] term: remove duplicate flag and add comment on windows Both windows.ENABLE_PROCESSED_INPUT and windows.ENABLE_PROCESSED_OUTPUT have the same value of 0x1. Using makeRaw on a console output screen buffer handle wouldn't make sense since on Windows the input handle and the output screen buffer handle are two separate things. See https://learn.microsoft.com/en-us/windows/console/setconsolemode Change-Id: I19feadf8da303a1ada8e517399416a4730845900 GitHub-Last-Rev: 832fc7e8af65b1377f2e46653aed8011d51fd3b9 GitHub-Pull-Request: golang/term#14 Reviewed-on: https://go-review.googlesource.com/c/term/+/562779 Reviewed-by: Michael Pratt Reviewed-by: Laurent Demailly Reviewed-by: Jorropo LUCI-TryBot-Result: Go LUCI Reviewed-by: Cherry Mui --- term_windows.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/term_windows.go b/term_windows.go index df6bf94..0ddd81c 100644 --- a/term_windows.go +++ b/term_windows.go @@ -20,12 +20,14 @@ func isTerminal(fd int) bool { return err == nil } +// This is intended to be used on a console input handle. +// See https://learn.microsoft.com/en-us/windows/console/setconsolemode func makeRaw(fd int) (*State, error) { var st uint32 if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { return nil, err } - raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) + raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT) raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil { return nil, err From 4f53e0cd3924d70667107169374a480bfd208348 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Wed, 16 Jul 2025 18:05:05 +0000 Subject: [PATCH 2/3] 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: 0cf26df9aec994dfc61392e98b9034fe7133fb7f GitHub-Pull-Request: golang/term#21 Reviewed-on: https://go-review.googlesource.com/c/term/+/687755 Reviewed-by: Michael Pratt Reviewed-by: Michael Knyszek LUCI-TryBot-Result: Go LUCI --- terminal.go | 9 +++++++-- terminal_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/terminal.go b/terminal.go index 13e9a64..bddb2e2 100644 --- a/terminal.go +++ b/terminal.go @@ -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 { diff --git a/terminal_test.go b/terminal_test.go index 29dd874..5d35cc5 100644 --- a/terminal_test.go +++ b/terminal_test.go @@ -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"), From a35244d18d7756b12deca31a518c0fa1327d050a Mon Sep 17 00:00:00 2001 From: Gopher Robot Date: Thu, 7 Aug 2025 05:04:15 -0700 Subject: [PATCH 3/3] go.mod: update golang.org/x dependencies Update golang.org/x dependencies to their latest tagged versions. Change-Id: I99ad2113f2fb7d1f8e22475659335424ff94ffb7 Reviewed-on: https://go-review.googlesource.com/c/term/+/693976 LUCI-TryBot-Result: Go LUCI Reviewed-by: David Chase Auto-Submit: Gopher Robot Reviewed-by: Dmitri Shuralyov --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e30f881..01f695e 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module golang.org/x/term go 1.23.0 -require golang.org/x/sys v0.34.0 +require golang.org/x/sys v0.35.0 diff --git a/go.sum b/go.sum index a197cce..5ace7b9 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=