diff --git a/terminal.go b/terminal.go index 5fdfff0..6ec537c 100644 --- a/terminal.go +++ b/terminal.go @@ -162,6 +162,7 @@ const ( keyDeleteLine keyDelete keyClearScreen + keyTranspose keyPasteStart keyPasteEnd ) @@ -195,6 +196,8 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { return keyDeleteLine, b[1:] case 12: // ^L return keyClearScreen, b[1:] + case 20: // ^T + return keyTranspose, b[1:] case 23: // ^W return keyDeleteWord, b[1:] case 14: // ^N @@ -605,6 +608,24 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) { } case keyCtrlU: 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: // Erases the screen and moves the cursor to the home position. t.queue([]rune("\x1b[2J\x1b[H")) diff --git a/terminal_test.go b/terminal_test.go index 45aeffa..7d0afd8 100644 --- a/terminal_test.go +++ b/terminal_test.go @@ -263,6 +263,66 @@ var keyPressTests = []struct { 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) {