feat(path): enable templates for max_width

resolves #5097
This commit is contained in:
Jan De Dobbeleer 2024-06-17 14:17:43 +02:00 committed by Jan De Dobbeleer
parent d5f0d94918
commit 33ac83551e
5 changed files with 114 additions and 20 deletions

View file

@ -3,6 +3,7 @@ package segments
import ( import (
"fmt" "fmt"
"sort" "sort"
"strconv"
"strings" "strings"
"github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/platform"
@ -190,13 +191,40 @@ func (pt *Path) setStyle() {
case FolderType: case FolderType:
pt.Path = pt.getFolderPath() pt.Path = pt.getFolderPath()
case Powerlevel: case Powerlevel:
maxWidth := int(pt.props.GetFloat64(MaxWidth, 0)) maxWidth := pt.getMaxWidth()
pt.Path = pt.getUniqueLettersPath(maxWidth) pt.Path = pt.getUniqueLettersPath(maxWidth)
default: default:
pt.Path = fmt.Sprintf("Path style: %s is not available", style) pt.Path = fmt.Sprintf("Path style: %s is not available", style)
} }
} }
func (pt *Path) getMaxWidth() int {
width := pt.props.GetString(MaxWidth, "")
if len(width) == 0 {
return 0
}
tmpl := &template.Text{
Template: width,
Context: pt,
Env: pt.env,
}
text, err := tmpl.Render()
if err != nil {
pt.env.Error(err)
return 0
}
value, err := strconv.Atoi(text)
if err != nil {
pt.env.Error(err)
return 0
}
return value
}
func (pt *Path) getFolderSeparator() string { func (pt *Path) getFolderSeparator() string {
separatorTemplate := pt.props.GetString(FolderSeparatorTemplate, "") separatorTemplate := pt.props.GetString(FolderSeparatorTemplate, "")
if len(separatorTemplate) == 0 { if len(separatorTemplate) == 0 {

View file

@ -744,6 +744,9 @@ func TestAgnosterPathStyles(t *testing.T) {
} }
env.On("Flags").Return(args) env.On("Flags").Return(args)
env.On("Shell").Return(shell.PWSH) env.On("Shell").Return(shell.PWSH)
env.On("DebugF", mock2.Anything, mock2.Anything).Return(nil)
path := &Path{ path := &Path{
env: env, env: env,
props: properties.Map{ props: properties.Map{
@ -754,6 +757,7 @@ func TestAgnosterPathStyles(t *testing.T) {
HideRootLocation: tc.HideRootLocation, HideRootLocation: tc.HideRootLocation,
}, },
} }
path.setPaths() path.setPaths()
path.setStyle() path.setStyle()
got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path) got := renderTemplateNoTrimSpace(env, "{{ .Path }}", path)
@ -1570,3 +1574,53 @@ func TestSplitPath(t *testing.T) {
assert.Equal(t, tc.Expected, got, tc.Case) assert.Equal(t, tc.Expected, got, tc.Case)
} }
} }
func TestGetMaxWidth(t *testing.T) {
cases := []struct {
Case string
MaxWidth any
Expected int
}{
{
Case: "Nil",
Expected: 0,
},
{
Case: "Empty string",
MaxWidth: "",
Expected: 0,
},
{
Case: "Invalid template",
MaxWidth: "{{ .Unknown }}",
Expected: 0,
},
{
Case: "Environment variable",
MaxWidth: "{{ .Env.MAX_WIDTH }}",
Expected: 120,
},
}
for _, tc := range cases {
env := new(mock.MockedEnvironment)
env.On("DebugF", mock2.Anything, mock2.Anything).Return(nil)
env.On("Error", mock2.Anything).Return(nil)
env.On("TemplateCache").Return(&platform.TemplateCache{
Env: map[string]string{
"MAX_WIDTH": "120",
},
Shell: "bash",
})
path := &Path{
env: env,
props: properties.Map{
MaxWidth: tc.MaxWidth,
},
}
got := path.getMaxWidth()
assert.Equal(t, tc.Expected, got, tc.Case)
}
}

View file

@ -70,19 +70,25 @@ func (c *Context) init(t *Text) {
func (t *Text) Render() (string, error) { func (t *Text) Render() (string, error) {
t.Env.DebugF("Rendering template: %s", t.Template) t.Env.DebugF("Rendering template: %s", t.Template)
if !strings.Contains(t.Template, "{{") || !strings.Contains(t.Template, "}}") { if !strings.Contains(t.Template, "{{") || !strings.Contains(t.Template, "}}") {
return t.Template, nil return t.Template, nil
} }
t.cleanTemplate() t.cleanTemplate()
tmpl, err := template.New(t.Template).Funcs(funcMap()).Parse(t.Template) tmpl, err := template.New(t.Template).Funcs(funcMap()).Parse(t.Template)
if err != nil { if err != nil {
t.Env.Error(err) t.Env.Error(err)
return "", errors.New(InvalidTemplate) return "", errors.New(InvalidTemplate)
} }
context := &Context{} context := &Context{}
context.init(t) context.init(t)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
defer buffer.Reset() defer buffer.Reset()
err = tmpl.Execute(buffer, context) err = tmpl.Execute(buffer, context)
if err != nil { if err != nil {
t.Env.Error(err) t.Env.Error(err)
@ -92,10 +98,12 @@ func (t *Text) Render() (string, error) {
} }
return "", errors.New(msg["MSG"]) return "", errors.New(msg["MSG"])
} }
text := buffer.String() text := buffer.String()
// issue with missingkey=zero ignored for map[string]any // issue with missingkey=zero ignored for map[string]any
// https://github.com/golang/go/issues/24963 // https://github.com/golang/go/issues/24963
text = strings.ReplaceAll(text, "<no value>", "") text = strings.ReplaceAll(text, "<no value>", "")
return text, nil return text, nil
} }
@ -103,19 +111,23 @@ func (t *Text) cleanTemplate() {
isKnownVariable := func(variable string) bool { isKnownVariable := func(variable string) bool {
variable = strings.TrimPrefix(variable, ".") variable = strings.TrimPrefix(variable, ".")
splitted := strings.Split(variable, ".") splitted := strings.Split(variable, ".")
if len(splitted) == 0 { if len(splitted) == 0 {
return true return true
} }
variable = splitted[0] variable = splitted[0]
// check if alphanumeric // check if alphanumeric
if !regex.MatchString(`^[a-zA-Z0-9]+$`, variable) { if !regex.MatchString(`^[a-zA-Z0-9]+$`, variable) {
return true return true
} }
for _, b := range knownVariables { for _, b := range knownVariables {
if variable == b { if variable == b {
return true return true
} }
} }
return false return false
} }

View file

@ -2479,7 +2479,7 @@
"default": 1 "default": 1
}, },
"max_width": { "max_width": {
"type": "integer", "type": ["integer","string"],
"title": "Maximum Width", "title": "Maximum Width",
"description": "Maximum path width to display for powerlevel style", "description": "Maximum path width to display for powerlevel style",
"default": 0 "default": 0

View file

@ -40,7 +40,7 @@ import Config from "@site/src/components/Config.js";
| `style` | `enum` | `agnoster` | how to display the current path | | `style` | `enum` | `agnoster` | how to display the current path |
| `mixed_threshold` | `number` | `4` | the maximum length of a path segment that will be displayed when using `Mixed` | | `mixed_threshold` | `number` | `4` | the maximum length of a path segment that will be displayed when using `Mixed` |
| `max_depth` | `number` | `1` | maximum path depth to display before shortening when using `agnoster_short` | | `max_depth` | `number` | `1` | maximum path depth to display before shortening when using `agnoster_short` |
| `max_width` | `number` | `0` | maximum path length to display when using `powerlevel` | | `max_width` | `any` | `0` | maximum path length to display when using `powerlevel`, can leverage [templates] |
| `hide_root_location` | `boolean` | `false` | ides the root location if it doesn't fit in the last `max_depth` folders, when using `agnoster_short` | | `hide_root_location` | `boolean` | `false` | ides the root location if it doesn't fit in the last `max_depth` folders, when using `agnoster_short` |
| `cycle` | `[]string` | | a list of color overrides to cycle through to colorize the individual path folders, e.g. `[ "#ffffff,#111111" ]` | | `cycle` | `[]string` | | a list of color overrides to cycle through to colorize the individual path folders, e.g. `[ "#ffffff,#111111" ]` |
| `cycle_folder_separator` | `boolean` | `false` | colorize the `folder_separator_icon` as well when using a cycle | | `cycle_folder_separator` | `boolean` | `false` | colorize the `folder_separator_icon` as well when using a cycle |