Skip to content

Commit

Permalink
fix #59 (#63)
Browse files Browse the repository at this point in the history
Enable "next word" and "previous word" in many terminals:

* Alt-LeftArrow and Alt-RightArrow in xterm and iTerm2 with default settings,
  where they translate to \x1b\x1b[D and \x1b\x1b[C respectively
* Alt-LeftArrow and Alt-RightArrow in iTerm2 in "Natural text editing mode",
  where they translate to \x1bb and \x1bf
* Alt-LeftArrow and Alt-RightArrow in libvte-based terminals,
  where they translate to \x1b[1;3D and \x1b[1;3C respectively
* Alt-b and Alt-f in xterm, where they translate to \x1bb and \x1bf
  • Loading branch information
slingamn authored Jul 4, 2024
1 parent 087affd commit 621a5c5
Showing 1 changed file with 64 additions and 5 deletions.
69 changes: 64 additions & 5 deletions terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,12 +312,29 @@ func (t *terminal) ioloop() {

func (t *terminal) consumeANSIEscape(buf *bufio.Reader, ansiBuf *bytes.Buffer) (result readResult, err error) {
ansiBuf.Reset()
// initial character is either [ or O; if we got something else,
// treat the sequence as terminated and don't interpret it
initial, _, err := buf.ReadRune()
if err != nil || !(initial == '[' || initial == 'O') {
if err != nil {
return
}
// we already read one \x1b. this can indicate either the start of an ANSI
// escape sequence, or a keychord with Alt (e.g. Alt+f produces `\x1bf` in
// a typical xterm).
switch initial {
case 'f':
// Alt-f in xterm, or Option+RightArrow in iTerm2 with "Natural text editing"
return readResult{r: MetaForward, ok: true}, nil // Alt-f
case 'b':
// Alt-b in xterm, or Option+LeftArrow in iTerm2 with "Natural text editing"
return readResult{r: MetaBackward, ok: true}, nil // Alt-b
case '[', 'O':
// this is a real ANSI escape sequence, read the rest of the sequence below:
case '\x1b':
// Alt plus a real ANSI escape sequence. Handle this specially since
// right now the only cases we want to handle are the arrow keys:
return consumeAltSequence(buf)
default:
return // invalid, ignore
}

// data consists of ; and 0-9 , anything else terminates the sequence
var type_ rune
Expand Down Expand Up @@ -345,9 +362,17 @@ func (t *terminal) consumeANSIEscape(buf *bufio.Reader, ansiBuf *bytes.Buffer) (
}
}
case 'D':
r = CharBackward
if altModifierEnabled(ansiBuf.Bytes()) {
r = MetaBackward
} else {
r = CharBackward
}
case 'C':
r = CharForward
if altModifierEnabled(ansiBuf.Bytes()) {
r = MetaForward
} else {
r = CharForward
}
case 'A':
r = CharPrev
case 'B':
Expand Down Expand Up @@ -379,6 +404,40 @@ func (t *terminal) consumeANSIEscape(buf *bufio.Reader, ansiBuf *bytes.Buffer) (
return // default: no interpretable rune value
}

func consumeAltSequence(buf *bufio.Reader) (result readResult, err error) {
initial, _, err := buf.ReadRune()
if err != nil {
return
}
if initial != '[' {
return
}
second, _, err := buf.ReadRune()
if err != nil {
return
}
switch second {
case 'D':
return readResult{r: MetaBackward, ok: true}, nil
case 'C':
return readResult{r: MetaForward, ok: true}, nil
default:
return
}
}

func altModifierEnabled(payload []byte) bool {
// https://www.xfree86.org/current/ctlseqs.html ; modifier keycodes
// go after the semicolon, e.g. Alt-LeftArrow is `\x1b[1;3D` in VTE
// terminals, where 3 indicates Alt
if semicolonIdx := bytes.IndexByte(payload, ';'); semicolonIdx != -1 {
if string(payload[semicolonIdx+1:]) == "3" {
return true
}
}
return false
}

func parseCPRResponse(payload []byte) (cursorPosition, error) {
if semicolonIdx := bytes.IndexByte(payload, ';'); semicolonIdx != -1 {
if row, err := strconv.Atoi(string(payload[:semicolonIdx])); err == nil {
Expand Down

0 comments on commit 621a5c5

Please sign in to comment.