Skip to content

Commit 943f25d

Browse files
committed
x/term: handle transpose
The behavior implemented here matches readline and libedit. Updates golang/go#76826 Change-Id: I893677f9bceaf75aa1dada7d893845728e07057e Reviewed-on: https://go-review.googlesource.com/c/term/+/730441 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Michael Knyszek <mknyszek@google.com> Reviewed-by: David Chase <drchase@google.com>
1 parent 9b991dd commit 943f25d

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

terminal.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ const (
162162
keyDeleteLine
163163
keyDelete
164164
keyClearScreen
165+
keyTranspose
165166
keyPasteStart
166167
keyPasteEnd
167168
)
@@ -195,6 +196,8 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
195196
return keyDeleteLine, b[1:]
196197
case 12: // ^L
197198
return keyClearScreen, b[1:]
199+
case 20: // ^T
200+
return keyTranspose, b[1:]
198201
case 23: // ^W
199202
return keyDeleteWord, b[1:]
200203
case 14: // ^N
@@ -605,6 +608,24 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
605608
}
606609
case keyCtrlU:
607610
t.eraseNPreviousChars(t.pos)
611+
case keyTranspose:
612+
// This transposes the two characters around the cursor and advances the cursor. Best-effort.
613+
if len(t.line) < 2 || t.pos < 1 {
614+
return
615+
}
616+
swap := t.pos
617+
if swap == len(t.line) {
618+
swap-- // special: at end of line, swap previous two chars
619+
}
620+
t.line[swap-1], t.line[swap] = t.line[swap], t.line[swap-1]
621+
if t.pos < len(t.line) {
622+
t.pos++
623+
}
624+
if t.echo {
625+
t.moveCursorToPos(swap - 1)
626+
t.writeLine(t.line[swap-1:])
627+
t.moveCursorToPos(t.pos)
628+
}
608629
case keyClearScreen:
609630
// Erases the screen and moves the cursor to the home position.
610631
t.queue([]rune("\x1b[2J\x1b[H"))

terminal_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,66 @@ var keyPressTests = []struct {
263263
in: "abc\x1b[H\x1b[3~\x1b[3~\r",
264264
line: "c",
265265
},
266+
{
267+
// Ctrl-T at end of line: transpose last two chars
268+
in: "abc\x14\r",
269+
line: "acb",
270+
},
271+
{
272+
// Ctrl-T at end then type: cursor stays at end
273+
in: "abc\x14N\r",
274+
line: "acbN",
275+
},
276+
{
277+
// Ctrl-T in middle: transpose chars before cursor, move cursor forward
278+
in: "abc\x1b[D\x14\r",
279+
line: "acb",
280+
},
281+
{
282+
// Ctrl-T in middle then type: cursor moved past swapped char
283+
in: "abcd\x1b[D\x1b[D\x14N\r",
284+
line: "acbNd",
285+
},
286+
{
287+
// Ctrl-T at pos 1 then type: cursor moves to pos 2
288+
in: "abc\x1b[H\x1b[C\x14N\r",
289+
line: "baNc",
290+
},
291+
{
292+
// Ctrl-T with one char: do nothing
293+
in: "a\x14\r",
294+
line: "a",
295+
},
296+
{
297+
// Ctrl-T with one char then type: cursor unchanged
298+
in: "a\x14N\r",
299+
line: "aN",
300+
},
301+
{
302+
// Ctrl-T at beginning: do nothing
303+
in: "ab\x1b[H\x14\r",
304+
line: "ab",
305+
},
306+
{
307+
// Ctrl-T at beginning then type: cursor unchanged, inserts at start
308+
in: "ab\x1b[H\x14N\r",
309+
line: "Nab",
310+
},
311+
{
312+
// Ctrl-T on empty line: do nothing
313+
in: "\x14\r",
314+
line: "",
315+
},
316+
{
317+
// Multiple Ctrl-T at end: keeps swapping last two
318+
in: "abc\x14\x14\r",
319+
line: "abc",
320+
},
321+
{
322+
// Multiple Ctrl-T in middle: progresses through line
323+
in: "abcd\x1b[D\x1b[D\x1b[D\x14\x14\x14\r",
324+
line: "bcda",
325+
},
266326
}
267327

268328
func TestKeyPresses(t *testing.T) {

0 commit comments

Comments
 (0)