diff --git a/examples/hello-world/main.go b/examples/hello-world/main.go index 8d96c7b..48633c8 100644 --- a/examples/hello-world/main.go +++ b/examples/hello-world/main.go @@ -40,11 +40,13 @@ func main() { fmt.Printf("\n\t%s %s\n", termenv.String("Has foreground color").Bold(), termenv.ForegroundColor()) fmt.Printf("\t%s %s\n", termenv.String("Has background color").Bold(), termenv.BackgroundColor()) fmt.Printf("\t%s %t\n", termenv.String("Has dark background?").Bold(), termenv.HasDarkBackground()) + fmt.Println() hw := "Hello, world!" termenv.Copy(hw) - fmt.Println() fmt.Printf("\t%q copied to clipboard\n", hw) + fmt.Println() fmt.Printf("\t%s", termenv.Hyperlink("http://example.com", "This is a link")) + fmt.Println() } diff --git a/examples/ssh/go.mod b/examples/ssh/go.mod new file mode 100644 index 0000000..66d96aa --- /dev/null +++ b/examples/ssh/go.mod @@ -0,0 +1,24 @@ +module github.com/muesli/termenv/examples/ssh + +go 1.18 + +require ( + github.com/charmbracelet/wish v0.4.0 + github.com/creack/pty v1.1.18 + github.com/gliderlabs/ssh v0.3.4 + github.com/muesli/termenv v0.11.1-0.20220601154942-afd10a1cfe15 +) + +require ( + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/aymanbagabas/go-osc52 v1.0.3 // indirect + github.com/caarlos0/sshmarshal v0.1.0 // indirect + github.com/charmbracelet/keygen v0.3.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect + golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect +) diff --git a/examples/ssh/go.sum b/examples/ssh/go.sum new file mode 100644 index 0000000..b56386f --- /dev/null +++ b/examples/ssh/go.sum @@ -0,0 +1,43 @@ +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= +github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= +github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBcsan2Y= +github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM= +github.com/charmbracelet/wish v0.4.0 h1:MLo8JjyvSK1lYkhPCzpI+8pwpKO8cUDP4GZtEoOKu4c= +github.com/charmbracelet/wish v0.4.0/go.mod h1:jRL2Shd80OlP77bR8x3v8PrLCtkYCc/1nUV1eGexNj0= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= +github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.11.1-0.20220601154942-afd10a1cfe15 h1:Rtce5txOG+kHRA1VwLUez8ZNgJPoYTJerC3lVv8v5eQ= +github.com/muesli/termenv v0.11.1-0.20220601154942-afd10a1cfe15/go.mod h1:bN6sPNtkiahdhHv2Xm6RGU16LSCxfbIZvMfqjOCfrR4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/examples/ssh/main.go b/examples/ssh/main.go new file mode 100644 index 0000000..488937e --- /dev/null +++ b/examples/ssh/main.go @@ -0,0 +1,124 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + + "github.com/charmbracelet/wish" + "github.com/creack/pty" + "github.com/gliderlabs/ssh" + "github.com/muesli/termenv" +) + +type sshOutput struct { + ssh.Session + pty *os.File +} + +func (s *sshOutput) Fd() uintptr { + return s.pty.Fd() +} + +type sshEnviron struct { + environ []string +} + +func (s *sshEnviron) Getenv(key string) string { + for _, v := range s.environ { + if strings.HasPrefix(v, key+"=") { + return v[len(key)+1:] + } + } + return "" +} + +func (s *sshEnviron) Environ() []string { + return s.environ +} + +func outputFromSession(s ssh.Session) *termenv.Output { + sshPty, _, _ := s.Pty() + pty, _, err := pty.Open() + if err != nil { + panic(err) + } + o := &sshOutput{ + Session: s, + pty: pty, + } + environ := s.Environ() + environ = append(environ, fmt.Sprintf("TERM=%s", sshPty.Term)) + e := &sshEnviron{ + environ: environ, + } + return termenv.NewOutput(o, e) +} + +func main() { + s, err := wish.NewServer( + wish.WithAddress(":2345"), + wish.WithHostKeyPath("termenv"), + wish.WithMiddleware( + func(sh ssh.Handler) ssh.Handler { + return func(s ssh.Session) { + output := outputFromSession(s) + + p := output.ColorProfile() + fmt.Fprintf(s, "\tColor Profile: %d\n", p) + + fmt.Fprintf(s, "\n\t%s %s %s %s %s", + output.String("bold").Bold(), + output.String("faint").Faint(), + output.String("italic").Italic(), + output.String("underline").Underline(), + output.String("crossout").CrossOut(), + ) + + fmt.Fprintf(s, "\n\t%s %s %s %s %s %s %s", + output.String("red").Foreground(p.Color("#E88388")), + output.String("green").Foreground(p.Color("#A8CC8C")), + output.String("yellow").Foreground(p.Color("#DBAB79")), + output.String("blue").Foreground(p.Color("#71BEF2")), + output.String("magenta").Foreground(p.Color("#D290E4")), + output.String("cyan").Foreground(p.Color("#66C2CD")), + output.String("gray").Foreground(p.Color("#B9BFCA")), + ) + + fmt.Fprintf(s, "\n\t%s %s %s %s %s %s %s\n\n", + output.String("red").Foreground(p.Color("0")).Background(p.Color("#E88388")), + output.String("green").Foreground(p.Color("0")).Background(p.Color("#A8CC8C")), + output.String("yellow").Foreground(p.Color("0")).Background(p.Color("#DBAB79")), + output.String("blue").Foreground(p.Color("0")).Background(p.Color("#71BEF2")), + output.String("magenta").Foreground(p.Color("0")).Background(p.Color("#D290E4")), + output.String("cyan").Foreground(p.Color("0")).Background(p.Color("#66C2CD")), + output.String("gray").Foreground(p.Color("0")).Background(p.Color("#B9BFCA")), + ) + + fmt.Fprintf(s, "\n\t%s %s\n", output.String("Has foreground color").Bold(), output.ForegroundColor()) + fmt.Fprintf(s, "\t%s %s\n", output.String("Has background color").Bold(), output.BackgroundColor()) + fmt.Fprintf(s, "\t%s %t\n", output.String("Has dark background?").Bold(), output.HasDarkBackground()) + fmt.Fprintln(s) + + hw := "Hello, world!" + output.Copy(hw) + fmt.Fprintf(s, "\t%q copied to clipboard\n", hw) + fmt.Fprintln(s) + + fmt.Fprintf(s, "\t%s", output.Hyperlink("http://example.com", "This is a link")) + fmt.Fprintln(s) + + sh(s) + } + }, + ), + ) + if err != nil { + log.Fatal(err) + } + log.Printf("Listening on %s", s.Addr) + if err := s.ListenAndServe(); err != nil { + log.Fatal(err) + } +} diff --git a/termenv_unix.go b/termenv_unix.go index 9eeca55..03eb079 100644 --- a/termenv_unix.go +++ b/termenv_unix.go @@ -231,7 +231,7 @@ func (o Output) termStatusReport(sequence int) (string, error) { t, err := unix.IoctlGetTermios(fd, tcgetattr) if err != nil { - return "", ErrStatusReport + return "", fmt.Errorf("%s: %s", ErrStatusReport, err) } defer unix.IoctlSetTermios(fd, tcsetattr, t) //nolint:errcheck @@ -239,7 +239,7 @@ func (o Output) termStatusReport(sequence int) (string, error) { noecho.Lflag = noecho.Lflag &^ unix.ECHO noecho.Lflag = noecho.Lflag &^ unix.ICANON if err := unix.IoctlSetTermios(fd, tcsetattr, &noecho); err != nil { - return "", ErrStatusReport + return "", fmt.Errorf("%s: %s", ErrStatusReport, err) } // first, send OSC query, which is ignored by terminal which do not support it @@ -251,7 +251,7 @@ func (o Output) termStatusReport(sequence int) (string, error) { // read the next response res, isOSC, err := readNextResponse(o.tty) if err != nil { - return "", err + return "", fmt.Errorf("%s: %s", ErrStatusReport, err) } // if this is not OSC response, then the terminal does not support it