From 7c72e76affecc380914c112f37e461d1e347b12f Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Tue, 15 Jun 2021 21:23:08 +0200 Subject: [PATCH] feat(pwsh): transient prompt --- docs/docs/beta.mdx | 70 ++++++++++++++++++++++++++++++++++-- docs/docs/configuration.md | 36 ++++++++++--------- docs/docs/segment-session.md | 2 ++ docs/docs/segment-time.md | 3 ++ src/ansi.go | 24 +++++++------ src/config.go | 13 +++++++ src/console_title.go | 19 +--------- src/engine.go | 20 +++++++++++ src/init/omp.ps1 | 19 ++++++++-- src/init/omp.zsh | 2 +- src/main.go | 61 +++++++++++++++++-------------- src/template.go | 23 ++++++++++++ themes/schema.json | 20 +++++++++++ 13 files changed, 235 insertions(+), 77 deletions(-) diff --git a/docs/docs/beta.mdx b/docs/docs/beta.mdx index 216cdab2..fba48363 100644 --- a/docs/docs/beta.mdx +++ b/docs/docs/beta.mdx @@ -9,15 +9,17 @@ import TabItem from "@theme/TabItem"; ## Tooltips +:::info +Due to limitations (or not having found a way just yet) this feature only work in `zsh` and `powershell` for +the time being. +::: + Tooltips are segments that are rendered as a right aligned prompt while you're typing certain keywords. They behave similar to the other segments when it comes to how and when they are shown so you can tweak them to act and look like you want. The key difference is that they can be invoked using `tips` which are the commands you are typing. Due to the possibility of the use of an alias, you can define for which keyword the segment should be rendered. -Due to limitations (or not having found a way just yet) this feature only work for `zsh` and `powershell` at -the time of writing. - ### Configuration You need to extend or create a custom theme with your tooltips. For example: @@ -87,3 +89,65 @@ Restart your shell or reload `.zshrc` using `source ~/.zshrc` for the changes to + +## Transient prompt + +:::info +This feature only works in `powershell` for the time being. +::: + +Transient prompt, when enabled, replaces the prompt with a simpler one to allow more screen real estate. +You can use go [text/template][go-text-template] templates extended with [sprig][sprig] to enrich the text. +Environment variables are available, just like the [`console_title_template`][console-title] functionality. + +### Configuration + +You need to extend or create a custom theme with your transient prompt. For example: + +```json +{ + "blocks": { + ... + } + "transient_prompt": { + "background": "transparent", + "foreground": "#ffffff", + "template": "{{ .Shell }}> <#f7dc66>{{ .Command }}" + } +} +``` + +The configuration has the following properties: + +- background: `string` [color][colors] +- foreground: `string` [color][colors] +- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the +properties below. Defaults to `{{ .Shell }}> <#f7dc66>{{ .Command }}` +- vertical_offset: `int` - Move the prompt up or down x lines. For example `vertical_offset: 1` moves the prompt down one line, `vertical_offset: -1` +moves it up one line. Useful when you have a multiline prompt. Defaults to `0` + +#### Template Properties + +- `.Command`: `string` - the shell command you typed +- `.Root`: `boolean` - is the current user root/admin or not +- `.Path`: `string` - the current working directory +- `.Folder`: `string` - the current working folder +- `.Shell`: `string` - the current shell name +- `.User`: `string` - the current user name +- `.Host`: `string` - the host name +- `.Env.VarName`: `string` - Any environment variable where `VarName` is the environment variable name + +### Enable the feature + +Import/invoke Oh My Posh in your `$PROFILE` and add the following line below: + +```pwsh +Enable-PoshTransientPrompt +``` + +Restart your shell or reload your `$PROFILE` using `. $PROFILE` for the changes to take effect. + +[go-text-template]: https://golang.org/pkg/text/template/ +[sprig]: https://masterminds.github.io/sprig/ +[console-title]: /docs/configure#console-title-template +[colors]: /docs/configure#colors diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md index 43b809c0..9521afed 100644 --- a/docs/docs/configuration.md +++ b/docs/docs/configuration.md @@ -319,7 +319,26 @@ Note for Windows users: Windows directory separators should be specified as 4 ba ] ``` -#### Colors +### Colors + +#### Standard colors + +Oh My Posh mainly supports three different color types being + +- Typical [hex colors][hexcolors] (for example `#CB4B16`). +- The `transparent` keyword which can be used to create either a transparent foreground override + or transparent background color using the segment's foreground property. +- 16 [ANSI color names][ansicolors]. + + These include 8 basic ANSI colors and `default`: + + `black` `red` `green` `yellow` `blue` `magenta` `cyan` `white` `default` + + as well as 8 extended ANSI colors: + + `darkGray` `lightRed` `lightGreen` `lightYellow` `lightBlue` `lightMagenta` `lightCyan` `lightWhite` + +#### Color overrides You have the ability to override the foreground and/or background color for text in any property that accepts it. The syntax is custom but should be rather straighforward: @@ -345,21 +364,6 @@ To change *only* the background color, just omit the first color from the above "prefix": "<,#FFFFFF>┏[", ``` -Oh My Posh mainly supports three different color types being - -- Typical [hex colors][hexcolors] (for example `#CB4B16`). -- The `transparent` keyword which can be used to create either a transparent foreground override - or transparent background color using the segment's foreground property. -- 16 [ANSI color names][ansicolors]. - - These include 8 basic ANSI colors and `default`: - - `black` `red` `green` `yellow` `blue` `magenta` `cyan` `white` `default` - - as well as 8 extended ANSI colors: - - `darkGray` `lightRed` `lightGreen` `lightYellow` `lightBlue` `lightMagenta` `lightCyan` `lightWhite` - ### Text decorations You can make use of the following syntax to decorate text: diff --git a/docs/docs/segment-session.md b/docs/docs/segment-session.md index 83991d8d..38c12c86 100644 --- a/docs/docs/segment-session.md +++ b/docs/docs/segment-session.md @@ -49,3 +49,5 @@ properties below. Only used when a value is set, making the above properties obs - `POSH_SESSION_DEFAULT_USER` - used to override the hardcoded `default_user_name` property [colors]: /docs/configure#colors +[go-text-template]: https://golang.org/pkg/text/template/ +[sprig]: https://masterminds.github.io/sprig/ diff --git a/docs/docs/segment-time.md b/docs/docs/segment-time.md index 82517f4a..5b5ed1aa 100644 --- a/docs/docs/segment-time.md +++ b/docs/docs/segment-time.md @@ -71,3 +71,6 @@ Show the current timestamp. - StampMilli = "Jan _2 15:04:05.000" - StampMicro = "Jan _2 15:04:05.000000" - StampNano = "Jan _2 15:04:05.000000000" + +[go-text-template]: https://golang.org/pkg/text/template/ +[sprig]: https://masterminds.github.io/sprig/ diff --git a/src/ansi.go b/src/ansi.go index 05263cc1..904c50b8 100644 --- a/src/ansi.go +++ b/src/ansi.go @@ -39,10 +39,10 @@ func (a *ansiUtils) init(shell string) { switch shell { case zsh: a.linechange = "%%{\x1b[%d%s%%}" - a.left = "%%{\x1b[%dC%%}" - a.right = "%%{\x1b[%dD%%}" + a.right = "%%{\x1b[%dC%%}" + a.left = "%%{\x1b[%dD%%}" a.creset = "%{\x1b[0m%}" - a.clearEOL = "%{\x1b[K%}" + a.clearEOL = "%{\x1b[0J%}" a.saveCursorPosition = "%{\x1b7%}" a.restoreCursorPosition = "%{\x1b8%}" a.title = "%%{\x1b]0;%s\007%%}" @@ -59,10 +59,10 @@ func (a *ansiUtils) init(shell string) { a.strikethrough = "%%{\x1b[9m%%}%s%%{\x1b[29m%%}" case bash: a.linechange = "\\[\x1b[%d%s\\]" - a.left = "\\[\x1b[%dC\\]" - a.right = "\\[\x1b[%dD\\]" + a.right = "\\[\x1b[%dC\\]" + a.left = "\\[\x1b[%dD\\]" a.creset = "\\[\x1b[0m\\]" - a.clearEOL = "\\[\x1b[K\\]" + a.clearEOL = "\\[\x1b[0J\\]" a.saveCursorPosition = "\\[\x1b7\\]" a.restoreCursorPosition = "\\[\x1b8\\]" a.title = "\\[\x1b]0;%s\007\\]" @@ -79,10 +79,10 @@ func (a *ansiUtils) init(shell string) { a.strikethrough = "\\[\x1b[9m\\]%s\\[\x1b[29m\\]" default: a.linechange = "\x1b[%d%s" - a.left = "\x1b[%dC" - a.right = "\x1b[%dD" + a.right = "\x1b[%dC" + a.left = "\x1b[%dD" a.creset = "\x1b[0m" - a.clearEOL = "\x1b[K" + a.clearEOL = "\x1b[0J" a.saveCursorPosition = "\x1b7" a.restoreCursorPosition = "\x1b8" a.title = "\x1b]0;%s\007" @@ -153,12 +153,16 @@ func (a *ansiUtils) formatText(text string) string { } func (a *ansiUtils) carriageForward() string { + return fmt.Sprintf(a.right, 1000) +} + +func (a *ansiUtils) carriageBackward() string { return fmt.Sprintf(a.left, 1000) } func (a *ansiUtils) getCursorForRightWrite(text string, offset int) string { strippedLen := a.lenWithoutANSI(text) + -offset - return fmt.Sprintf(a.right, strippedLen) + return fmt.Sprintf(a.left, strippedLen) } func (a *ansiUtils) changeLine(numberOfLines int) string { diff --git a/src/config.go b/src/config.go index 212f9a3c..c8b1e8d0 100644 --- a/src/config.go +++ b/src/config.go @@ -28,6 +28,14 @@ type Config struct { TerminalBackground string `config:"terminal_background"` Blocks []*Block `config:"blocks"` Tooltips []*Segment `config:"tooltips"` + TransientPrompt *TransientPrompt `config:"transient_prompt"` +} + +type TransientPrompt struct { + Template string `config:"template"` + Background string `config:"background"` + Foreground string `config:"foreground"` + VerticalOffset int `config:"vertical_offset"` } const ( @@ -80,6 +88,11 @@ func loadConfig(env environmentInfo) (*Config, error) { return nil, errors.New("INVALID CONFIG") } + // initialize default values + if cfg.TransientPrompt == nil { + cfg.TransientPrompt = &TransientPrompt{} + } + return &cfg, nil } diff --git a/src/console_title.go b/src/console_title.go index e3719ea5..537e00fd 100644 --- a/src/console_title.go +++ b/src/console_title.go @@ -40,28 +40,11 @@ func (t *consoleTitle) getConsoleTitle() string { } func (t *consoleTitle) getTemplateText() string { - context := make(map[string]interface{}) - - context["Root"] = t.env.isRunningAsRoot() - context["Path"] = t.getPwd() - context["Folder"] = base(t.getPwd(), t.env) - context["Shell"] = t.env.getShellName() - context["User"] = t.env.getCurrentUser() - context["Host"] = "" - if host, err := t.env.getHostName(); err == nil { - context["Host"] = host - } - template := &textTemplate{ Template: t.config.ConsoleTitleTemplate, - Context: context, Env: t.env, } - text, err := template.render() - if err != nil { - return err.Error() - } - return text + return template.renderPlainContextTemplate(nil) } func (t *consoleTitle) getPwd() string { diff --git a/src/engine.go b/src/engine.go index dbff67aa..cb118b18 100644 --- a/src/engine.go +++ b/src/engine.go @@ -217,3 +217,23 @@ func (e *engine) renderTooltip(tip string) string { } return "" } + +func (e *engine) renderTransientPrompt(command string) string { + promptTemplate := e.config.TransientPrompt.Template + if len(promptTemplate) == 0 { + promptTemplate = "{{ .Shell }}> <#f7dc66>{{ .Command }}" + } + template := &textTemplate{ + Template: promptTemplate, + Env: e.env, + } + context := make(map[string]interface{}) + context["Command"] = command + prompt := template.renderPlainContextTemplate(context) + e.colorWriter.write(e.config.TransientPrompt.Background, e.config.TransientPrompt.Foreground, prompt) + transientPrompt := e.ansi.carriageBackward() + if e.config.TransientPrompt.VerticalOffset != 0 { + transientPrompt += e.ansi.changeLine(e.config.TransientPrompt.VerticalOffset) + } + return transientPrompt + e.colorWriter.string() + e.ansi.clearEOL +} diff --git a/src/init/omp.ps1 b/src/init/omp.ps1 index 9bae3466..fb44a40b 100644 --- a/src/init/omp.ps1 +++ b/src/init/omp.ps1 @@ -192,11 +192,24 @@ function global:Enable-PoshTooltips { $position = $host.UI.RawUI.CursorPosition $omp = "::OMP::" $config, $cleanPWD, $cleanPSWD = Get-PoshContext - $tooltip = $null + $command = $null $cursor = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$tooltip, [ref]$cursor) - $standardOut = @(&$omp --pwd="$cleanPWD" --pswd="$cleanPSWD" --config="$config" --tooltip="$tooltip" 2>&1) + [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$command, [ref]$cursor) + $standardOut = @(&$omp --pwd="$cleanPWD" --pswd="$cleanPSWD" --config="$config" --command="$command" 2>&1) Write-Host $standardOut -NoNewline $host.UI.RawUI.CursorPosition = $position } } + +function global:Enable-PoshTransientPrompt { + Set-PSReadlineKeyHandler -Key Enter -ScriptBlock { + $command = $null + $cursor = $null + [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$command, [ref]$cursor) + $omp = "::OMP::" + $config, $cleanPWD, $cleanPSWD = Get-PoshContext + $standardOut = @(&$omp --pwd="$cleanPWD" --pswd="$cleanPSWD" --config="$config" --command="$command" --print-transient 2>&1) + Write-Host $standardOut -NoNewline + [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine() + } +} diff --git a/src/init/omp.zsh b/src/init/omp.zsh index 4101083c..90cfb7e9 100644 --- a/src/init/omp.zsh +++ b/src/init/omp.zsh @@ -57,7 +57,7 @@ function self-insert() { zle .self-insert return fi - tooltip=$(::OMP:: --config $POSH_THEME --shell zsh --tooltip $BUFFER) + tooltip=$(::OMP:: --config $POSH_THEME --shell zsh --command $BUFFER) # ignore an empty tooltip if [[ ! -z "$tooltip" ]]; then RPROMPT=$tooltip diff --git a/src/main.go b/src/main.go index b406df36..8975ffcf 100644 --- a/src/main.go +++ b/src/main.go @@ -37,27 +37,28 @@ const ( ) type args struct { - ErrorCode *int - PrintConfig *bool - ConfigFormat *string - PrintShell *bool - Config *string - Shell *string - PWD *string - PSWD *string - Version *bool - Debug *bool - ExecutionTime *float64 - Millis *bool - Eval *bool - Init *bool - PrintInit *bool - ExportPNG *bool - Author *string - CursorPadding *int - RPromptOffset *int - StackCount *int - ToolTip *string + ErrorCode *int + PrintConfig *bool + ConfigFormat *string + PrintShell *bool + Config *string + Shell *string + PWD *string + PSWD *string + Version *bool + Debug *bool + ExecutionTime *float64 + Millis *bool + Eval *bool + Init *bool + PrintInit *bool + ExportPNG *bool + Author *string + CursorPadding *int + RPromptOffset *int + StackCount *int + Command *string + PrintTransient *bool } func main() { @@ -142,10 +143,14 @@ func main() { "stack-count", 0, "The current location stack count"), - ToolTip: flag.String( - "tooltip", + Command: flag.String( + "command", "", - "Render a tooltip based on the string value"), + "Render a tooltip based on the command value"), + PrintTransient: flag.Bool( + "print-transient", + false, + "Print the transient prompt"), } flag.Parse() env := &environment{} @@ -200,8 +205,12 @@ func main() { fmt.Print(engine.debug()) return } - if len(*args.ToolTip) != 0 { - fmt.Print(engine.renderTooltip(*args.ToolTip)) + if *args.PrintTransient { + fmt.Print(engine.renderTransientPrompt(*args.Command)) + return + } + if len(*args.Command) != 0 { + fmt.Print(engine.renderTooltip(*args.Command)) return } prompt := engine.render() diff --git a/src/template.go b/src/template.go index 4925c0b7..cb49ffb6 100644 --- a/src/template.go +++ b/src/template.go @@ -24,6 +24,29 @@ type textTemplate struct { Env environmentInfo } +func (t *textTemplate) renderPlainContextTemplate(context map[string]interface{}) string { + if context == nil { + context = make(map[string]interface{}) + } + context["Root"] = t.Env.isRunningAsRoot() + pwd := t.Env.getcwd() + pwd = strings.Replace(pwd, t.Env.homeDir(), "~", 1) + context["Path"] = pwd + context["Folder"] = base(pwd, t.Env) + context["Shell"] = t.Env.getShellName() + context["User"] = t.Env.getCurrentUser() + context["Host"] = "" + if host, err := t.Env.getHostName(); err == nil { + context["Host"] = host + } + t.Context = context + text, err := t.render() + if err != nil { + return err.Error() + } + return text +} + func (t *textTemplate) render() (string, error) { tmpl, err := template.New("title").Funcs(sprig.TxtFuncMap()).Parse(t.Template) if err != nil { diff --git a/themes/schema.json b/themes/schema.json index 19973923..f09449db 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -1589,6 +1589,26 @@ }, "required": ["tips"] } + }, + "transient_prompt": { + "type": "object", + "title": "Transient Prompt Settings", + "description": "https://ohmyposh.dev/docs/beta#transient-prompt", + "default": {}, + "properties": { + "template": { + "type": "string", + "title": "Transient Prompt Template", + "default": "{{ .Shell }}> <#f7dc66>{{ .Command }}" + }, + "vertical_offset": { + "type": "integer", + "title": "Transient Prompt vertical offset", + "default": 0 + }, + "background": { "$ref": "#/definitions/color" }, + "foreground": { "$ref": "#/definitions/color" } + } } } }