mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2026-08-24 10:14:12 -05:00
fix(config): widen heuristic scan to all config texts, stabilize fingerprints
Two review findings on the template-derived fetch fallback: The heuristic scanned only the unanalyzable segment's own templates and options, but unanalyzability can be caused by a text OUTSIDE the segment - another segment laundering a cross-reference through a variable, or a global template. In that shape the field name that defeated the analysis was never scanned, the probe stayed off, and with the fetch options gone there was no user-side fix. ResolveFieldSets now assembles the whole-config text corpus (every segment's templates and templated options plus the global nil-context strings) once and stamps it on each unanalyzable segment; the fallback scan covers it all. The stamp gained a field, so fieldSetAnalysisVersion bumps. templatedOptionValues iterated option maps in Go's randomized order, and the collected sources feed the unanalyzable cache-key fingerprint: any segment with two or more templated option values got a fresh key per process, so its snapshots never hit and stale entries piled up. Collection now sorts map keys recursively, making the delivered order - and the fingerprint - stable across parses. Also strips the removed fetch_* keys from the src/test fixtures and updates the segment-docs skill's worked example off the deleted FetchStatus const. Acknowledged default changes now stated in the git segment docs' migration note: the default config's statusline derives the upstream icon its old explicit fetch_upstream_icon: false suppressed; the svn, mercurial and jujutsu default templates fetch status by default; and jujutsu's default rendering requires the jj binary. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M4RA9ZrP7vuqVSR7nddNiQ
This commit is contained in:
@@ -33,7 +33,7 @@ Options are declared as `options.Option` string constants in the segment's `cons
|
|||||||
```go
|
```go
|
||||||
const (
|
const (
|
||||||
BranchIcon options.Option = "branch_icon" // option name used in config
|
BranchIcon options.Option = "branch_icon" // option name used in config
|
||||||
FetchStatus options.Option = "fetch_status"
|
NativeStatus options.Option = "native_status"
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+91
-23
@@ -1,6 +1,8 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -20,10 +22,12 @@ type FieldSetConsumer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// refSet assembles the delivery for FieldSetConsumer writers from the
|
// refSet assembles the delivery for FieldSetConsumer writers from the
|
||||||
// stamped analysis. The heuristic sources are only materialized for an
|
// stamped analysis. The heuristic sources only exist for an unanalyzable
|
||||||
// unanalyzable set - they are recomputed from the segment's own (persisted)
|
// set: the whole-config corpus ResolveFieldSets stamped (unanalyzability can
|
||||||
// config fields rather than stamped, so a session-cache round trip cannot
|
// originate outside the segment - a cross-segment reference laundered
|
||||||
// desynchronize them from the config content.
|
// through a variable, a global template - so the scan must cover every text
|
||||||
|
// that can reference this segment, not just its own), or the segment's own
|
||||||
|
// texts when the config was never analyzed (library callers).
|
||||||
func (segment *Segment) refSet() template.RefSet {
|
func (segment *Segment) refSet() template.RefSet {
|
||||||
refs := template.RefSet{
|
refs := template.RefSet{
|
||||||
Fields: segment.ReferencedFields,
|
Fields: segment.ReferencedFields,
|
||||||
@@ -31,16 +35,27 @@ func (segment *Segment) refSet() template.RefSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !segment.FieldsAnalyzable {
|
if !segment.FieldsAnalyzable {
|
||||||
refs.Sources = segment.heuristicSources()
|
refs.Sources = segment.fallbackSources()
|
||||||
}
|
}
|
||||||
|
|
||||||
return refs
|
return refs
|
||||||
}
|
}
|
||||||
|
|
||||||
// heuristicSources returns every raw text the fallback heuristic may scan:
|
// fallbackSources returns the raw texts the substring heuristic scans for an
|
||||||
// the template sources (with the writer default substituted for an empty
|
// unanalyzable segment: the stamped whole-config corpus when the config went
|
||||||
// template) plus any templated option values.
|
// through ResolveFieldSets, the segment's own texts otherwise.
|
||||||
func (segment *Segment) heuristicSources() []string {
|
func (segment *Segment) fallbackSources() []string {
|
||||||
|
if segment.HeuristicSources != nil {
|
||||||
|
return segment.HeuristicSources
|
||||||
|
}
|
||||||
|
|
||||||
|
return segment.ownSources()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ownSources returns every raw text of this segment itself: the template
|
||||||
|
// sources (with the writer default substituted for an empty template) plus
|
||||||
|
// any templated option values.
|
||||||
|
func (segment *Segment) ownSources() []string {
|
||||||
sources := segment.templateSources()
|
sources := segment.templateSources()
|
||||||
sources[0] = segment.analysisTemplate()
|
sources[0] = segment.analysisTemplate()
|
||||||
|
|
||||||
@@ -57,7 +72,7 @@ var analyzeFields = template.AnalyzeFields
|
|||||||
// stamp shape - so a config stamped by another binary generation is treated
|
// stamp shape - so a config stamped by another binary generation is treated
|
||||||
// as unstamped (see Get) and a long-lived session (tmux) re-analyzes once
|
// as unstamped (see Get) and a long-lived session (tmux) re-analyzes once
|
||||||
// after a binary upgrade instead of trusting stale stamps.
|
// after a binary upgrade instead of trusting stale stamps.
|
||||||
const fieldSetAnalysisVersion = 2
|
const fieldSetAnalysisVersion = 3
|
||||||
|
|
||||||
// ResolveFieldSets analyzes every template in the config and stamps each
|
// ResolveFieldSets analyzes every template in the config and stamps each
|
||||||
// renderable segment with the set of top-level context fields those templates
|
// renderable segment with the set of top-level context fields those templates
|
||||||
@@ -90,8 +105,19 @@ func (cfg *Config) ResolveFieldSets() {
|
|||||||
analysis.analyzeSegment(segment)
|
analysis.analyzeSegment(segment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The corpus for the unanalyzable fallback: every text in the config that
|
||||||
|
// can reference a segment. Unanalyzability can be caused by a text
|
||||||
|
// outside the segment (another segment laundering a cross-reference, a
|
||||||
|
// global template), so scanning only the segment's own texts would miss
|
||||||
|
// exactly the reference that defeated the analysis. Assembled once and
|
||||||
|
// shared by every unanalyzable segment.
|
||||||
|
corpus := cfg.globalTemplateSources()
|
||||||
for _, segment := range segments {
|
for _, segment := range segments {
|
||||||
analysis.stamp(segment)
|
corpus = append(corpus, segment.ownSources()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, segment := range segments {
|
||||||
|
analysis.stamp(segment, corpus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,11 +199,22 @@ func (segment *Segment) templateSources() []string {
|
|||||||
// templatedOptionValues collects the segment's option values (nested ones
|
// templatedOptionValues collects the segment's option values (nested ones
|
||||||
// included) that carry template syntax. These render through
|
// included) that carry template syntax. These render through
|
||||||
// options.Map.Template against contexts this analysis cannot model per
|
// options.Map.Template against contexts this analysis cannot model per
|
||||||
// option, so analyzeSegment treats any hit conservatively.
|
// option, so analyzeSegment treats any hit conservatively. Maps iterate
|
||||||
|
// with their keys sorted, recursively: the collected order feeds the
|
||||||
|
// segment cache key fingerprint, which must be identical across processes -
|
||||||
|
// Go's randomized map order would otherwise produce a fresh key per prompt
|
||||||
|
// and the cache would never hit.
|
||||||
func (segment *Segment) templatedOptionValues() []string {
|
func (segment *Segment) templatedOptionValues() []string {
|
||||||
var sources []string
|
var sources []string
|
||||||
|
|
||||||
var collect func(value any)
|
var collect func(value any)
|
||||||
|
|
||||||
|
collectMap := func(m map[string]any) {
|
||||||
|
for _, key := range slices.Sorted(maps.Keys(m)) {
|
||||||
|
collect(m[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
collect = func(value any) {
|
collect = func(value any) {
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -185,16 +222,33 @@ func (segment *Segment) templatedOptionValues() []string {
|
|||||||
sources = append(sources, v)
|
sources = append(sources, v)
|
||||||
}
|
}
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
for _, nested := range v {
|
collectMap(v)
|
||||||
collect(nested)
|
|
||||||
}
|
|
||||||
case map[any]any:
|
case map[any]any:
|
||||||
for _, nested := range v {
|
type pair struct {
|
||||||
collect(nested)
|
value any
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := make([]pair, 0, len(v))
|
||||||
|
for key, nested := range v {
|
||||||
|
pairs = append(pairs, pair{key: fmt.Sprint(key), value: nested})
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(pairs, func(a, b pair) int { return strings.Compare(a.key, b.key) })
|
||||||
|
|
||||||
|
for _, entry := range pairs {
|
||||||
|
collect(entry.value)
|
||||||
}
|
}
|
||||||
case options.Map:
|
case options.Map:
|
||||||
for _, nested := range v {
|
keys := make([]string, 0, len(v))
|
||||||
collect(nested)
|
for key := range v {
|
||||||
|
keys = append(keys, string(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(keys)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
collect(v[options.Option(key)])
|
||||||
}
|
}
|
||||||
case []any:
|
case []any:
|
||||||
for _, nested := range v {
|
for _, nested := range v {
|
||||||
@@ -207,8 +261,15 @@ func (segment *Segment) templatedOptionValues() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, value := range segment.Options {
|
keys := make([]string, 0, len(segment.Options))
|
||||||
collect(value)
|
for key := range segment.Options {
|
||||||
|
keys = append(keys, string(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.Sort(keys)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
collect(segment.Options[options.Option(key)])
|
||||||
}
|
}
|
||||||
|
|
||||||
return sources
|
return sources
|
||||||
@@ -321,8 +382,10 @@ func (a *fieldAnalysis) mergeCross(refs *template.Refs) {
|
|||||||
// stamp fixes the analysis outcome onto the segment: the sorted union of its
|
// stamp fixes the analysis outcome onto the segment: the sorted union of its
|
||||||
// own references and the cross-references to its data key, plus whether that
|
// own references and the cross-references to its data key, plus whether that
|
||||||
// union is trustworthy. Cross-references resolve by Name(), the key
|
// union is trustworthy. Cross-references resolve by Name(), the key
|
||||||
// AddSegmentData stores segment data under.
|
// AddSegmentData stores segment data under. corpus is the whole-config text
|
||||||
func (a *fieldAnalysis) stamp(segment *Segment) {
|
// collection an unanalyzable segment's fallback heuristic scans; nil stays
|
||||||
|
// stamped for analyzable segments, whose exact field set needs no fallback.
|
||||||
|
func (a *fieldAnalysis) stamp(segment *Segment, corpus []string) {
|
||||||
name := segment.Name()
|
name := segment.Name()
|
||||||
|
|
||||||
set := a.own[segment]
|
set := a.own[segment]
|
||||||
@@ -339,4 +402,9 @@ func (a *fieldAnalysis) stamp(segment *Segment) {
|
|||||||
|
|
||||||
segment.ReferencedFields = fields
|
segment.ReferencedFields = fields
|
||||||
segment.FieldsAnalyzable = !a.opaque && !a.ownOpaque[segment] && !a.crossOpaque[name]
|
segment.FieldsAnalyzable = !a.opaque && !a.ownOpaque[segment] && !a.crossOpaque[name]
|
||||||
|
segment.HeuristicSources = nil
|
||||||
|
|
||||||
|
if !segment.FieldsAnalyzable {
|
||||||
|
segment.HeuristicSources = corpus
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,8 +252,10 @@ func TestGitFetchDerivedFromTemplates(t *testing.T) {
|
|||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Options options.Map
|
Options options.Map
|
||||||
|
Extra *Segment
|
||||||
Case string
|
Case string
|
||||||
Template string
|
Template string
|
||||||
|
ConsoleTitle string
|
||||||
ExpectStatus bool
|
ExpectStatus bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@@ -282,6 +284,36 @@ func TestGitFetchDerivedFromTemplates(t *testing.T) {
|
|||||||
Options: options.Map{"custom": "{{ .Env.POSH_UNUSED }}"},
|
Options: options.Map{"custom": "{{ .Env.POSH_UNUSED }}"},
|
||||||
ExpectStatus: true,
|
ExpectStatus: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// the reviewer scenario: another segment launders the git
|
||||||
|
// reference through a variable, defeating exact analysis for
|
||||||
|
// git - the heuristic must scan that OTHER segment's text too,
|
||||||
|
// or the laundered .Working reference renders permanent zeros
|
||||||
|
Case: "cross-segment laundering triggers the status probe",
|
||||||
|
Template: "{{ .HEAD }}",
|
||||||
|
Extra: &Segment{
|
||||||
|
Type: TEXT,
|
||||||
|
Template: "{{ $g := .Segments.Git }}{{ $g.Working.String }}",
|
||||||
|
},
|
||||||
|
ExpectStatus: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// same shape from a global nil-context text
|
||||||
|
Case: "console title laundering triggers the status probe",
|
||||||
|
Template: "{{ .HEAD }}",
|
||||||
|
ConsoleTitle: "{{ $g := .Segments.Git }}{{ $g.Working.String }}",
|
||||||
|
ExpectStatus: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// laundering elsewhere must not force units nobody names
|
||||||
|
Case: "cross-segment laundering without status mentions skips the probe",
|
||||||
|
Template: "{{ .HEAD }}",
|
||||||
|
Extra: &Segment{
|
||||||
|
Type: TEXT,
|
||||||
|
Template: "{{ $g := .Segments.Git }}{{ $g.RepoName }}",
|
||||||
|
},
|
||||||
|
ExpectStatus: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@@ -303,10 +335,16 @@ func TestGitFetchDerivedFromTemplates(t *testing.T) {
|
|||||||
env.On("HasFilesInDir", testify_.Anything, testify_.Anything).Return(false)
|
env.On("HasFilesInDir", testify_.Anything, testify_.Anything).Return(false)
|
||||||
env.MockGitCommand(repoRoot, "", statusArgs...)
|
env.MockGitCommand(repoRoot, "", statusArgs...)
|
||||||
|
|
||||||
|
segments := []*Segment{
|
||||||
|
{Type: GIT, Template: tc.Template, Options: tc.Options},
|
||||||
|
}
|
||||||
|
if tc.Extra != nil {
|
||||||
|
segments = append(segments, tc.Extra)
|
||||||
|
}
|
||||||
|
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
Blocks: []*Block{{Segments: []*Segment{
|
Blocks: []*Block{{Segments: segments}},
|
||||||
{Type: GIT, Template: tc.Template, Options: tc.Options},
|
ConsoleTitleTemplate: tc.ConsoleTitle,
|
||||||
}}},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.ResolveFieldSets()
|
cfg.ResolveFieldSets()
|
||||||
@@ -448,3 +486,48 @@ func TestGetRefreshesOutdatedStampVersion(t *testing.T) {
|
|||||||
assert.Equal(t, fieldSetAnalysisVersion, cfg.FieldSetsVersion)
|
assert.Equal(t, fieldSetAnalysisVersion, cfg.FieldSetsVersion)
|
||||||
assert.Zero(t, *count, "the refreshed entry must restore with current stamps")
|
assert.Zero(t, *count, "the refreshed entry must restore with current stamps")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFieldSetFingerprintDeterministic pins the segment cache key against
|
||||||
|
// Go's randomized map iteration: an unanalyzable segment's fingerprint
|
||||||
|
// hashes its heuristic sources, which include templated option values
|
||||||
|
// collected from (nested) option maps - without sorted iteration the key
|
||||||
|
// would change per process and the segment cache would never hit.
|
||||||
|
func TestFieldSetFingerprintDeterministic(t *testing.T) {
|
||||||
|
build := func() *Segment {
|
||||||
|
// fresh maps per call, as an independently parsed config would have
|
||||||
|
return &Segment{
|
||||||
|
Type: GIT,
|
||||||
|
Template: "{{ .HEAD }}",
|
||||||
|
Options: options.Map{
|
||||||
|
"cab": "{{ .Env.C }}",
|
||||||
|
"abc": "{{ .Env.A }}",
|
||||||
|
"bca": "{{ .Env.B }}",
|
||||||
|
"nested": map[string]any{
|
||||||
|
"zed": "{{ .Env.Z }}",
|
||||||
|
"alpha": "{{ .Env.AA }}",
|
||||||
|
"mixed": map[any]any{
|
||||||
|
"two": "{{ .Env.TWO }}",
|
||||||
|
"one": "{{ .Env.ONE }}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reference := build()
|
||||||
|
|
||||||
|
cfg := &Config{Blocks: []*Block{{Segments: []*Segment{reference}}}}
|
||||||
|
cfg.ResolveFieldSets()
|
||||||
|
require.False(t, reference.FieldsAnalyzable, "templated options must make the segment unanalyzable")
|
||||||
|
|
||||||
|
expected := reference.fieldSetFingerprint()
|
||||||
|
|
||||||
|
for range 32 {
|
||||||
|
segment := build()
|
||||||
|
|
||||||
|
fresh := &Config{Blocks: []*Block{{Segments: []*Segment{segment}}}}
|
||||||
|
fresh.ResolveFieldSets()
|
||||||
|
|
||||||
|
assert.Equal(t, expected, segment.fieldSetFingerprint(), "fingerprint must be stable across processes/parses")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,7 +87,14 @@ type Segment struct {
|
|||||||
// true; see FieldSetConsumer. Exported (but kept out of every config
|
// true; see FieldSetConsumer. Exported (but kept out of every config
|
||||||
// format, like Needs) so the session cache's gob round trip preserves
|
// format, like Needs) so the session cache's gob round trip preserves
|
||||||
// the analysis instead of forcing a re-run on every render.
|
// the analysis instead of forcing a re-run on every render.
|
||||||
ReferencedFields []string `json:"-" toml:"-" yaml:"-"`
|
ReferencedFields []string `json:"-" toml:"-" yaml:"-"`
|
||||||
|
// HeuristicSources is the whole-config text corpus the fallback
|
||||||
|
// heuristic scans when FieldsAnalyzable is false - stamped (and
|
||||||
|
// gob-persisted) because the reference that defeated the analysis can
|
||||||
|
// live outside this segment, in texts the segment cannot reconstruct
|
||||||
|
// from its own fields. Nil for analyzable segments and for configs that
|
||||||
|
// never went through ResolveFieldSets.
|
||||||
|
HeuristicSources []string `json:"-" toml:"-" yaml:"-"`
|
||||||
Index int `json:"index,omitempty" toml:"index,omitempty" yaml:"index,omitempty"`
|
Index int `json:"index,omitempty" toml:"index,omitempty" yaml:"index,omitempty"`
|
||||||
MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty" yaml:"min_width,omitempty"`
|
MinWidth int `json:"min_width,omitempty" toml:"min_width,omitempty" yaml:"min_width,omitempty"`
|
||||||
Duration time.Duration `json:"-" toml:"-" yaml:"-"`
|
Duration time.Duration `json:"-" toml:"-" yaml:"-"`
|
||||||
@@ -851,7 +858,7 @@ func (segment *Segment) fieldSetFingerprint() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !segment.FieldsAnalyzable {
|
if !segment.FieldsAnalyzable {
|
||||||
for _, source := range segment.heuristicSources() {
|
for _, source := range segment.fallbackSources() {
|
||||||
_, _ = h.Write([]byte(source))
|
_, _ = h.Write([]byte(source))
|
||||||
_, _ = h.Write([]byte{0})
|
_, _ = h.Write([]byte{0})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,6 @@
|
|||||||
"foreground": "p:git-foreground",
|
"foreground": "p:git-foreground",
|
||||||
"leading_diamond": "\ue0b6",
|
"leading_diamond": "\ue0b6",
|
||||||
"powerline_symbol": "\ue0b0",
|
"powerline_symbol": "\ue0b0",
|
||||||
"options": {
|
|
||||||
"fetch_status": true,
|
|
||||||
"fetch_upstream_icon": true
|
|
||||||
},
|
|
||||||
"style": "powerline",
|
"style": "powerline",
|
||||||
"template": " {{ .UpstreamIcon }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} \ueb4b {{ .StashCount }}{{ end }} ",
|
"template": " {{ .UpstreamIcon }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} \ueb4b {{ .StashCount }}{{ end }} ",
|
||||||
"trailing_diamond": "\ue0b4",
|
"trailing_diamond": "\ue0b4",
|
||||||
|
|||||||
@@ -37,10 +37,6 @@
|
|||||||
"foreground": "#193549",
|
"foreground": "#193549",
|
||||||
"leading_diamond": "\ue0b6",
|
"leading_diamond": "\ue0b6",
|
||||||
"powerline_symbol": "\ue0b0",
|
"powerline_symbol": "\ue0b0",
|
||||||
"options": {
|
|
||||||
"fetch_status": true,
|
|
||||||
"fetch_upstream_icon": true
|
|
||||||
},
|
|
||||||
"style": "powerline",
|
"style": "powerline",
|
||||||
"template": " {{ .UpstreamIcon }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} \ueb4b {{ .StashCount }}{{ end }} ",
|
"template": " {{ .UpstreamIcon }}{{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }}{{ end }}{{ if .Working.Changed }} \uf044 {{ .Working.String }}{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }} |{{ end }}{{ if .Staging.Changed }} \uf046 {{ .Staging.String }}{{ end }}{{ if gt .StashCount 0 }} \ueb4b {{ .StashCount }}{{ end }} ",
|
||||||
"trailing_diamond": "\ue0b4",
|
"trailing_diamond": "\ue0b4",
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ is a template that prints its whole context (`{{ . }}`) without naming any field
|
|||||||
The `fetch_status`, `fetch_push_status`, `fetch_upstream_icon`, `fetch_bare_info` and `fetch_user`
|
The `fetch_status`, `fetch_push_status`, `fetch_upstream_icon`, `fetch_bare_info` and `fetch_user`
|
||||||
options no longer exist: fetching is driven entirely by what your templates reference. To stop fetching
|
options no longer exist: fetching is driven entirely by what your templates reference. To stop fetching
|
||||||
something, stop rendering it. Unknown `fetch_*` keys in an existing config are ignored silently.
|
something, stop rendering it. Unknown `fetch_*` keys in an existing config are ignored silently.
|
||||||
|
|
||||||
|
Defaults that changed with this, because the default templates reference the matching fields:
|
||||||
|
|
||||||
|
- the default config's statusline segment now fetches the upstream icon its explicit
|
||||||
|
`fetch_upstream_icon: false` used to suppress
|
||||||
|
- the svn, mercurial and jujutsu segments now fetch their working-copy status by default
|
||||||
|
- the jujutsu segment's default rendering therefore requires the `jj` binary
|
||||||
:::
|
:::
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|
|||||||
Reference in New Issue
Block a user