mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-11-10 04:54:03 -08:00
fix(hyperlink): use unique format to avoid conflicts
BREAKING CHANGE: this can cause existing manual hyperlinks (`[text](link)`) to stop working. To fix, change those to the following syntax: `«text»(link)` resolves #3353
This commit is contained in:
parent
7ce487d6b1
commit
4f83452e84
|
@ -110,10 +110,10 @@ type Writer struct {
|
||||||
osc51 string
|
osc51 string
|
||||||
|
|
||||||
// hyperlink
|
// hyperlink
|
||||||
hasHyperlink bool
|
hasHyperlink bool
|
||||||
hyperlinkBuilder strings.Builder
|
hyperlinkBuilder strings.Builder
|
||||||
squareIndex, roundCount int
|
bracketIndex, roundCount int
|
||||||
hyperlinkState string
|
hyperlinkState string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Writer) Init(shellName string) {
|
func (w *Writer) Init(shellName string) {
|
||||||
|
@ -302,7 +302,7 @@ func (w *Writer) Write(background, foreground, text string) {
|
||||||
w.runes = []rune(text)
|
w.runes = []rune(text)
|
||||||
|
|
||||||
// only run hyperlink logic when we have to
|
// only run hyperlink logic when we have to
|
||||||
w.hasHyperlink = strings.Count(text, "[")+strings.Count(text, "]")+strings.Count(text, "(")+strings.Count(text, ")") >= 4
|
w.hasHyperlink = strings.Count(text, "«")+strings.Count(text, "»")+strings.Count(text, "(")+strings.Count(text, ")") >= 4
|
||||||
|
|
||||||
for i := 0; i < len(w.runes); i++ {
|
for i := 0; i < len(w.runes); i++ {
|
||||||
s := w.runes[i]
|
s := w.runes[i]
|
||||||
|
|
|
@ -20,7 +20,7 @@ func (w *Writer) write(i int, s rune) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s == '[' && w.hyperlinkState == OTHER {
|
if s == '«' && w.hyperlinkState == OTHER {
|
||||||
w.hyperlinkState = TEXT
|
w.hyperlinkState = TEXT
|
||||||
w.hyperlinkBuilder.WriteRune(s)
|
w.hyperlinkBuilder.WriteRune(s)
|
||||||
return
|
return
|
||||||
|
@ -35,12 +35,12 @@ func (w *Writer) write(i int, s rune) {
|
||||||
w.hyperlinkBuilder.WriteRune(s)
|
w.hyperlinkBuilder.WriteRune(s)
|
||||||
|
|
||||||
switch s {
|
switch s {
|
||||||
case ']':
|
case '»':
|
||||||
// potential end of text part of hyperlink
|
// potential end of text part of hyperlink
|
||||||
w.squareIndex = i
|
w.bracketIndex = i
|
||||||
case '(':
|
case '(':
|
||||||
// split into link part
|
// split into link part
|
||||||
if w.squareIndex == i-1 {
|
if w.bracketIndex == i-1 {
|
||||||
w.hyperlinkState = LINK
|
w.hyperlinkState = LINK
|
||||||
}
|
}
|
||||||
if w.hyperlinkState == LINK {
|
if w.hyperlinkState == LINK {
|
||||||
|
@ -63,7 +63,7 @@ func (w *Writer) write(i int, s rune) {
|
||||||
|
|
||||||
func (w *Writer) replaceHyperlink(text string) string {
|
func (w *Writer) replaceHyperlink(text string) string {
|
||||||
// hyperlink matching
|
// hyperlink matching
|
||||||
results := regex.FindNamedRegexMatch("(?P<ALL>(?:\\[(?P<TEXT>.+)\\])(?:\\((?P<URL>.*)\\)))", text)
|
results := regex.FindNamedRegexMatch("(?P<ALL>(?:«(?P<TEXT>.+)»)(?:\\((?P<URL>.*)\\)))", text)
|
||||||
if len(results) != 3 {
|
if len(results) != 3 {
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ func TestGenerateHyperlinkWithUrl(t *testing.T) {
|
||||||
Expected string
|
Expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Text: "[google](http://www.google.be) [maps (2/2)](http://maps.google.be)",
|
Text: "«google»(http://www.google.be) «maps (2/2)»(http://maps.google.be)",
|
||||||
ShellName: shell.FISH,
|
ShellName: shell.FISH,
|
||||||
Expected: "\x1b[47m\x1b[30m\x1b]8;;http://www.google.be\x1b\\google\x1b]8;;\x1b\\ \x1b]8;;http://maps.google.be\x1b\\maps (2/2)\x1b]8;;\x1b\\\x1b[0m",
|
Expected: "\x1b[47m\x1b[30m\x1b]8;;http://www.google.be\x1b\\google\x1b]8;;\x1b\\ \x1b]8;;http://maps.google.be\x1b\\maps (2/2)\x1b]8;;\x1b\\\x1b[0m",
|
||||||
},
|
},
|
||||||
|
@ -46,18 +46,23 @@ func TestGenerateHyperlinkWithUrl(t *testing.T) {
|
||||||
ShellName: shell.PWSH,
|
ShellName: shell.PWSH,
|
||||||
Expected: "\x1b[47m\x1b[30min \x1b[0m\x1b[30m\x1b[1mpwsh \x1b[22m\x1b[47m \x1b[0m",
|
Expected: "\x1b[47m\x1b[30min \x1b[0m\x1b[30m\x1b[1mpwsh \x1b[22m\x1b[47m \x1b[0m",
|
||||||
},
|
},
|
||||||
{Text: "[google](http://www.google.be)", ShellName: shell.ZSH, Expected: "%{\x1b[47m%}%{\x1b[30m%}%{\x1b]8;;http://www.google.be\x1b\\%}google%{\x1b]8;;\x1b\\%}%{\x1b[0m%}"},
|
{Text: "«google»(http://www.google.be)", ShellName: shell.ZSH, Expected: "%{\x1b[47m%}%{\x1b[30m%}%{\x1b]8;;http://www.google.be\x1b\\%}google%{\x1b]8;;\x1b\\%}%{\x1b[0m%}"},
|
||||||
{Text: "[google](http://www.google.be)", ShellName: shell.PWSH, Expected: "\x1b[47m\x1b[30m\x1b]8;;http://www.google.be\x1b\\google\x1b]8;;\x1b\\\x1b[0m"},
|
{Text: "«google»(http://www.google.be)", ShellName: shell.PWSH, Expected: "\x1b[47m\x1b[30m\x1b]8;;http://www.google.be\x1b\\google\x1b]8;;\x1b\\\x1b[0m"},
|
||||||
{
|
{
|
||||||
Text: "[google](http://www.google.be)",
|
Text: "«google»(http://www.google.be)",
|
||||||
ShellName: shell.BASH,
|
ShellName: shell.BASH,
|
||||||
Expected: "\\[\x1b[47m\\]\\[\x1b[30m\\]\\[\x1b]8;;http://www.google.be\x1b\\\\\\]google\\[\x1b]8;;\x1b\\\\\\]\\[\x1b[0m\\]",
|
Expected: "\\[\x1b[47m\\]\\[\x1b[30m\\]\\[\x1b]8;;http://www.google.be\x1b\\\\\\]google\\[\x1b]8;;\x1b\\\\\\]\\[\x1b[0m\\]",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Text: "[google](http://www.google.be) [maps](http://maps.google.be)",
|
Text: "«google»(http://www.google.be) «maps»(http://maps.google.be)",
|
||||||
ShellName: shell.FISH,
|
ShellName: shell.FISH,
|
||||||
Expected: "\x1b[47m\x1b[30m\x1b]8;;http://www.google.be\x1b\\google\x1b]8;;\x1b\\ \x1b]8;;http://maps.google.be\x1b\\maps\x1b]8;;\x1b\\\x1b[0m",
|
Expected: "\x1b[47m\x1b[30m\x1b]8;;http://www.google.be\x1b\\google\x1b]8;;\x1b\\ \x1b]8;;http://maps.google.be\x1b\\maps\x1b]8;;\x1b\\\x1b[0m",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Text: "[]«google»(http://www.google.be)[]",
|
||||||
|
ShellName: shell.FISH,
|
||||||
|
Expected: "\x1b[47m\x1b[30m[]\x1b]8;;http://www.google.be\x1b\\google\x1b]8;;\x1b\\[]\x1b[0m",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
a := Writer{
|
a := Writer{
|
||||||
|
@ -76,9 +81,9 @@ func TestGenerateHyperlinkWithUrlNoName(t *testing.T) {
|
||||||
ShellName string
|
ShellName string
|
||||||
Expected string
|
Expected string
|
||||||
}{
|
}{
|
||||||
{Text: "[](http://www.google.be)", ShellName: shell.ZSH, Expected: "%{\x1b[47m%}%{\x1b[30m%}[](http://www.google.be)%{\x1b[0m%}"},
|
{Text: "«»(http://www.google.be)", ShellName: shell.ZSH, Expected: "%{\x1b[47m%}%{\x1b[30m%}«»(http://www.google.be)%{\x1b[0m%}"},
|
||||||
{Text: "[](http://www.google.be)", ShellName: shell.PWSH, Expected: "\x1b[47m\x1b[30m[](http://www.google.be)\x1b[0m"},
|
{Text: "«»(http://www.google.be)", ShellName: shell.PWSH, Expected: "\x1b[47m\x1b[30m«»(http://www.google.be)\x1b[0m"},
|
||||||
{Text: "[](http://www.google.be)", ShellName: shell.BASH, Expected: "\\[\x1b[47m\\]\\[\x1b[30m\\][](http://www.google.be)\\[\x1b[0m\\]"},
|
{Text: "«»(http://www.google.be)", ShellName: shell.BASH, Expected: "\\[\x1b[47m\\]\\[\x1b[30m\\]«»(http://www.google.be)\\[\x1b[0m\\]"},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
a := Writer{
|
a := Writer{
|
||||||
|
@ -97,10 +102,10 @@ func TestGenerateFileLink(t *testing.T) {
|
||||||
Expected string
|
Expected string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Text: `[Posh](file:C:/Program Files (x86)/Common Files/Microsoft Shared/Posh)`,
|
Text: `«Posh»(file:C:/Program Files (x86)/Common Files/Microsoft Shared/Posh)`,
|
||||||
Expected: "\x1b[47m\x1b[30m\x1b]8;;file:C:/Program Files (x86)/Common Files/Microsoft Shared/Posh\x1b\\Posh\x1b]8;;\x1b\\\x1b[0m",
|
Expected: "\x1b[47m\x1b[30m\x1b]8;;file:C:/Program Files (x86)/Common Files/Microsoft Shared/Posh\x1b\\Posh\x1b]8;;\x1b\\\x1b[0m",
|
||||||
},
|
},
|
||||||
{Text: `[Windows](file:C:/Windows)`, Expected: "\x1b[47m\x1b[30m\x1b]8;;file:C:/Windows\x1b\\Windows\x1b]8;;\x1b\\\x1b[0m"},
|
{Text: `«Windows»(file:C:/Windows)`, Expected: "\x1b[47m\x1b[30m\x1b]8;;file:C:/Windows\x1b\\Windows\x1b]8;;\x1b\\\x1b[0m"},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
a := Writer{
|
a := Writer{
|
||||||
|
|
|
@ -226,13 +226,13 @@ func TestWriteLength(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Bold with color override and link",
|
Case: "Bold with color override and link",
|
||||||
Input: "<b><#ffffff>test</></b> [url](https://example.com)",
|
Input: "<b><#ffffff>test</></b> «url»(https://example.com)",
|
||||||
Expected: 8,
|
Expected: 8,
|
||||||
Colors: &Colors{Foreground: "black", Background: ParentBackground},
|
Colors: &Colors{Foreground: "black", Background: ParentBackground},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Case: "Bold with color override and link and leading/trailing spaces",
|
Case: "Bold with color override and link and leading/trailing spaces",
|
||||||
Input: " <b><#ffffff>test</></b> [url](https://example.com) ",
|
Input: " <b><#ffffff>test</></b> «url»(https://example.com) ",
|
||||||
Expected: 10,
|
Expected: 10,
|
||||||
Colors: &Colors{Foreground: "black", Background: ParentBackground},
|
Colors: &Colors{Foreground: "black", Background: ParentBackground},
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,7 +29,7 @@ type Brewfather struct {
|
||||||
DaysBottled uint
|
DaysBottled uint
|
||||||
DaysBottledOrFermented *uint // help avoid chronic template logic - code will point this to one of above or be nil depending on status
|
DaysBottledOrFermented *uint // help avoid chronic template logic - code will point this to one of above or be nil depending on status
|
||||||
|
|
||||||
URL string // URL of batch page to open if hyperlink enabled on the segment and URL formatting used in template: [name](link)
|
URL string // URL of batch page to open if hyperlink enabled on the segment and URL formatting used in template: «text»(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -207,7 +207,7 @@ func TestOWMSegmentIcons(t *testing.T) {
|
||||||
env := &mock.MockedEnvironment{}
|
env := &mock.MockedEnvironment{}
|
||||||
|
|
||||||
response := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20.3}}`, tc.IconID)
|
response := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20.3}}`, tc.IconID)
|
||||||
expectedString := fmt.Sprintf("[%s (20°C)](http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key)", tc.ExpectedIconString)
|
expectedString := fmt.Sprintf("«%s (20°C)»(http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key)", tc.ExpectedIconString)
|
||||||
|
|
||||||
env.On("HTTPRequest", OWMAPIURL).Return([]byte(response), nil)
|
env.On("HTTPRequest", OWMAPIURL).Return([]byte(response), nil)
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ func TestOWMSegmentIcons(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Nil(t, o.setStatus())
|
assert.Nil(t, o.setStatus())
|
||||||
assert.Equal(t, expectedString, renderTemplate(env, "[{{.Weather}} ({{.Temperature}}{{.UnitIcon}})]({{.URL}})", o), tc.Case)
|
assert.Equal(t, expectedString, renderTemplate(env, "«{{.Weather}} ({{.Temperature}}{{.UnitIcon}})»({{.URL}})", o), tc.Case)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestOWMSegmentFromCache(t *testing.T) {
|
func TestOWMSegmentFromCache(t *testing.T) {
|
||||||
|
@ -250,7 +250,7 @@ func TestOWMSegmentFromCache(t *testing.T) {
|
||||||
|
|
||||||
func TestOWMSegmentFromCacheWithHyperlink(t *testing.T) {
|
func TestOWMSegmentFromCacheWithHyperlink(t *testing.T) {
|
||||||
response := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20}}`, "01d")
|
response := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20}}`, "01d")
|
||||||
expectedString := fmt.Sprintf("[%s (20°C)](http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key)", "\ufa98")
|
expectedString := fmt.Sprintf("«%s (20°C)»(http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key)", "\ufa98")
|
||||||
|
|
||||||
env := &mock.MockedEnvironment{}
|
env := &mock.MockedEnvironment{}
|
||||||
cache := &mock.MockedCache{}
|
cache := &mock.MockedCache{}
|
||||||
|
@ -269,5 +269,5 @@ func TestOWMSegmentFromCacheWithHyperlink(t *testing.T) {
|
||||||
env.On("Cache").Return(cache)
|
env.On("Cache").Return(cache)
|
||||||
|
|
||||||
assert.Nil(t, o.setStatus())
|
assert.Nil(t, o.setStatus())
|
||||||
assert.Equal(t, expectedString, renderTemplate(env, "[{{.Weather}} ({{.Temperature}}{{.UnitIcon}})]({{.URL}})", o))
|
assert.Equal(t, expectedString, renderTemplate(env, "«{{.Weather}} ({{.Temperature}}{{.UnitIcon}})»({{.URL}})", o))
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,9 @@ func url(text, url string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[%s](%s)", text, url), nil
|
return fmt.Sprintf("«%s»(%s)", text, url), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func path(text, path string) (string, error) {
|
func path(text, path string) (string, error) {
|
||||||
return fmt.Sprintf("[%s](file:%s)", text, path), nil
|
return fmt.Sprintf("«%s»(file:%s)", text, path), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ func TestUrl(t *testing.T) {
|
||||||
Template string
|
Template string
|
||||||
ShouldError bool
|
ShouldError bool
|
||||||
}{
|
}{
|
||||||
{Case: "valid url", Expected: "[link](https://ohmyposh.dev)", Template: `{{ url "link" "https://ohmyposh.dev" }}`},
|
{Case: "valid url", Expected: "«link»(https://ohmyposh.dev)", Template: `{{ url "link" "https://ohmyposh.dev" }}`},
|
||||||
{Case: "invalid url", Expected: "", Template: `{{ url "link" "Foo" }}`, ShouldError: true},
|
{Case: "invalid url", Expected: "", Template: `{{ url "link" "Foo" }}`, ShouldError: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ func TestPath(t *testing.T) {
|
||||||
Expected string
|
Expected string
|
||||||
Template string
|
Template string
|
||||||
}{
|
}{
|
||||||
{Case: "valid path", Expected: "[link](file:/test/test)", Template: `{{ path "link" "/test/test" }}`},
|
{Case: "valid path", Expected: "«link»(file:/test/test)", Template: `{{ path "link" "/test/test" }}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
env := &mock.MockedEnvironment{}
|
env := &mock.MockedEnvironment{}
|
||||||
|
|
|
@ -137,8 +137,8 @@ Hyperlink formatting example
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
// General format: [Text](Url)
|
// General format: «text»(link)
|
||||||
"template": "[{{.StatusIcon}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d{{end}} {{.Recipe.Name}}]({{.URL}})"
|
"template": "«{{.StatusIcon}} {{if .DaysBottledOrFermented}}{{.DaysBottledOrFermented}}d{{end}} {{.Recipe.Name}}»({{.URL}})"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue