diff --git a/apm.lock b/apm.lock index 7e6e43eaaeff..1e5ea554760b 100644 --- a/apm.lock +++ b/apm.lock @@ -1,5 +1,5 @@ lockfile_version: '1' -generated_at: '2026-03-11T11:40:09.315428+00:00' +generated_at: '2026-03-11T19:43:11.877128+00:00' apm_version: 0.7.7 dependencies: - repo_url: JanDeDobbeleer/agentic @@ -9,7 +9,6 @@ dependencies: is_virtual: true package_type: claude_skill deployed_files: - - .claude/skills/conventional-commit - .github/skills/conventional-commit - repo_url: JanDeDobbeleer/agentic host: github.com @@ -18,7 +17,6 @@ dependencies: is_virtual: true package_type: claude_skill deployed_files: - - .claude/skills/golang - .github/skills/golang - repo_url: JanDeDobbeleer/agentic host: github.com @@ -27,7 +25,6 @@ dependencies: is_virtual: true package_type: claude_skill deployed_files: - - .claude/skills/markdown - .github/skills/markdown - repo_url: JanDeDobbeleer/agentic host: github.com @@ -36,5 +33,4 @@ dependencies: is_virtual: true package_type: claude_skill deployed_files: - - .claude/skills/powershell - .github/skills/powershell diff --git a/src/cli/upgrade.go b/src/cli/upgrade.go index 2d8f2f4cbc09..6eb8f7f09ddd 100644 --- a/src/cli/upgrade.go +++ b/src/cli/upgrade.go @@ -71,9 +71,11 @@ var upgradeCmd = &cobra.Command{ } terminal.Init(sh) - fmt.Print(terminal.StartProgress()) cfg := config.Get(configFlag, false) + terminal.Features = cfg.TerminalFeatures + + fmt.Print(terminal.StartProgress()) defer func() { fmt.Print(terminal.StopProgress()) diff --git a/src/config/config.go b/src/config/config.go index a4087ba1ec57..f994dbbde8b2 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -70,7 +70,7 @@ type Config struct { ToolTipsAction Action `json:"tooltips_action,omitempty" toml:"tooltips_action,omitempty" yaml:"tooltips_action,omitempty"` Blocks []*Block `json:"blocks,omitempty" toml:"blocks,omitempty" yaml:"blocks,omitempty"` Cycle color.Cycle `json:"cycle,omitempty" toml:"cycle,omitempty" yaml:"cycle,omitempty"` - ITermFeatures terminal.ITermFeatures `json:"iterm_features,omitempty" toml:"iterm_features,omitempty" yaml:"iterm_features,omitempty"` + TerminalFeatures map[string][]string `json:"terminal_features,omitempty" toml:"terminal_features,omitempty" yaml:"terminal_features,omitempty"` Tooltips []*Segment `json:"tooltips,omitempty" toml:"tooltips,omitempty" yaml:"tooltips,omitempty"` hash uint64 Version int `json:"version" toml:"version" yaml:"version"` @@ -166,9 +166,14 @@ func (cfg *Config) Features(env runtime.Environment) shell.Features { feats |= shell.Tooltips } - if env.Shell() == shell.FISH && cfg.ITermFeatures != nil && cfg.ITermFeatures.Contains(terminal.PromptMark) { - log.Debug("prompt mark enabled") - feats |= shell.PromptMark + if env.Shell() == shell.FISH { + for _, features := range cfg.TerminalFeatures { + if slices.Contains(features, terminal.PromptMark) { + log.Debug("prompt mark enabled") + feats |= shell.PromptMark + break + } + } } for i, block := range cfg.Blocks { diff --git a/src/prompt/engine.go b/src/prompt/engine.go index e97fc6cc5d0e..c5db3e5f6f0f 100644 --- a/src/prompt/engine.go +++ b/src/prompt/engine.go @@ -566,6 +566,7 @@ func New(flags *runtime.Flags) *Engine { } terminal.Init(sh) + terminal.Features = cfg.TerminalFeatures terminal.BackgroundColor = cfg.TerminalBackground.ResolveTemplate() terminal.Colors = cfg.MakeColors(env) terminal.Plain = flags.Plain diff --git a/src/prompt/primary.go b/src/prompt/primary.go index 227b7e5b9bab..e3e40243d490 100644 --- a/src/prompt/primary.go +++ b/src/prompt/primary.go @@ -115,9 +115,9 @@ func (e *Engine) writePrimaryPromptInternal(needsPrimaryRPrompt, fromCache bool) e.currentLineLength++ } - if e.Config.ITermFeatures != nil && e.isIterm() { + if terminal.HasITermFeatures() { host, _ := e.Env.Host() - e.write(terminal.RenderItermFeatures(e.Config.ITermFeatures, e.Env.Shell(), e.Env.Pwd(), e.Env.User(), host)) + e.write(terminal.RenderItermFeatures(e.Env.Shell(), e.Env.Pwd(), e.Env.User(), host)) } if e.Config.ShellIntegration { diff --git a/src/terminal/iterm.go b/src/terminal/iterm.go index 5bc051494abd..15244696deb2 100644 --- a/src/terminal/iterm.go +++ b/src/terminal/iterm.go @@ -1,7 +1,6 @@ package terminal import ( - "encoding/gob" "fmt" "slices" @@ -9,42 +8,31 @@ import ( "github.com/jandedobbeleer/oh-my-posh/src/text" ) -func init() { - gob.Register(&ITermFeatures{}) -} - -type iTermFeature string - const ( - PromptMark iTermFeature = "prompt_mark" - CurrentDir iTermFeature = "current_dir" - RemoteHost iTermFeature = "remote_host" + PromptMark = "prompt_mark" + CurrentDir = "current_dir" + RemoteHost = "remote_host" ) -type ITermFeatures []iTermFeature - -func (f ITermFeatures) Contains(feature iTermFeature) bool { - return slices.Contains(f, feature) +func HasITermFeatures() bool { + return SupportsFeature(PromptMark) || SupportsFeature(CurrentDir) || SupportsFeature(RemoteHost) } -func RenderItermFeatures(features ITermFeatures, sh, pwd, user, host string) string { +func RenderItermFeatures(sh, pwd, user, host string) string { + result := text.NewBuilder() + supportedShells := []string{shell.BASH, shell.ZSH} - result := text.NewBuilder() + if SupportsFeature(PromptMark) && slices.Contains(supportedShells, sh) { + result.WriteString(formats.ITermPromptMark) + } + + if SupportsFeature(CurrentDir) { + result.WriteString(fmt.Sprintf(formats.ITermCurrentDir, pwd)) + } - for _, feature := range features { - switch feature { - case PromptMark: - if !slices.Contains(supportedShells, sh) { - continue - } - - result.WriteString(formats.ITermPromptMark) - case CurrentDir: - result.WriteString(fmt.Sprintf(formats.ITermCurrentDir, pwd)) - case RemoteHost: - result.WriteString(fmt.Sprintf(formats.ITermRemoteHost, user, host)) - } + if SupportsFeature(RemoteHost) { + result.WriteString(fmt.Sprintf(formats.ITermRemoteHost, user, host)) } return result.String() diff --git a/src/terminal/writer.go b/src/terminal/writer.go index 5074a133bd4b..f14a0b7cf2b3 100644 --- a/src/terminal/writer.go +++ b/src/terminal/writer.go @@ -3,6 +3,7 @@ package terminal import ( "fmt" "os" + "slices" "strings" "github.com/jandedobbeleer/oh-my-posh/src/color" @@ -62,6 +63,11 @@ var ( Shell string Program string + // Features maps terminal program names to the list of features they support. + // When set, SupportsFeature uses this map to resolve feature support. + // When unset, SupportsFeature falls back to the built-in defaults. + Features map[string][]string + formats *shell.Formats ) @@ -93,6 +99,9 @@ const ( setProgress = "\x1b]9;4;4;%d\x07" endProgress = "\x1b]9;4;0;0\x07" + // Progress is the feature name for OSC 9;4 taskbar progress sequences. + Progress = "progress" + WindowsTerminal = "Windows Terminal" Warp = "WarpTerminal" ITerm = "iTerm.app" @@ -247,8 +256,19 @@ func LineBreak() string { return cr + lf } +// SupportsFeature reports whether the current terminal program supports the named feature. +// When Features contains an entry for the current Program, the configured feature list is used. +// Otherwise, the provided defaults (program names) are checked against the current Program. +func SupportsFeature(feature string, defaults ...string) bool { + if features, ok := Features[Program]; ok { + return slices.Contains(features, feature) + } + + return slices.Contains(defaults, Program) +} + func StartProgress() string { - if Program != WindowsTerminal { + if !SupportsFeature(Progress, WindowsTerminal) { return "" } @@ -256,7 +276,7 @@ func StartProgress() string { } func SetProgress(percentage int) string { - if Program != WindowsTerminal { + if !SupportsFeature(Progress, WindowsTerminal) { return "" } @@ -264,7 +284,7 @@ func SetProgress(percentage int) string { } func StopProgress() string { - if Program != WindowsTerminal { + if !SupportsFeature(Progress, WindowsTerminal) { return "" } diff --git a/src/terminal/writer_test.go b/src/terminal/writer_test.go index 0e3823920c86..098af2228b3d 100644 --- a/src/terminal/writer_test.go +++ b/src/terminal/writer_test.go @@ -294,3 +294,97 @@ func TestWriteLength(t *testing.T) { assert.Equal(t, tc.Expected, got, tc.Case) } } + +func TestSupportsFeature(t *testing.T) { + cases := []struct { + Case string + Program string + Features map[string][]string + Feature string + Defaults []string + Expected bool + }{ + { + Case: "default match - Windows Terminal matches built-in default", + Program: WindowsTerminal, + Feature: Progress, + Defaults: []string{WindowsTerminal}, + Expected: true, + }, + { + Case: "default no match - Ghostty not in built-in defaults", + Program: "Ghostty", + Feature: Progress, + Defaults: []string{WindowsTerminal}, + Expected: false, + }, + { + Case: "configured list - Ghostty added by user", + Program: "Ghostty", + Feature: Progress, + Features: map[string][]string{ + "Ghostty": {Progress}, + }, + Defaults: []string{WindowsTerminal}, + Expected: true, + }, + { + Case: "configured list - Windows Terminal explicitly cleared", + Program: WindowsTerminal, + Feature: Progress, + Features: map[string][]string{ + WindowsTerminal: {}, + }, + Defaults: []string{WindowsTerminal}, + Expected: false, + }, + { + Case: "configured list - unlisted program falls back to defaults", + Program: WindowsTerminal, + Feature: Progress, + Features: map[string][]string{ + "Ghostty": {Progress}, + }, + Defaults: []string{WindowsTerminal}, + Expected: true, + }, + { + Case: "nil Features map uses defaults", + Program: WindowsTerminal, + Feature: Progress, + Features: nil, + Defaults: []string{WindowsTerminal}, + Expected: true, + }, + { + Case: "iTerm prompt_mark configured for iTerm.app", + Program: ITerm, + Feature: PromptMark, + Features: map[string][]string{ + ITerm: {PromptMark, CurrentDir, RemoteHost}, + }, + Expected: true, + }, + { + Case: "iTerm prompt_mark not configured - no defaults", + Program: ITerm, + Feature: PromptMark, + Expected: false, + }, + } + + for _, tc := range cases { + t.Run(tc.Case, func(t *testing.T) { + Program = tc.Program + Features = tc.Features + + got := SupportsFeature(tc.Feature, tc.Defaults...) + + assert.Equal(t, tc.Expected, got, tc.Case) + }) + } + + // Reset global state + Features = nil + Program = Unknown +} diff --git a/themes/schema.json b/themes/schema.json index 8343451c8a0d..8c9b6d697bee 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -2622,7 +2622,7 @@ }, "extensions": { "default": [ - "*.🔥", + "*.\ud83d\udd25", "*.mojo", "mojoproject.toml" ] @@ -5202,16 +5202,15 @@ "title": "Accent color", "$ref": "#/definitions/color" }, - "iterm_features": { - "type": "array", - "title": "The iTerm2 features to enable", - "items": { - "type": "string", - "enum": [ - "prompt_mark", - "current_dir", - "remote_host" - ] + "terminal_features": { + "type": "object", + "title": "Terminal feature support overrides", + "description": "Maps terminal program names to the list of features they support. Keys are program names matched against the $TERM_PROGRAM environment variable (or \"Windows Terminal\" when $WT_SESSION is set). Values are lists of feature names (e.g. \"progress\", \"prompt_mark\", \"current_dir\", \"remote_host\"). Programs not listed fall back to built-in defaults.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } } }, "var": { diff --git a/website/docs/configuration/general.mdx b/website/docs/configuration/general.mdx index bbf9c633be65..6ffc52bca394 100644 --- a/website/docs/configuration/general.mdx +++ b/website/docs/configuration/general.mdx @@ -137,8 +137,8 @@ For example, the following is a valid `--config` flag: | `enable_cursor_positioning` | `boolean` | `false` | enable fetching the cursor position in bash and zsh to allow automatic hiding of leading newlines when at the top of the shell | | `patch_pwsh_bleed` | `boolean` | `false` | patch a PowerShell bug where the background colors bleed into the next line at the end of the buffer (can be removed when [this][pwsh-bleed] is merged) | | `upgrade` | `Upgrade` | | enable auto upgrade or the upgrade notice. See [Upgrade] | -| `iterm_features` | `[]string` | `false` | enable iTerm2 specific features: | -| `maps` | [`Maps`](#maps) | | a list of custom text mappings | +| `terminal_features` | `map[string][]string` | | configure which features each terminal supports. Keys are terminal program names matched against the `$TERM_PROGRAM` environment variable (or `Windows Terminal` when `$WT_SESSION` is set). Values are lists of feature names: `progress` (OSC 9;4 taskbar progress), `prompt_mark` (iTerm2 prompt mark for bash/zsh), `current_dir` (iTerm2 current directory), `remote_host` (iTerm2 remote host). A program not listed falls back to built-in defaults (e.g. `progress` defaults to `Windows Terminal`). This replaces the old `iterm_features` config — iTerm2 features are now configured under the `iTerm.app` key. | +| `maps` | [`Maps`](#maps) | | a list of custom text mappings | | `async` | `boolean` | `false` | load the prompt async. Will either load the standard prompt, or allow you to start typing right away. Supported for `pwsh`, `powershell`, `zsh`, `bash` and `fish` | | `version` | `int` | `4` | the config version, currently at `4` | | `extends` | `string` | | the configuration to [extend] from |