-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(term): ansi: add passthrough sequences
Screen and Tmux passthrough
- Loading branch information
1 parent
cff6f7e
commit 0100b8b
Showing
2 changed files
with
126 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package ansi | ||
|
||
import ( | ||
"bytes" | ||
) | ||
|
||
// ScreenPassthrough wraps the given ANSI sequence in a DCS passthrough | ||
// sequence to be sent to the outer terminal. This is used to send raw escape | ||
// sequences to the outer terminal when running inside GNU Screen. | ||
// | ||
// DCS <data> ST | ||
// | ||
// Note: Screen limits the length of string sequences to 768 bytes (since 2014). | ||
// Use zero to indicate no limit, otherwise, this will chunk the returned | ||
// string into limit sized chunks. | ||
// | ||
// See: https://www.gnu.org/software/screen/manual/screen.html#String-Escapes | ||
// See: https://git.savannah.gnu.org/cgit/screen.git/tree/src/screen.h?id=c184c6ec27683ff1a860c45be5cf520d896fd2ef#n44 | ||
func ScreenPassthrough(seq string, limit int) string { | ||
var b bytes.Buffer | ||
b.WriteString("\x1bP") | ||
if limit > 0 { | ||
for i := 0; i < len(seq); i += limit { | ||
end := i + limit | ||
if end > len(seq) { | ||
end = len(seq) | ||
} | ||
b.WriteString(seq[i:end]) | ||
if end < len(seq) { | ||
b.WriteString("\x1b\\\x1bP") | ||
} | ||
} | ||
} else { | ||
b.WriteString(seq) | ||
} | ||
b.WriteString("\x1b\\") | ||
return b.String() | ||
} | ||
|
||
// TmuxPassthrough wraps the given ANSI sequence in a special DCS passthrough | ||
// sequence to be sent to the outer terminal. This is used to send raw escape | ||
// sequences to the outer terminal when running inside Tmux. | ||
// | ||
// DCS tmux ; <escaped-data> ST | ||
// | ||
// Where <escaped-data> is the given sequence in which all occurrences of ESC | ||
// (0x1b) are doubled i.e. replaced with ESC ESC (0x1b 0x1b). | ||
// | ||
// Note: this needs the `allow-passthrough` option to be set to `on`. | ||
// | ||
// See: https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it | ||
func TmuxPassthrough(seq string) string { | ||
var b bytes.Buffer | ||
b.WriteString("\x1bPtmux;") | ||
for i := 0; i < len(seq); i++ { | ||
if seq[i] == ESC { | ||
b.WriteByte(ESC) | ||
} | ||
b.WriteByte(seq[i]) | ||
} | ||
b.WriteString("\x1b\\") | ||
return b.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package ansi_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/charmbracelet/x/exp/term/ansi" | ||
) | ||
|
||
var passthroughCases = []struct { | ||
name string | ||
seq string | ||
limit int | ||
screen string | ||
tmux string | ||
}{ | ||
{ | ||
name: "empty", | ||
seq: "", | ||
screen: "\x1bP\x1b\\", | ||
tmux: "\x1bPtmux;\x1b\\", | ||
}, | ||
{ | ||
name: "short", | ||
seq: "hello", | ||
screen: "\x1bPhello\x1b\\", | ||
tmux: "\x1bPtmux;hello\x1b\\", | ||
}, | ||
{ | ||
name: "limit", | ||
seq: "foobarbaz", | ||
limit: 3, | ||
screen: "\x1bPfoo\x1b\\\x1bPbar\x1b\\\x1bPbaz\x1b\\", | ||
tmux: "\x1bPtmux;foobarbaz\x1b\\", | ||
}, | ||
{ | ||
name: "escaped", | ||
seq: "\x1b]52;c;Zm9vYmFy\x07", | ||
screen: "\x1bP\x1b]52;c;Zm9vYmFy\x07\x1b\\", | ||
tmux: "\x1bPtmux;\x1b\x1b]52;c;Zm9vYmFy\x07\x1b\\", | ||
}, | ||
} | ||
|
||
func TestScreenPassthrough(t *testing.T) { | ||
for i, tt := range passthroughCases { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got := ansi.ScreenPassthrough(tt.seq, tt.limit) | ||
if got != tt.screen { | ||
t.Errorf("case: %d, ScreenPassthrough() = %q, want %q", i+1, got, tt.screen) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestTmuxPassthrough(t *testing.T) { | ||
for i, tt := range passthroughCases { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got := ansi.TmuxPassthrough(tt.seq) | ||
if got != tt.tmux { | ||
t.Errorf("case: %d, TmuxPassthrough() = %q, want %q", i+1, got, tt.tmux) | ||
} | ||
}) | ||
} | ||
} |