Skip to content

Commit

Permalink
feat: add hyperlink support
Browse files Browse the repository at this point in the history
This commit adds support for hyperlinks in lipgloss. Hyperlinks are
useful for rendering text that can be clicked on in a terminal emulator
that supports hyperlinks.
  • Loading branch information
aymanbagabas committed Feb 12, 2025
1 parent 634dfe4 commit f9ad575
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 1 deletion.
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,12 +278,18 @@ 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 {
str = transform(str)
}

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

if s.props == 0 {
return s.maybeConvertTabs(str)
}
Expand Down
32 changes: 32 additions & 0 deletions style_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,3 +562,35 @@ 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[1;38;5;234m\x1b]8;id=123;https://example.com\x07example\x1b]8;;\x07\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

0 comments on commit f9ad575

Please sign in to comment.