Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support copying text (OSC52) & hyperlinks (OSC8) #80

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@

# Dependency directories (remove the comment below to include it)
# vendor/

aymanbagabas marked this conversation as resolved.
Show resolved Hide resolved
# MacOS specific
.DS_Store
174 changes: 93 additions & 81 deletions README.md

Large diffs are not rendered by default.

68 changes: 0 additions & 68 deletions color.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ package termenv
import (
"errors"
"fmt"
"image/color"
"math"
"strconv"
"strings"

"github.com/lucasb-eyer/go-colorful"
Expand Down Expand Up @@ -69,72 +67,6 @@ func ConvertToRGB(c Color) colorful.Color {
return ch
}

// Convert transforms a given Color to a Color supported within the Profile.
func (p Profile) Convert(c Color) Color {
if p == Ascii {
return NoColor{}
}

switch v := c.(type) {
case ANSIColor:
return v

case ANSI256Color:
if p == ANSI {
return ansi256ToANSIColor(v)
}
return v

case RGBColor:
h, err := colorful.Hex(string(v))
if err != nil {
return nil
}
if p < TrueColor {
ac := hexToANSI256Color(h)
if p == ANSI {
return ansi256ToANSIColor(ac)
}
return ac
}
return v
}

return c
}

// Color creates a Color from a string. Valid inputs are hex colors, as well as
// ANSI color codes (0-15, 16-255).
func (p Profile) Color(s string) Color {
if len(s) == 0 {
return nil
}

var c Color
if strings.HasPrefix(s, "#") {
c = RGBColor(s)
} else {
i, err := strconv.Atoi(s)
if err != nil {
return nil
}

if i < 16 {
c = ANSIColor(i)
} else {
c = ANSI256Color(i)
}
}

return p.Convert(c)
}

// FromColor creates a Color from a color.Color.
func (p Profile) FromColor(c color.Color) Color {
col, _ := colorful.MakeColor(c)
return p.Color(col.Hex())
}

// Sequence returns the ANSI Sequence for the color.
func (c NoColor) Sequence(bg bool) string {
return ""
Expand Down
16 changes: 16 additions & 0 deletions copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package termenv

import (
"github.com/aymanbagabas/go-osc52"
)

// Copy copies text to clipboard using OSC 52 escape sequence.
func (o Output) Copy(str string) {
out := osc52.NewOutput(o.tty, o.environ.Environ())
out.Copy(str)
}

// Copy copies text to clipboard using OSC 52 escape sequence.
func Copy(str string) {
output.Copy(str)
}
7 changes: 7 additions & 0 deletions examples/hello-world/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,11 @@ 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())

hw := "Hello, world!"
termenv.Copy(hw)
fmt.Println()
fmt.Printf("\t%q copied to clipboard\n", hw)

fmt.Printf("\t%s", termenv.Hyperlink("http://example.com", "This is a link"))
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/muesli/termenv
go 1.13

require (
github.com/aymanbagabas/go-osc52 v1.0.3
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-runewidth v0.0.13
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
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/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=
Expand Down
15 changes: 15 additions & 0 deletions hyperlink.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package termenv

import (
"fmt"
)

// Hyperlink creates a hyperlink using OSC8.
func Hyperlink(link, name string) string {
return output.Hyperlink(link, name)
}

// Hyperlink creates a hyperlink using OSC8.
func (o *Output) Hyperlink(link, name string) string {
return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", link, name)
}
89 changes: 89 additions & 0 deletions output.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package termenv

import (
"io"
"os"
)

var (
// output is the default global output.
output = NewOutputWithProfile(os.Stdout, &osEnviron{}, ANSI)
)

// File represents a file descriptor.
type File interface {
io.ReadWriter
Fd() uintptr
}

// Output is a terminal output.
type Output struct {
Profile
tty File
environ Environ
}

// Environ is an interface for getting environment variables.
type Environ interface {
Environ() []string
Getenv(string) string
}

type osEnviron struct{}

func (oe *osEnviron) Environ() []string {
return os.Environ()
}

func (oe *osEnviron) Getenv(key string) string {
return os.Getenv(key)
}

// DefaultOutput returns the default global output.
func DefaultOutput() *Output {
return output
}

// NewOutput returns a new Output for the given file descriptor.
func NewOutput(tty File, environ Environ) *Output {
o := NewOutputWithProfile(tty, environ, Ascii)
if o.isTTY() {
o.Profile = o.EnvColorProfile()
}
return o
}

// NewOutputWithProfile returns a new Output for the given file descriptor and
// profile.
func NewOutputWithProfile(tty File, environ Environ, profile Profile) *Output {
return &Output{
Profile: profile,
tty: tty,
environ: environ,
}
}

// ForegroundColor returns the terminal's default foreground color.
func (o Output) ForegroundColor() Color {
if !o.isTTY() {
return NoColor{}
}

return o.foregroundColor()
}

// BackgroundColor returns the terminal's default background color.
func (o Output) BackgroundColor() Color {
if !o.isTTY() {
return NoColor{}
}

return o.backgroundColor()
}

// HasDarkBackground returns whether terminal uses a dark-ish background.
func (o Output) HasDarkBackground() bool {
c := ConvertToRGB(o.BackgroundColor())
_, _, l := c.Hsl()
return l < 0.5
}
97 changes: 97 additions & 0 deletions profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package termenv

import (
"image/color"
"strconv"
"strings"

"github.com/lucasb-eyer/go-colorful"
)

// Profile is a color profile: Ascii, ANSI, ANSI256, or TrueColor.
type Profile int

const (
// Ascii, uncolored profile.
Ascii = Profile(iota) //nolint:revive
// ANSI, 4-bit color profile
ANSI
// ANSI256, 8-bit color profile
ANSI256
// TrueColor, 24-bit color profile
TrueColor
)

// String returns a new Style.
func (p Profile) String(s ...string) Style {
return Style{
Profile: p,
string: strings.Join(s, " "),
}
}

// Convert transforms a given Color to a Color supported within the Profile.
func (p Profile) Convert(c Color) Color {
if p == Ascii {
return NoColor{}
}

switch v := c.(type) {
case ANSIColor:
return v

case ANSI256Color:
if p == ANSI {
return ansi256ToANSIColor(v)
}
return v

case RGBColor:
h, err := colorful.Hex(string(v))
if err != nil {
return nil
}
if p < TrueColor {
ac := hexToANSI256Color(h)
if p == ANSI {
return ansi256ToANSIColor(ac)
}
return ac
}
return v
}

return c
}

// Color creates a Color from a string. Valid inputs are hex colors, as well as
// ANSI color codes (0-15, 16-255).
func (p Profile) Color(s string) Color {
if len(s) == 0 {
return nil
}

var c Color
if strings.HasPrefix(s, "#") {
c = RGBColor(s)
} else {
i, err := strconv.Atoi(s)
if err != nil {
return nil
}

if i < 16 {
c = ANSIColor(i)
} else {
c = ANSI256Color(i)
}
}

return p.Convert(c)
}

// FromColor creates a Color from a color.Color.
func (p Profile) FromColor(c color.Color) Color {
col, _ := colorful.MakeColor(c)
return p.Color(col.Hex())
}
Loading