mirror of
https://github.com/golang/term.git
synced 2026-02-08 19:56:05 +03:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7e5b0437f | ||
|
|
943f25d359 | ||
|
|
9b991dd831 | ||
|
|
3863673230 | ||
|
|
1231d5465b | ||
|
|
3475bc8ef1 | ||
|
|
3a0828a666 | ||
|
|
1a11b45a6f | ||
|
|
d862cd548e |
4
go.mod
4
go.mod
@@ -1,5 +1,5 @@
|
|||||||
module golang.org/x/term
|
module golang.org/x/term
|
||||||
|
|
||||||
go 1.23.0
|
go 1.24.0
|
||||||
|
|
||||||
require golang.org/x/sys v0.35.0
|
require golang.org/x/sys v0.40.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1,2 +1,2 @@
|
|||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
|||||||
34
terminal.go
34
terminal.go
@@ -160,7 +160,9 @@ const (
|
|||||||
keyEnd
|
keyEnd
|
||||||
keyDeleteWord
|
keyDeleteWord
|
||||||
keyDeleteLine
|
keyDeleteLine
|
||||||
|
keyDelete
|
||||||
keyClearScreen
|
keyClearScreen
|
||||||
|
keyTranspose
|
||||||
keyPasteStart
|
keyPasteStart
|
||||||
keyPasteEnd
|
keyPasteEnd
|
||||||
)
|
)
|
||||||
@@ -194,6 +196,8 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
|||||||
return keyDeleteLine, b[1:]
|
return keyDeleteLine, b[1:]
|
||||||
case 12: // ^L
|
case 12: // ^L
|
||||||
return keyClearScreen, b[1:]
|
return keyClearScreen, b[1:]
|
||||||
|
case 20: // ^T
|
||||||
|
return keyTranspose, b[1:]
|
||||||
case 23: // ^W
|
case 23: // ^W
|
||||||
return keyDeleteWord, b[1:]
|
return keyDeleteWord, b[1:]
|
||||||
case 14: // ^N
|
case 14: // ^N
|
||||||
@@ -228,6 +232,10 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 4 && b[0] == keyEscape && b[1] == '[' && b[2] == '3' && b[3] == '~' {
|
||||||
|
return keyDelete, b[4:]
|
||||||
|
}
|
||||||
|
|
||||||
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
||||||
switch b[5] {
|
switch b[5] {
|
||||||
case 'C':
|
case 'C':
|
||||||
@@ -413,7 +421,7 @@ func (t *Terminal) eraseNPreviousChars(n int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// countToLeftWord returns then number of characters from the cursor to the
|
// countToLeftWord returns the number of characters from the cursor to the
|
||||||
// start of the previous word.
|
// start of the previous word.
|
||||||
func (t *Terminal) countToLeftWord() int {
|
func (t *Terminal) countToLeftWord() int {
|
||||||
if t.pos == 0 {
|
if t.pos == 0 {
|
||||||
@@ -438,7 +446,7 @@ func (t *Terminal) countToLeftWord() int {
|
|||||||
return t.pos - pos
|
return t.pos - pos
|
||||||
}
|
}
|
||||||
|
|
||||||
// countToRightWord returns then number of characters from the cursor to the
|
// countToRightWord returns the number of characters from the cursor to the
|
||||||
// start of the next word.
|
// start of the next word.
|
||||||
func (t *Terminal) countToRightWord() int {
|
func (t *Terminal) countToRightWord() int {
|
||||||
pos := t.pos
|
pos := t.pos
|
||||||
@@ -478,7 +486,7 @@ func visualLength(runes []rune) int {
|
|||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
|
|
||||||
// histroryAt unlocks the terminal and relocks it while calling History.At.
|
// historyAt unlocks the terminal and relocks it while calling History.At.
|
||||||
func (t *Terminal) historyAt(idx int) (string, bool) {
|
func (t *Terminal) historyAt(idx int) (string, bool) {
|
||||||
t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer.
|
t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer.
|
||||||
defer t.lock.Lock() // panic in At (or Len) protection.
|
defer t.lock.Lock() // panic in At (or Len) protection.
|
||||||
@@ -590,7 +598,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
|||||||
}
|
}
|
||||||
t.line = t.line[:t.pos]
|
t.line = t.line[:t.pos]
|
||||||
t.moveCursorToPos(t.pos)
|
t.moveCursorToPos(t.pos)
|
||||||
case keyCtrlD:
|
case keyCtrlD, keyDelete:
|
||||||
// Erase the character under the current position.
|
// Erase the character under the current position.
|
||||||
// The EOF case when the line is empty is handled in
|
// The EOF case when the line is empty is handled in
|
||||||
// readLine().
|
// readLine().
|
||||||
@@ -600,6 +608,24 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
|||||||
}
|
}
|
||||||
case keyCtrlU:
|
case keyCtrlU:
|
||||||
t.eraseNPreviousChars(t.pos)
|
t.eraseNPreviousChars(t.pos)
|
||||||
|
case keyTranspose:
|
||||||
|
// This transposes the two characters around the cursor and advances the cursor. Best-effort.
|
||||||
|
if len(t.line) < 2 || t.pos < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
swap := t.pos
|
||||||
|
if swap == len(t.line) {
|
||||||
|
swap-- // special: at end of line, swap previous two chars
|
||||||
|
}
|
||||||
|
t.line[swap-1], t.line[swap] = t.line[swap], t.line[swap-1]
|
||||||
|
if t.pos < len(t.line) {
|
||||||
|
t.pos++
|
||||||
|
}
|
||||||
|
if t.echo {
|
||||||
|
t.moveCursorToPos(swap - 1)
|
||||||
|
t.writeLine(t.line[swap-1:])
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
case keyClearScreen:
|
case keyClearScreen:
|
||||||
// Erases the screen and moves the cursor to the home position.
|
// Erases the screen and moves the cursor to the home position.
|
||||||
t.queue([]rune("\x1b[2J\x1b[H"))
|
t.queue([]rune("\x1b[2J\x1b[H"))
|
||||||
|
|||||||
@@ -238,6 +238,91 @@ var keyPressTests = []struct {
|
|||||||
in: "a\003\r",
|
in: "a\003\r",
|
||||||
err: io.EOF,
|
err: io.EOF,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Delete at EOL: nothing
|
||||||
|
in: "abc\x1b[3~\r",
|
||||||
|
line: "abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Delete in empty string: nothing
|
||||||
|
in: "\x1b[3~\r",
|
||||||
|
line: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Move left, delete: delete 'c'
|
||||||
|
in: "abc\x1b[D\x1b[3~\r",
|
||||||
|
line: "ab",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Home, delete: delete 'a'
|
||||||
|
in: "abc\x1b[H\x1b[3~\r",
|
||||||
|
line: "bc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Home, delete twice: delete 'a' and 'b'
|
||||||
|
in: "abc\x1b[H\x1b[3~\x1b[3~\r",
|
||||||
|
line: "c",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at end of line: transpose last two chars
|
||||||
|
in: "abc\x14\r",
|
||||||
|
line: "acb",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at end then type: cursor stays at end
|
||||||
|
in: "abc\x14N\r",
|
||||||
|
line: "acbN",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T in middle: transpose chars before cursor, move cursor forward
|
||||||
|
in: "abc\x1b[D\x14\r",
|
||||||
|
line: "acb",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T in middle then type: cursor moved past swapped char
|
||||||
|
in: "abcd\x1b[D\x1b[D\x14N\r",
|
||||||
|
line: "acbNd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at pos 1 then type: cursor moves to pos 2
|
||||||
|
in: "abc\x1b[H\x1b[C\x14N\r",
|
||||||
|
line: "baNc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T with one char: do nothing
|
||||||
|
in: "a\x14\r",
|
||||||
|
line: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T with one char then type: cursor unchanged
|
||||||
|
in: "a\x14N\r",
|
||||||
|
line: "aN",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at beginning: do nothing
|
||||||
|
in: "ab\x1b[H\x14\r",
|
||||||
|
line: "ab",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at beginning then type: cursor unchanged, inserts at start
|
||||||
|
in: "ab\x1b[H\x14N\r",
|
||||||
|
line: "Nab",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T on empty line: do nothing
|
||||||
|
in: "\x14\r",
|
||||||
|
line: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multiple Ctrl-T at end: keeps swapping last two
|
||||||
|
in: "abc\x14\x14\r",
|
||||||
|
line: "abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multiple Ctrl-T in middle: progresses through line
|
||||||
|
in: "abcd\x1b[D\x1b[D\x1b[D\x14\x14\x14\r",
|
||||||
|
line: "bcda",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyPresses(t *testing.T) {
|
func TestKeyPresses(t *testing.T) {
|
||||||
@@ -387,7 +472,7 @@ func TestReadPasswordLineEnd(t *testing.T) {
|
|||||||
input string
|
input string
|
||||||
want string
|
want string
|
||||||
}
|
}
|
||||||
var tests = []testType{
|
tests := []testType{
|
||||||
{"\r\n", ""},
|
{"\r\n", ""},
|
||||||
{"test\r\n", "test"},
|
{"test\r\n", "test"},
|
||||||
{"test\r", "test"},
|
{"test\r", "test"},
|
||||||
|
|||||||
Reference in New Issue
Block a user