feat: add osc7 support

resolves #2515
This commit is contained in:
Jan De Dobbeleer 2022-07-13 13:53:55 +02:00 committed by Jan De Dobbeleer
parent 19d6d01242
commit 859fd0bbef
25 changed files with 90 additions and 36 deletions

View file

@ -9,6 +9,9 @@ import (
const ( const (
AnsiRegex = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" AnsiRegex = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
OSC99 string = "osc99"
OSC7 string = "osc7"
) )
type Ansi struct { type Ansi struct {
@ -29,7 +32,7 @@ type Ansi struct {
escapeRight string escapeRight string
hyperlink string hyperlink string
hyperlinkRegex string hyperlinkRegex string
osc99 string pwd string
bold string bold string
italic string italic string
underline string underline string
@ -62,7 +65,7 @@ func (a *Ansi) Init(shellName string) {
a.escapeRight = "%}" a.escapeRight = "%}"
a.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}" a.hyperlink = "%%{\x1b]8;;%s\x1b\\%%}%s%%{\x1b]8;;\x1b\\%%}"
a.hyperlinkRegex = `(?P<STR>%{\x1b]8;;(.+)\x1b\\%}(?P<TEXT>.+)%{\x1b]8;;\x1b\\%})` a.hyperlinkRegex = `(?P<STR>%{\x1b]8;;(.+)\x1b\\%}(?P<TEXT>.+)%{\x1b]8;;\x1b\\%})`
a.osc99 = "%%{\x1b]9;9;\"%s\"\x1b\\%%}" a.pwd = "%%{\x1b]%s;\"%s\"\x1b\\%%}"
a.bold = "%%{\x1b[1m%%}%s%%{\x1b[22m%%}" a.bold = "%%{\x1b[1m%%}%s%%{\x1b[22m%%}"
a.italic = "%%{\x1b[3m%%}%s%%{\x1b[23m%%}" a.italic = "%%{\x1b[3m%%}%s%%{\x1b[23m%%}"
a.underline = "%%{\x1b[4m%%}%s%%{\x1b[24m%%}" a.underline = "%%{\x1b[4m%%}%s%%{\x1b[24m%%}"
@ -89,7 +92,7 @@ func (a *Ansi) Init(shellName string) {
a.escapeRight = "\\]" a.escapeRight = "\\]"
a.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]" a.hyperlink = "\\[\x1b]8;;%s\x1b\\\\\\]%s\\[\x1b]8;;\x1b\\\\\\]"
a.hyperlinkRegex = `(?P<STR>\\\[\x1b\]8;;(.+)\x1b\\\\\\\](?P<TEXT>.+)\\\[\x1b\]8;;\x1b\\\\\\\])` a.hyperlinkRegex = `(?P<STR>\\\[\x1b\]8;;(.+)\x1b\\\\\\\](?P<TEXT>.+)\\\[\x1b\]8;;\x1b\\\\\\\])`
a.osc99 = "\\[\x1b]9;9;\"%s\"\x1b\\\\\\]" a.pwd = "\\[\x1b]%s;\"%s\"\x1b\\\\\\]"
a.bold = "\\[\x1b[1m\\]%s\\[\x1b[22m\\]" a.bold = "\\[\x1b[1m\\]%s\\[\x1b[22m\\]"
a.italic = "\\[\x1b[3m\\]%s\\[\x1b[23m\\]" a.italic = "\\[\x1b[3m\\]%s\\[\x1b[23m\\]"
a.underline = "\\[\x1b[4m\\]%s\\[\x1b[24m\\]" a.underline = "\\[\x1b[4m\\]%s\\[\x1b[24m\\]"
@ -116,7 +119,7 @@ func (a *Ansi) Init(shellName string) {
a.escapeRight = "" a.escapeRight = ""
a.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\" a.hyperlink = "\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\"
a.hyperlinkRegex = "(?P<STR>\x1b]8;;(.+)\x1b\\\\\\\\?(?P<TEXT>.+)\x1b]8;;\x1b\\\\)" a.hyperlinkRegex = "(?P<STR>\x1b]8;;(.+)\x1b\\\\\\\\?(?P<TEXT>.+)\x1b]8;;\x1b\\\\)"
a.osc99 = "\x1b]9;9;\"%s\"\x1b\\" a.pwd = "\x1b]%s;\"%s\"\x1b\\"
a.bold = "\x1b[1m%s\x1b[22m" a.bold = "\x1b[1m%s\x1b[22m"
a.italic = "\x1b[3m%s\x1b[23m" a.italic = "\x1b[3m%s\x1b[23m"
a.underline = "\x1b[4m%s\x1b[24m" a.underline = "\x1b[4m%s\x1b[24m"
@ -240,11 +243,18 @@ func (a *Ansi) ChangeLine(numberOfLines int) string {
return fmt.Sprintf(a.linechange, numberOfLines, position) return fmt.Sprintf(a.linechange, numberOfLines, position)
} }
func (a *Ansi) ConsolePwd(pwd string) string { func (a *Ansi) ConsolePwd(pwdType, pwd string) string {
if strings.HasSuffix(pwd, ":") { if strings.HasSuffix(pwd, ":") {
pwd += "\\" pwd += "\\"
} }
return fmt.Sprintf(a.osc99, pwd) switch pwdType {
case OSC7:
return fmt.Sprintf(a.pwd, "7", pwd)
case OSC99:
fallthrough
default:
return fmt.Sprintf(a.pwd, "9;9", pwd)
}
} }
func (a *Ansi) ClearAfter() string { func (a *Ansi) ClearAfter() string {

View file

@ -32,7 +32,6 @@ const (
type Config struct { type Config struct {
Version int `json:"version"` Version int `json:"version"`
FinalSpace bool `json:"final_space,omitempty"` FinalSpace bool `json:"final_space,omitempty"`
OSC99 bool `json:"osc99,omitempty"`
ConsoleTitleTemplate string `json:"console_title_template,omitempty"` ConsoleTitleTemplate string `json:"console_title_template,omitempty"`
TerminalBackground string `json:"terminal_background,omitempty"` TerminalBackground string `json:"terminal_background,omitempty"`
AccentColor string `json:"accent_color,omitempty"` AccentColor string `json:"accent_color,omitempty"`
@ -44,6 +43,10 @@ type Config struct {
SecondaryPrompt *Segment `json:"secondary_prompt,omitempty"` SecondaryPrompt *Segment `json:"secondary_prompt,omitempty"`
DebugPrompt *Segment `json:"debug_prompt,omitempty"` DebugPrompt *Segment `json:"debug_prompt,omitempty"`
Palette color.Palette `json:"palette,omitempty"` Palette color.Palette `json:"palette,omitempty"`
PWD string `json:"pwd,omitempty"`
// Deprecated
OSC99 bool `json:"osc99,omitempty"`
Output string `json:"-"` Output string `json:"-"`

View file

@ -74,12 +74,30 @@ func (e *Engine) PrintPrimary() string {
if e.Config.FinalSpace { if e.Config.FinalSpace {
e.write(" ") e.write(" ")
} }
if !e.Config.OSC99 { e.printPWD()
return e.print() return e.print()
}
func (e *Engine) printPWD() {
if len(e.Config.PWD) == 0 && !e.Config.OSC99 {
return
} }
cwd := e.Env.Pwd() cwd := e.Env.Pwd()
e.writeANSI(e.Ansi.ConsolePwd(cwd)) // Backwards compatibility for deprecated OSC99
return e.print() if e.Config.OSC99 {
e.writeANSI(e.Ansi.ConsolePwd(color.OSC99, cwd))
return
}
// Allow template logic to define when to enable the PWD (when supported)
tmpl := &template.Text{
Template: e.Config.PWD,
Env: e.Env,
}
pwdType, err := tmpl.Render()
if err != nil || len(pwdType) == 0 {
return
}
e.writeANSI(e.Ansi.ConsolePwd(pwdType, cwd))
} }
func (e *Engine) newline() { func (e *Engine) newline() {

View file

@ -44,6 +44,45 @@ func TestCanWriteRPrompt(t *testing.T) {
} }
} }
func TestPrintPWD(t *testing.T) {
cases := []struct {
Case string
Expected string
PWD string
OSC99 bool
}{
{Case: "Empty PWD"},
{Case: "OSC99", PWD: color.OSC99, Expected: "\x1b]9;9;\"pwd\"\x1b\\"},
{Case: "OSC7", PWD: color.OSC7, Expected: "\x1b]7;\"pwd\"\x1b\\"},
{Case: "Deprecated OSC99", OSC99: true, Expected: "\x1b]9;9;\"pwd\"\x1b\\"},
{Case: "Template (empty)", PWD: "{{ if eq .Shell \"pwsh\" }}osc7{{ end }}"},
{Case: "Template (non empty)", PWD: "{{ if eq .Shell \"shell\" }}osc7{{ end }}", Expected: "\x1b]7;\"pwd\"\x1b\\"},
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
env.On("Pwd").Return("pwd")
env.On("Shell").Return("shell")
env.On("TemplateCache").Return(&environment.TemplateCache{
Env: make(map[string]string),
Shell: "shell",
})
ansi := &color.Ansi{}
ansi.InitPlain()
engine := &Engine{
Env: env,
Config: &Config{
PWD: tc.PWD,
OSC99: tc.OSC99,
},
Ansi: ansi,
}
engine.printPWD()
got := engine.print()
assert.Equal(t, tc.Expected, got, tc.Case)
}
}
func BenchmarkEngineRender(b *testing.B) { func BenchmarkEngineRender(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
engineRender() engineRender()

View file

@ -69,6 +69,7 @@ const (
color16 = "color16" color16 = "color16"
left = "left" left = "left"
osc99 = "osc99" osc99 = "osc99"
osc7 = "osc7"
lineChange = "linechange" lineChange = "linechange"
consoleTitle = "title" consoleTitle = "title"
link = "link" link = "link"
@ -195,6 +196,7 @@ func (ir *ImageRenderer) Init(config string) {
color16: `^(?P<STR>\x1b\[(?P<BC>[349][0-7]|10[0-7]|39)m)`, color16: `^(?P<STR>\x1b\[(?P<BC>[349][0-7]|10[0-7]|39)m)`,
left: `^(?P<STR>\x1b\[(\d{1,3})D)`, left: `^(?P<STR>\x1b\[(\d{1,3})D)`,
osc99: `^(?P<STR>\x1b\]9;9;(.+)\x1b\\)`, osc99: `^(?P<STR>\x1b\]9;9;(.+)\x1b\\)`,
osc7: `^(?P<STR>\x1b\]7;(.+)\x1b\\)`,
lineChange: `^(?P<STR>\x1b\[(\d)[FB])`, lineChange: `^(?P<STR>\x1b\[(\d)[FB])`,
consoleTitle: `^(?P<STR>\x1b\]0;(.+)\007)`, consoleTitle: `^(?P<STR>\x1b\]0;(.+)\007)`,
link: fmt.Sprintf(`^%s`, regex.LINK), link: fmt.Sprintf(`^%s`, regex.LINK),
@ -502,7 +504,7 @@ func (ir *ImageRenderer) shouldPrint() bool {
case boldReset, italicReset, underlineReset, overlineReset: case boldReset, italicReset, underlineReset, overlineReset:
ir.style = "" ir.style = ""
return false return false
case strikethrough, strikethroughReset, left, osc99, lineChange, consoleTitle: case strikethrough, strikethroughReset, left, osc99, osc7, lineChange, consoleTitle:
return false return false
case color16: case color16:
ir.setBase16Color(match[bc]) ir.setBase16Color(match[bc])

View file

@ -116,7 +116,6 @@
} }
], ],
"console_title_template": "{{ .Folder }}", "console_title_template": "{{ .Folder }}",
"osc99": true,
"transient_prompt": { "transient_prompt": {
"background": "transparent", "background": "transparent",
"foreground": "#FEF5ED", "foreground": "#FEF5ED",

View file

@ -272,6 +272,5 @@
"type": "prompt" "type": "prompt"
} }
], ],
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -189,6 +189,5 @@
"type": "prompt" "type": "prompt"
} }
], ],
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -120,6 +120,5 @@
} }
], ],
"console_title_template": "{{if .Root}} \u26a1 {{end}}{{.Folder | replace \"~\" \"🏚\" }} @ {{.HostName}}", "console_title_template": "{{if .Root}} \u26a1 {{end}}{{.Folder | replace \"~\" \"🏚\" }} @ {{.HostName}}",
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -134,7 +134,6 @@
} }
], ],
"console_title_template": "{{ .Folder }}", "console_title_template": "{{ .Folder }}",
"osc99": true,
"transient_prompt": { "transient_prompt": {
"background": "transparent", "background": "transparent",
"foreground": "#FEF5ED", "foreground": "#FEF5ED",

View file

@ -218,7 +218,6 @@ blocks:
root_icon: root_icon:
template: "{{ if and .Root ( not .Segments.Path.Writable ) }} {{ end }}{{ if and .Root .Segments.Path.Writable }}  {{ end }} \b" template: "{{ if and .Root ( not .Segments.Path.Writable ) }} {{ end }}{{ if and .Root .Segments.Path.Writable }}  {{ end }} \b"
console_title_template: "{{ .Folder }}" console_title_template: "{{ .Folder }}"
osc99: true
palette: palette:
black: "#1B1A23" black: "#1B1A23"
blue: "#9580FF" blue: "#9580FF"

View file

@ -120,6 +120,5 @@
} }
], ],
"console_title_template": "{{if .Root}} \u26a1 {{end}}{{.Folder | replace \"~\" \"🏚\" }} @ {{.HostName}}", "console_title_template": "{{if .Root}} \u26a1 {{end}}{{.Folder | replace \"~\" \"🏚\" }} @ {{.HostName}}",
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -53,6 +53,5 @@
], ],
"console_title_template": "{{.Folder}}{{if .Root}} :: root{{end}} :: {{.Shell}}", "console_title_template": "{{.Folder}}{{if .Root}} :: root{{end}} :: {{.Shell}}",
"final_space": true, "final_space": true,
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -166,7 +166,6 @@
"console_title_style": "template", "console_title_style": "template",
"console_title_template": "{{ .Folder }}", "console_title_template": "{{ .Folder }}",
"osc99": true,
"transient_prompt": { "transient_prompt": {
"background": "transparent", "background": "transparent",
"foreground": "#FEF5ED", "foreground": "#FEF5ED",

View file

@ -96,6 +96,5 @@
], ],
"console_title_template": "{{if .Root}} \u26a1 {{end}}{{.Folder | replace \"~\" \"🏠\"}} @ {{.HostName}}", "console_title_template": "{{if .Root}} \u26a1 {{end}}{{.Folder | replace \"~\" \"🏠\"}} @ {{.HostName}}",
"final_space": true, "final_space": true,
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -115,6 +115,5 @@
], ],
"console_title_template": "{{if .Root}}\u26a1 {{end}}{{.Folder}}", "console_title_template": "{{if .Root}}\u26a1 {{end}}{{.Folder}}",
"final_space": true, "final_space": true,
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -291,7 +291,6 @@
} }
], ],
"console_title_template": "{{ .Folder }}", "console_title_template": "{{ .Folder }}",
"osc99": true,
"transient_prompt": { "transient_prompt": {
"background": "transparent", "background": "transparent",
"foreground": "#d6deeb", "foreground": "#d6deeb",

View file

@ -2601,11 +2601,11 @@
"description": "https://ohmyposh.dev/docs/configuration/overview#general-settings", "description": "https://ohmyposh.dev/docs/configuration/overview#general-settings",
"default": true "default": true
}, },
"osc99": { "pwd": {
"type": "boolean", "type": "string",
"title": "Enable OSC99", "title": "Enable OSC99/7",
"description": "https://ohmyposh.dev/docs/configuration/overview#general-settings", "description": "https://ohmyposh.dev/docs/configuration/overview#general-settings",
"default": false "default": ""
}, },
"console_title_template": { "console_title_template": {
"type": "string", "type": "string",

View file

@ -121,6 +121,5 @@
} }
], ],
"final_space": true, "final_space": true,
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -121,6 +121,5 @@
} }
], ],
"final_space": true, "final_space": true,
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -118,6 +118,5 @@
} }
], ],
"final_space": true, "final_space": true,
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -111,6 +111,5 @@
"type": "prompt" "type": "prompt"
} }
], ],
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -116,11 +116,10 @@
} }
], ],
"console_title_template": "{{ .Folder }}", "console_title_template": "{{ .Folder }}",
"osc99": true,
"transient_prompt": { "transient_prompt": {
"background": "transparent", "background": "transparent",
"foreground": "#FEF5ED", "foreground": "#FEF5ED",
"template": "\ue285 " "template": "\ue285 "
}, },
"version": 2 "version": 2
} }

View file

@ -166,6 +166,5 @@
], ],
"console_title_template": "{{ .Shell }} - {{ .Folder }}", "console_title_template": "{{ .Shell }} - {{ .Folder }}",
"final_space": true, "final_space": true,
"osc99": true,
"version": 2 "version": 2
} }

View file

@ -124,7 +124,7 @@ For example, the following is a valid `--config` flag:
## General Settings ## General Settings
- final_space: `boolean` - when true adds a space at the end of the prompt - final_space: `boolean` - when true adds a space at the end of the prompt
- osc99: `boolean` - when true adds support for OSC9;9; (notify terminal of current working directory) - pwd: `string` - notify terminal of current working directory, values can be `osc99` or `osc7` depending on your terminal
- terminal_background: `string` [color][colors] - terminal background color, set to your terminal's background color when - terminal_background: `string` [color][colors] - terminal background color, set to your terminal's background color when
you notice black elements in Windows Terminal or the Visual Studio Code integrated terminal you notice black elements in Windows Terminal or the Visual Studio Code integrated terminal
- accent_color: `string` [color][colors] - accent color, used as a fallback when the `accent` [color][accent] is not supported - accent_color: `string` [color][colors] - accent color, used as a fallback when the `accent` [color][accent] is not supported