Skip to content

Commit f47da95

Browse files
feat(exit): implement pipestatus
BREAKING CHANGE: exit segment is now called status segment. The exit keyword is now deprecated and will be removed in a future release. Please use the status keyword instead: ```diff "segments": { { - "type": "exit" + "type": "status" } } ``` Additionally, the status segment configuration has changed to support $PIPESTATUS. You can include a status template to customize the rendering of each individual status code (supported in fish, zsh and bash). ```json "segments": { { "type": "status", "properties": { "status_template": "{{ if gt .Code 0 }}\uf071{{ else }}\uf00c{{ end }}", "status_separator": " " } } } ``` In case no $PIPESTATUS is available, the status segment will fall back to the exit code of the last command using the status template for rendering. The `{{ .Meaning }}` property has been marked as deprecated and can be replaced with `{{ reason .Code }}`, allowing it to be reused in cross segment templates. resolves JanDeDobbeleer#4070
1 parent 2da3722 commit f47da95

File tree

109 files changed

+470
-330
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+470
-330
lines changed

.vscode/launch.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"print",
4141
"transient",
4242
"--shell=pwsh",
43-
"--error=1"
43+
"--status=1"
4444
]
4545
},
4646
{

src/cli/print.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import (
1212
var (
1313
pwd string
1414
pswd string
15-
exitCode int
15+
status int
16+
pipestatus string
1617
timing float64
1718
stackCount int
1819
terminalWidth int
@@ -22,7 +23,7 @@ var (
2223
command string
2324
shellVersion string
2425
plain bool
25-
noExitCode bool
26+
noStatus bool
2627
)
2728

2829
// printCmd represents the prompt command
@@ -51,7 +52,8 @@ var printCmd = &cobra.Command{
5152
Config: config,
5253
PWD: pwd,
5354
PSWD: pswd,
54-
ErrorCode: exitCode,
55+
ErrorCode: status,
56+
PipeStatus: pipestatus,
5557
ExecutionTime: timing,
5658
StackCount: stackCount,
5759
TerminalWidth: terminalWidth,
@@ -61,7 +63,7 @@ var printCmd = &cobra.Command{
6163
Plain: plain,
6264
Primary: args[0] == "primary",
6365
Cleared: cleared,
64-
NoExitCode: noExitCode,
66+
NoExitCode: noStatus,
6567
}
6668

6769
eng := engine.New(flags)
@@ -95,14 +97,18 @@ func init() { //nolint:gochecknoinits
9597
printCmd.Flags().StringVar(&pswd, "pswd", "", "current working directory (according to pwsh)")
9698
printCmd.Flags().StringVar(&shellName, "shell", "", "the shell to print for")
9799
printCmd.Flags().StringVar(&shellVersion, "shell-version", "", "the shell version")
98-
printCmd.Flags().IntVarP(&exitCode, "error", "e", 0, "last exit code")
100+
printCmd.Flags().IntVar(&status, "status", 0, "last known status code")
101+
printCmd.Flags().BoolVar(&noStatus, "no-status", false, "no valid status code (cancelled or no command yet)")
102+
printCmd.Flags().StringVar(&pipestatus, "pipestatus", "", "the PIPESTATUS array")
99103
printCmd.Flags().Float64Var(&timing, "execution-time", 0, "timing of the last command")
100104
printCmd.Flags().IntVarP(&stackCount, "stack-count", "s", 0, "number of locations on the stack")
101105
printCmd.Flags().IntVarP(&terminalWidth, "terminal-width", "w", 0, "width of the terminal")
102106
printCmd.Flags().StringVar(&command, "command", "", "tooltip command")
103107
printCmd.Flags().BoolVarP(&plain, "plain", "p", false, "plain text output (no ANSI)")
104108
printCmd.Flags().BoolVar(&cleared, "cleared", false, "do we have a clear terminal or not")
105109
printCmd.Flags().BoolVar(&eval, "eval", false, "output the prompt for eval")
106-
printCmd.Flags().BoolVar(&noExitCode, "no-exit-code", false, "no valid exit code (cancelled or no command yet)")
110+
// Deprecated flags
111+
printCmd.Flags().IntVarP(&status, "error", "e", 0, "last exit code")
112+
printCmd.Flags().BoolVar(&noStatus, "no-exit-code", false, "no valid exit code (cancelled or no command yet)")
107113
RootCmd.AddCommand(printCmd)
108114
}

src/engine/migrate.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func (segment *Segment) migrationOne(env platform.Environment) {
135135
segment.migrateColorOverride("version_mismatch_color", "{{ if .Mismatch }}%s{{ end }}", background)
136136
}
137137
case EXIT:
138-
template := segment.Properties.GetString(segmentTemplate, segment.writer.Template())
138+
template := segment.Properties.GetString(segmentTemplate, "{{ if gt .Code 0 }}\uf00d {{ .Meaning }}{{ else }}\uf42e{{ end }}")
139139
if strings.Contains(template, ".Text") {
140140
template = strings.ReplaceAll(template, ".Text", ".Meaning")
141141
segment.Properties[segmentTemplate] = template

src/engine/migrate_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/jandedobbeleer/oh-my-posh/src/segments"
1010

1111
"github.com/stretchr/testify/assert"
12+
mock2 "github.com/stretchr/testify/mock"
1213
)
1314

1415
const (
@@ -326,7 +327,9 @@ func TestSegmentTemplateMigration(t *testing.T) {
326327
Type: tc.Type,
327328
Properties: tc.Props,
328329
}
329-
segment.migrationOne(&mock.MockedEnvironment{})
330+
env := &mock.MockedEnvironment{}
331+
env.On("Debug", mock2.Anything).Return(nil)
332+
segment.migrationOne(env)
330333
assert.Equal(t, tc.Expected, segment.Properties[segmentTemplate], tc.Case)
331334
}
332335
}

src/engine/prompt.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const (
2121

2222
func (e *Engine) Primary() string {
2323
if e.Config.ShellIntegration {
24-
exitCode := e.Env.ErrorCode()
24+
exitCode, _ := e.Env.StatusCodes()
2525
e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
2626
e.write(e.Writer.PromptStart())
2727
}
@@ -150,7 +150,7 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string {
150150
}
151151

152152
if promptType == Transient && e.Config.ShellIntegration {
153-
exitCode := e.Env.ErrorCode()
153+
exitCode, _ := e.Env.StatusCodes()
154154
e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
155155
e.write(e.Writer.PromptStart())
156156
}

src/engine/segment.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ const (
217217
SITECORE SegmentType = "sitecore"
218218
// SPOTIFY writes the SPOTIFY status for Mac
219219
SPOTIFY SegmentType = "spotify"
220+
// STATUS writes the last know command status
221+
STATUS SegmentType = "status"
220222
// STRAVA is a sports activity tracker
221223
STRAVA SegmentType = "strava"
222224
// Subversion segment
@@ -275,7 +277,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
275277
DOTNET: func() SegmentWriter { return &segments.Dotnet{} },
276278
EXECUTIONTIME: func() SegmentWriter { return &segments.Executiontime{} },
277279
ELIXIR: func() SegmentWriter { return &segments.Elixir{} },
278-
EXIT: func() SegmentWriter { return &segments.Exit{} },
280+
EXIT: func() SegmentWriter { return &segments.Status{} },
279281
FLUTTER: func() SegmentWriter { return &segments.Flutter{} },
280282
FOSSIL: func() SegmentWriter { return &segments.Fossil{} },
281283
GCP: func() SegmentWriter { return &segments.Gcp{} },
@@ -314,6 +316,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
314316
SHELL: func() SegmentWriter { return &segments.Shell{} },
315317
SITECORE: func() SegmentWriter { return &segments.Sitecore{} },
316318
SPOTIFY: func() SegmentWriter { return &segments.Spotify{} },
319+
STATUS: func() SegmentWriter { return &segments.Status{} },
317320
STRAVA: func() SegmentWriter { return &segments.Strava{} },
318321
SVN: func() SegmentWriter { return &segments.Svn{} },
319322
SWIFT: func() SegmentWriter { return &segments.Swift{} },

src/mock/environment.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ func (env *MockedEnvironment) RunShellCommand(shell, command string) string {
105105
return args.String(0)
106106
}
107107

108-
func (env *MockedEnvironment) ErrorCode() int {
108+
func (env *MockedEnvironment) StatusCodes() (int, string) {
109109
args := env.Called()
110-
return args.Int(0)
110+
return args.Int(0), args.String(1)
111111
}
112112

113113
func (env *MockedEnvironment) ExecutionTime() float64 {

src/platform/shell.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ var (
5252

5353
type Flags struct {
5454
ErrorCode int
55+
PipeStatus string
5556
Config string
5657
Shell string
5758
ShellVersion string
@@ -215,7 +216,7 @@ type Environment interface {
215216
GOOS() string
216217
Shell() string
217218
Platform() string
218-
ErrorCode() int
219+
StatusCodes() (int, string)
219220
PathSeparator() string
220221
HasFiles(pattern string) bool
221222
HasFilesInDir(dir, pattern string) bool
@@ -611,9 +612,9 @@ func (env *Shell) HasCommand(command string) bool {
611612
return false
612613
}
613614

614-
func (env *Shell) ErrorCode() int {
615+
func (env *Shell) StatusCodes() (int, string) {
615616
defer env.Trace(time.Now())
616-
return env.CmdFlags.ErrorCode
617+
return env.CmdFlags.ErrorCode, env.CmdFlags.PipeStatus
617618
}
618619

619620
func (env *Shell) ExecutionTime() float64 {
@@ -798,7 +799,7 @@ func (env *Shell) TemplateCache() *TemplateCache {
798799
tmplCache.Root = env.Root()
799800
tmplCache.Shell = env.Shell()
800801
tmplCache.ShellVersion = env.CmdFlags.ShellVersion
801-
tmplCache.Code = env.ErrorCode()
802+
tmplCache.Code, _ = env.StatusCodes()
802803
tmplCache.WSL = env.IsWsl()
803804
tmplCache.Segments = make(map[string]interface{})
804805
tmplCache.PromptCount = env.CmdFlags.PromptCount

src/segments/exit_test.go

-92
This file was deleted.

src/segments/status.go

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package segments
2+
3+
import (
4+
"strconv"
5+
"strings"
6+
7+
"github.com/jandedobbeleer/oh-my-posh/src/platform"
8+
"github.com/jandedobbeleer/oh-my-posh/src/properties"
9+
"github.com/jandedobbeleer/oh-my-posh/src/template"
10+
)
11+
12+
const (
13+
StatusTemplate properties.Property = "status_template"
14+
StatusSeparator properties.Property = "status_separator"
15+
)
16+
17+
type Status struct {
18+
props properties.Properties
19+
env platform.Environment
20+
21+
String string
22+
Error bool
23+
Code int
24+
25+
template *template.Text
26+
27+
// Deprecated: Use {{ reason .Code }} instead
28+
Meaning string
29+
}
30+
31+
func (s *Status) Template() string {
32+
return " {{ .String }} "
33+
}
34+
35+
func (s *Status) Enabled() bool {
36+
status, pipeStatus := s.env.StatusCodes()
37+
38+
s.String = s.formatStatus(status, pipeStatus)
39+
// Deprecated: Use {{ reason .Code }} instead
40+
s.Meaning = template.GetReasonFromStatus(status)
41+
42+
if s.props.GetBool(properties.AlwaysEnabled, false) {
43+
return true
44+
}
45+
46+
return s.Error
47+
}
48+
49+
func (s *Status) Init(props properties.Properties, env platform.Environment) {
50+
s.props = props
51+
s.env = env
52+
53+
statusTemplate := s.props.GetString(StatusTemplate, "{{ .Code }}")
54+
s.template = &template.Text{
55+
Template: statusTemplate,
56+
Env: s.env,
57+
}
58+
}
59+
60+
func (s *Status) formatStatus(status int, pipeStatus string) string {
61+
if status != 0 {
62+
s.Error = true
63+
}
64+
65+
if len(pipeStatus) == 0 {
66+
s.Code = status
67+
s.template.Context = s
68+
if text, err := s.template.Render(); err == nil {
69+
return text
70+
}
71+
return strconv.Itoa(status)
72+
}
73+
74+
StatusSeparator := s.props.GetString(StatusSeparator, "|")
75+
76+
var builder strings.Builder
77+
78+
splitted := strings.Split(pipeStatus, " ")
79+
for i, codeStr := range splitted {
80+
write := func(text string) {
81+
if i > 0 {
82+
builder.WriteString(StatusSeparator)
83+
}
84+
builder.WriteString(text)
85+
}
86+
87+
code, err := strconv.Atoi(codeStr)
88+
if err != nil {
89+
write(codeStr)
90+
continue
91+
}
92+
93+
if code != 0 {
94+
s.Error = true
95+
}
96+
97+
s.Code = code
98+
s.template.Context = s
99+
text, err := s.template.Render()
100+
if err != nil {
101+
write(codeStr)
102+
continue
103+
}
104+
105+
write(text)
106+
}
107+
108+
return builder.String()
109+
}

0 commit comments

Comments
 (0)