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

(v2) feat: add hyperlink support #473

Open
wants to merge 1 commit into
base: v2-exp
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
12 changes: 12 additions & 0 deletions get.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,18 @@ func (s Style) GetTransform() func(string) string {
return s.getAsTransform(transformKey)
}

// GetHyperlink returns the hyperlink along with its parameters. If no
// hyperlink is set, empty strings are returned.
func (s Style) GetHyperlink() (link, params string) {
if s.isSet(linkKey) {
link = s.link
}
if s.isSet(linkParamsKey) {
params = s.linkParams
}
return
}

// Returns whether or not the given property is set.
func (s Style) isSet(k propKey) bool {
return s.props.has(k)
Expand Down
24 changes: 23 additions & 1 deletion set.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package lipgloss

import "image/color"
import (
"image/color"
"strings"
)

// Set a value on the underlying rules map.
func (s *Style) set(key propKey, value interface{}) {
Expand Down Expand Up @@ -67,6 +70,10 @@ func (s *Style) set(key propKey, value interface{}) {
s.tabWidth = value.(int)
case transformKey:
s.transform = value.(func(string) string)
case linkKey:
s.link = value.(string)
case linkParamsKey:
s.linkParams = value.(string)
default:
if v, ok := value.(bool); ok { //nolint:nestif
if v {
Expand Down Expand Up @@ -688,6 +695,21 @@ func (s Style) Transform(fn func(string) string) Style {
return s
}

// Hyperlink sets a hyperlink on a style. This is useful for rendering text that
// can be clicked on in a terminal emulator that supports hyperlinks.
//
// Example:
//
// s := lipgloss.NewStyle().Hyperlink("https://charm.sh")
// s := lipgloss.NewStyle().Hyperlink("https://charm.sh", "id=1")
func (s Style) Hyperlink(link string, params ...string) Style {
s.set(linkKey, link)
if len(params) > 0 {
s.set(linkParamsKey, strings.Join(params, ":"))
}
return s
}

// whichSidesInt is a helper method for setting values on sides of a block based
// on the number of arguments. It follows the CSS shorthand rules for blocks
// like margin, padding. and borders. Here are how the rules work:
Expand Down
13 changes: 13 additions & 0 deletions style.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ const (
tabWidthKey

transformKey

// Hyperlink.
linkKey
linkParamsKey
)

// props is a set of properties.
Expand Down Expand Up @@ -107,6 +111,9 @@ type Style struct {
props props
value string

// hyperlink
link, linkParams string

// we store bool props values here
attrs int

Expand Down Expand Up @@ -271,6 +278,8 @@ func (s Style) Render(strs ...string) string {
useSpaceStyler = (underline && !underlineSpaces) || (strikethrough && !strikethroughSpaces) || underlineSpaces || strikethroughSpaces

transform = s.getAsTransform(transformKey)

link, linkParams = s.GetHyperlink()
)

if transform != nil {
Expand Down Expand Up @@ -379,6 +388,10 @@ func (s Style) Render(strs ...string) string {
}

str = b.String()

if len(link) > 0 {
str = ansi.SetHyperlink(link, linkParams) + str + ansi.ResetHyperlink()
}
}

// Padding
Expand Down
64 changes: 64 additions & 0 deletions style_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,3 +562,67 @@ func TestHeight(t *testing.T) {
}
}
}

func TestHyperlink(t *testing.T) {
tests := []struct {
name string
style Style
expected string
}{
{
name: "hyperlink",
style: NewStyle().Hyperlink("https://example.com").SetString("https://example.com"),
expected: "\x1b]8;;https://example.com\x07https://example.com\x1b]8;;\x07",
},
{
name: "hyperlink with text",
style: NewStyle().Hyperlink("https://example.com", "id=123").SetString("example"),
expected: "\x1b]8;id=123;https://example.com\x07example\x1b]8;;\x07",
},
{
name: "hyperlink with text and style",
style: NewStyle().Hyperlink("https://example.com", "id=123").SetString("example").
Bold(true).Foreground(Color("234")),
expected: "\x1b]8;id=123;https://example.com\x07\x1b[1;38;5;234mexample\x1b[m\x1b]8;;\x07",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if tc.style.String() != tc.expected {
t.Fatalf("got: %q, want: %q", tc.style.String(), tc.expected)
}
})
}
}

func TestUnsetHyperlink(t *testing.T) {
tests := []struct {
name string
style Style
expected string
}{
{
name: "unset hyperlink",
style: NewStyle().Hyperlink("https://example.com").SetString("https://example.com").UnsetHyperlink(),
expected: "https://example.com",
},
{
name: "unset hyperlink with text",
style: NewStyle().Hyperlink("https://example.com", "id=123").SetString("example").UnsetHyperlink(),
expected: "example",
},
{
name: "unset hyperlink with text and style",
style: NewStyle().Hyperlink("https://example.com", "id=123").SetString("example").
Bold(true).Foreground(Color("234")).UnsetHyperlink(),
expected: "\x1b[1;38;5;234mexample\x1b[m",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if tc.style.String() != tc.expected {
t.Fatalf("got: %q, want: %q", tc.style.String(), tc.expected)
}
})
}
}
8 changes: 8 additions & 0 deletions unset.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,14 @@ func (s Style) UnsetTransform() Style {
return s
}

// UnsetHyperlink removes the value set by Hyperlink.
func (s Style) UnsetHyperlink() Style {
s.unset(linkKey)
s.unset(linkParamsKey)
s.link, s.linkParams = "", "" // save memory
return s
}

// UnsetString sets the underlying string value to the empty string.
func (s Style) UnsetString() Style {
s.value = ""
Expand Down
Loading